/src/botan/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * OpenPGP S2K |
3 | | * (C) 1999-2007,2017 Jack Lloyd |
4 | | * (C) 2018 Ribose Inc |
5 | | * |
6 | | * Botan is released under the Simplified BSD License (see license.txt) |
7 | | */ |
8 | | |
9 | | #include <botan/pgp_s2k.h> |
10 | | #include <botan/exceptn.h> |
11 | | #include <botan/internal/timer.h> |
12 | | #include <algorithm> |
13 | | |
14 | | namespace Botan { |
15 | | |
16 | | namespace { |
17 | | |
18 | | void pgp_s2k(HashFunction& hash, |
19 | | uint8_t output_buf[], size_t output_len, |
20 | | const char* password, const size_t password_size, |
21 | | const uint8_t salt[], size_t salt_len, |
22 | | size_t iterations) |
23 | 0 | { |
24 | 0 | if(iterations > 1 && salt_len == 0) |
25 | 0 | throw Invalid_Argument("OpenPGP S2K requires a salt in iterated mode"); |
26 | | |
27 | 0 | secure_vector<uint8_t> input_buf(salt_len + password_size); |
28 | 0 | if(salt_len > 0) |
29 | 0 | { |
30 | 0 | copy_mem(&input_buf[0], salt, salt_len); |
31 | 0 | } |
32 | 0 | if(password_size > 0) |
33 | 0 | { |
34 | 0 | copy_mem(&input_buf[salt_len], |
35 | 0 | cast_char_ptr_to_uint8(password), |
36 | 0 | password_size); |
37 | 0 | } |
38 | |
|
39 | 0 | secure_vector<uint8_t> hash_buf(hash.output_length()); |
40 | |
|
41 | 0 | size_t pass = 0; |
42 | 0 | size_t generated = 0; |
43 | |
|
44 | 0 | while(generated != output_len) |
45 | 0 | { |
46 | 0 | const size_t output_this_pass = |
47 | 0 | std::min(hash_buf.size(), output_len - generated); |
48 | | |
49 | | // Preload some number of zero bytes (empty first iteration) |
50 | 0 | std::vector<uint8_t> zero_padding(pass); |
51 | 0 | hash.update(zero_padding); |
52 | | |
53 | | // The input is always fully processed even if iterations is very small |
54 | 0 | if(input_buf.empty() == false) |
55 | 0 | { |
56 | 0 | size_t left = std::max(iterations, input_buf.size()); |
57 | 0 | while(left > 0) |
58 | 0 | { |
59 | 0 | const size_t input_to_take = std::min(left, input_buf.size()); |
60 | 0 | hash.update(input_buf.data(), input_to_take); |
61 | 0 | left -= input_to_take; |
62 | 0 | } |
63 | 0 | } |
64 | |
|
65 | 0 | hash.final(hash_buf.data()); |
66 | 0 | copy_mem(output_buf + generated, hash_buf.data(), output_this_pass); |
67 | 0 | generated += output_this_pass; |
68 | 0 | ++pass; |
69 | 0 | } |
70 | 0 | } |
71 | | |
72 | | } |
73 | | |
74 | | size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len, |
75 | | const std::string& password, |
76 | | const uint8_t salt[], size_t salt_len, |
77 | | size_t iterations, |
78 | | std::chrono::milliseconds msec) const |
79 | 0 | { |
80 | 0 | std::unique_ptr<PasswordHash> pwdhash; |
81 | |
|
82 | 0 | if(iterations == 0) |
83 | 0 | { |
84 | 0 | RFC4880_S2K_Family s2k_params(m_hash->clone()); |
85 | 0 | iterations = s2k_params.tune(output_len, msec, 0)->iterations(); |
86 | 0 | } |
87 | |
|
88 | 0 | pgp_s2k(*m_hash, output_buf, output_len, |
89 | 0 | password.c_str(), password.size(), |
90 | 0 | salt, salt_len, |
91 | 0 | iterations); |
92 | |
|
93 | 0 | return iterations; |
94 | 0 | } |
95 | | |
96 | | std::string RFC4880_S2K_Family::name() const |
97 | 0 | { |
98 | 0 | return "OpenPGP-S2K(" + m_hash->name() + ")"; |
99 | 0 | } |
100 | | |
101 | | std::unique_ptr<PasswordHash> RFC4880_S2K_Family::tune(size_t output_len, std::chrono::milliseconds msec, size_t) const |
102 | 0 | { |
103 | 0 | const auto tune_time = BOTAN_PBKDF_TUNING_TIME; |
104 | |
|
105 | 0 | const size_t buf_size = 1024; |
106 | 0 | std::vector<uint8_t> buffer(buf_size); |
107 | |
|
108 | 0 | Timer timer("RFC4880_S2K", buf_size); |
109 | 0 | timer.run_until_elapsed(tune_time, [&]() { |
110 | 0 | m_hash->update(buffer); |
111 | 0 | }); |
112 | |
|
113 | 0 | const double hash_bytes_per_second = timer.bytes_per_second(); |
114 | 0 | const uint64_t desired_nsec = msec.count() * 1000000; |
115 | |
|
116 | 0 | const size_t hash_size = m_hash->output_length(); |
117 | 0 | const size_t blocks_required = (output_len <= hash_size ? 1 : (output_len + hash_size - 1) / hash_size); |
118 | |
|
119 | 0 | const double bytes_to_be_hashed = (hash_bytes_per_second * (desired_nsec / 1000000000.0)) / blocks_required; |
120 | 0 | const size_t iterations = RFC4880_round_iterations(static_cast<size_t>(bytes_to_be_hashed)); |
121 | |
|
122 | 0 | return std::make_unique<RFC4880_S2K>(m_hash->clone(), iterations); |
123 | 0 | } |
124 | | |
125 | | std::unique_ptr<PasswordHash> RFC4880_S2K_Family::from_params(size_t iter, size_t, size_t) const |
126 | 0 | { |
127 | 0 | return std::make_unique<RFC4880_S2K>(m_hash->clone(), iter); |
128 | 0 | } |
129 | | |
130 | | std::unique_ptr<PasswordHash> RFC4880_S2K_Family::default_params() const |
131 | 0 | { |
132 | 0 | return std::make_unique<RFC4880_S2K>(m_hash->clone(), 50331648); |
133 | 0 | } |
134 | | |
135 | | std::unique_ptr<PasswordHash> RFC4880_S2K_Family::from_iterations(size_t iter) const |
136 | 0 | { |
137 | 0 | return std::make_unique<RFC4880_S2K>(m_hash->clone(), iter); |
138 | 0 | } |
139 | | |
140 | | RFC4880_S2K::RFC4880_S2K(HashFunction* hash, size_t iterations) : |
141 | | m_hash(hash), |
142 | | m_iterations(iterations) |
143 | 0 | { |
144 | 0 | } |
145 | | |
146 | | std::string RFC4880_S2K::to_string() const |
147 | 0 | { |
148 | 0 | return "OpenPGP-S2K(" + m_hash->name() + "," + std::to_string(m_iterations) + ")"; |
149 | 0 | } |
150 | | |
151 | | void RFC4880_S2K::derive_key(uint8_t out[], size_t out_len, |
152 | | const char* password, const size_t password_len, |
153 | | const uint8_t salt[], size_t salt_len) const |
154 | 0 | { |
155 | 0 | pgp_s2k(*m_hash, out, out_len, |
156 | 0 | password, password_len, |
157 | 0 | salt, salt_len, |
158 | 0 | m_iterations); |
159 | 0 | } |
160 | | |
161 | | } |