/src/botan/src/lib/pbkdf/scrypt/scrypt.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * (C) 2018 Jack Lloyd |
3 | | * (C) 2018 Ribose Inc |
4 | | * |
5 | | * Botan is released under the Simplified BSD License (see license.txt) |
6 | | */ |
7 | | |
8 | | #include <botan/scrypt.h> |
9 | | #include <botan/pbkdf2.h> |
10 | | #include <botan/exceptn.h> |
11 | | #include <botan/internal/salsa20.h> |
12 | | #include <botan/internal/loadstor.h> |
13 | | #include <botan/internal/bit_ops.h> |
14 | | #include <botan/internal/timer.h> |
15 | | #include <sstream> |
16 | | |
17 | | namespace Botan { |
18 | | |
19 | | namespace { |
20 | | |
21 | | size_t scrypt_memory_usage(size_t N, size_t r, size_t p) |
22 | 0 | { |
23 | 0 | return 128 * r * (N + p); |
24 | 0 | } |
25 | | |
26 | | } |
27 | | |
28 | | std::string Scrypt_Family::name() const |
29 | 0 | { |
30 | 0 | return "Scrypt"; |
31 | 0 | } |
32 | | |
33 | | std::unique_ptr<PasswordHash> Scrypt_Family::default_params() const |
34 | 0 | { |
35 | 0 | return std::make_unique<Scrypt>(32768, 8, 1); |
36 | 0 | } |
37 | | |
38 | | std::unique_ptr<PasswordHash> Scrypt_Family::tune(size_t output_length, |
39 | | std::chrono::milliseconds msec, |
40 | | size_t max_memory_usage_mb) const |
41 | 0 | { |
42 | 0 | BOTAN_UNUSED(output_length); |
43 | | |
44 | | /* |
45 | | * Some rough relations between scrypt parameters and runtime. |
46 | | * Denote here by stime(N,r,p) the msec it takes to run scrypt. |
47 | | * |
48 | | * Emperically for smaller sizes: |
49 | | * stime(N,8*r,p) / stime(N,r,p) is ~ 6-7 |
50 | | * stime(N,r,8*p) / stime(N,r,8*p) is ~ 7 |
51 | | * stime(2*N,r,p) / stime(N,r,p) is ~ 2 |
52 | | * |
53 | | * Compute stime(8192,1,1) as baseline and extrapolate |
54 | | */ |
55 | |
|
56 | 0 | const size_t max_memory_usage = max_memory_usage_mb * 1024 * 1024; |
57 | | // Starting parameters |
58 | 0 | size_t N = 8192; |
59 | 0 | size_t r = 1; |
60 | 0 | size_t p = 1; |
61 | |
|
62 | 0 | Timer timer("Scrypt"); |
63 | 0 | const auto tune_time = BOTAN_PBKDF_TUNING_TIME; |
64 | |
|
65 | 0 | auto pwdhash = this->from_params(N, r, p); |
66 | |
|
67 | 0 | timer.run_until_elapsed(tune_time, [&]() { |
68 | 0 | uint8_t output[32] = { 0 }; |
69 | 0 | pwdhash->derive_key(output, sizeof(output), |
70 | 0 | "test", 4, |
71 | 0 | nullptr, 0); |
72 | 0 | }); |
73 | | |
74 | | // No timer events seems strange, perhaps something is wrong - give |
75 | | // up on this and just return default params |
76 | 0 | if(timer.events() == 0) |
77 | 0 | return default_params(); |
78 | | |
79 | | // nsec per eval of scrypt with initial params |
80 | 0 | const uint64_t measured_time = timer.value() / timer.events(); |
81 | |
|
82 | 0 | const uint64_t target_nsec = msec.count() * static_cast<uint64_t>(1000000); |
83 | |
|
84 | 0 | uint64_t est_nsec = measured_time; |
85 | | |
86 | | // First move increase r by 8x if possible |
87 | |
|
88 | 0 | if(max_memory_usage == 0 || scrypt_memory_usage(N, r, p)*8 < max_memory_usage) |
89 | 0 | { |
90 | 0 | if(target_nsec / est_nsec >= 5) |
91 | 0 | { |
92 | 0 | r *= 8; |
93 | 0 | est_nsec *= 5; |
94 | 0 | } |
95 | 0 | } |
96 | | |
97 | | // Now double N as many times as we can |
98 | |
|
99 | 0 | while(max_memory_usage == 0 || scrypt_memory_usage(N, r, p)*2 < max_memory_usage) |
100 | 0 | { |
101 | 0 | if(target_nsec / est_nsec >= 2) |
102 | 0 | { |
103 | 0 | N *= 2; |
104 | 0 | est_nsec *= 2; |
105 | 0 | } |
106 | 0 | else |
107 | 0 | break; |
108 | 0 | } |
109 | | |
110 | | // If we have extra runtime budget, increment p |
111 | |
|
112 | 0 | if(target_nsec / est_nsec > 2) |
113 | 0 | p *= std::min<size_t>(1024, static_cast<size_t>(target_nsec / est_nsec)); |
114 | |
|
115 | 0 | return std::make_unique<Scrypt>(N, r, p); |
116 | 0 | } |
117 | | |
118 | | std::unique_ptr<PasswordHash> Scrypt_Family::from_params(size_t N, size_t r, size_t p) const |
119 | 1.14k | { |
120 | 1.14k | return std::make_unique<Scrypt>(N, r, p); |
121 | 1.14k | } |
122 | | |
123 | | std::unique_ptr<PasswordHash> Scrypt_Family::from_iterations(size_t iter) const |
124 | 0 | { |
125 | 0 | const size_t r = 8; |
126 | 0 | const size_t p = 1; |
127 | |
|
128 | 0 | size_t N = 8192; |
129 | |
|
130 | 0 | if(iter > 50000) |
131 | 0 | N = 16384; |
132 | 0 | if(iter > 100000) |
133 | 0 | N = 32768; |
134 | 0 | if(iter > 150000) |
135 | 0 | N = 65536; |
136 | |
|
137 | 0 | return std::make_unique<Scrypt>(N, r, p); |
138 | 0 | } |
139 | | |
140 | | Scrypt::Scrypt(size_t N, size_t r, size_t p) : |
141 | | m_N(N), m_r(r), m_p(p) |
142 | 1.14k | { |
143 | 1.14k | if(!is_power_of_2(N)) |
144 | 343 | throw Invalid_Argument("Scrypt N parameter must be a power of 2"); |
145 | | |
146 | 805 | if(p == 0 || p > 1024) |
147 | 192 | throw Invalid_Argument("Invalid or unsupported scrypt p"); |
148 | 613 | if(r == 0 || r > 256) |
149 | 121 | throw Invalid_Argument("Invalid or unsupported scrypt r"); |
150 | 492 | if(N < 1 || N > 4194304) |
151 | 0 | throw Invalid_Argument("Invalid or unsupported scrypt N"); |
152 | 492 | } |
153 | | |
154 | | std::string Scrypt::to_string() const |
155 | 0 | { |
156 | 0 | std::ostringstream out; |
157 | 0 | out << "Scrypt(" |
158 | 0 | << std::to_string(m_N) << "," |
159 | 0 | << std::to_string(m_r) << "," |
160 | 0 | << std::to_string(m_p) << ")"; |
161 | 0 | return out.str(); |
162 | 0 | } |
163 | | |
164 | | size_t Scrypt::total_memory_usage() const |
165 | 0 | { |
166 | 0 | const size_t N = memory_param(); |
167 | 0 | const size_t p = parallelism(); |
168 | 0 | const size_t r = iterations(); |
169 | |
|
170 | 0 | return scrypt_memory_usage(N, r, p); |
171 | 0 | } |
172 | | |
173 | | namespace { |
174 | | |
175 | | void scryptBlockMix(size_t r, uint8_t* B, uint8_t* Y) |
176 | 9.44k | { |
177 | 9.44k | uint32_t B32[16]; |
178 | 9.44k | secure_vector<uint8_t> X(64); |
179 | 9.44k | copy_mem(X.data(), &B[(2*r-1)*64], 64); |
180 | | |
181 | 97.5k | for(size_t i = 0; i != 2*r; i++) |
182 | 88.1k | { |
183 | 88.1k | xor_buf(X.data(), &B[64*i], 64); |
184 | 88.1k | load_le<uint32_t>(B32, X.data(), 16); |
185 | 88.1k | Salsa20::salsa_core(X.data(), B32, 8); |
186 | 88.1k | copy_mem(&Y[64*i], X.data(), 64); |
187 | 88.1k | } |
188 | | |
189 | 53.5k | for(size_t i = 0; i < r; ++i) |
190 | 44.0k | { |
191 | 44.0k | copy_mem(&B[i*64], &Y[(i * 2) * 64], 64); |
192 | 44.0k | } |
193 | | |
194 | 53.5k | for(size_t i = 0; i < r; ++i) |
195 | 44.0k | { |
196 | 44.0k | copy_mem(&B[(i + r) * 64], &Y[(i * 2 + 1) * 64], 64); |
197 | 44.0k | } |
198 | 9.44k | } |
199 | | |
200 | | void scryptROMmix(size_t r, size_t N, uint8_t* B, secure_vector<uint8_t>& V) |
201 | 1.27k | { |
202 | 1.27k | const size_t S = 128 * r; |
203 | | |
204 | 5.99k | for(size_t i = 0; i != N; ++i) |
205 | 4.72k | { |
206 | 4.72k | copy_mem(&V[S*i], B, S); |
207 | 4.72k | scryptBlockMix(r, B, &V[N*S]); |
208 | 4.72k | } |
209 | | |
210 | 5.99k | for(size_t i = 0; i != N; ++i) |
211 | 4.72k | { |
212 | | // compiler doesn't know here that N is power of 2 |
213 | 4.72k | const size_t j = load_le<uint32_t>(&B[(2*r-1)*64], 0) & (N - 1); |
214 | 4.72k | xor_buf(B, &V[j*S], S); |
215 | 4.72k | scryptBlockMix(r, B, &V[N*S]); |
216 | 4.72k | } |
217 | 1.27k | } |
218 | | |
219 | | } |
220 | | |
221 | | void Scrypt::derive_key(uint8_t output[], size_t output_len, |
222 | | const char* password, size_t password_len, |
223 | | const uint8_t salt[], size_t salt_len) const |
224 | 492 | { |
225 | 492 | const size_t N = memory_param(); |
226 | 492 | const size_t p = parallelism(); |
227 | 492 | const size_t r = iterations(); |
228 | | |
229 | 492 | const size_t S = 128 * r; |
230 | 492 | secure_vector<uint8_t> B(p * S); |
231 | | // temp space |
232 | 492 | secure_vector<uint8_t> V((N+1) * S); |
233 | | |
234 | 492 | auto hmac_sha256 = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); |
235 | | |
236 | 492 | try |
237 | 492 | { |
238 | 492 | hmac_sha256->set_key(cast_char_ptr_to_uint8(password), password_len); |
239 | 492 | } |
240 | 492 | catch(Invalid_Key_Length&) |
241 | 492 | { |
242 | 63 | throw Invalid_Argument("Scrypt cannot accept passphrases of the provided length"); |
243 | 63 | } |
244 | | |
245 | 429 | pbkdf2(*hmac_sha256, |
246 | 429 | B.data(), B.size(), |
247 | 429 | salt, salt_len, |
248 | 429 | 1); |
249 | | |
250 | | // these can be parallel |
251 | 1.70k | for(size_t i = 0; i != p; ++i) |
252 | 1.27k | { |
253 | 1.27k | scryptROMmix(r, N, &B[128*r*i], V); |
254 | 1.27k | } |
255 | | |
256 | 429 | pbkdf2(*hmac_sha256, |
257 | 429 | output, output_len, |
258 | 429 | B.data(), B.size(), |
259 | 429 | 1); |
260 | 429 | } |
261 | | |
262 | | } |