/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 | | |
10 | | #include <botan/exceptn.h> |
11 | | #include <botan/pbkdf2.h> |
12 | | #include <botan/internal/bit_ops.h> |
13 | | #include <botan/internal/fmt.h> |
14 | | #include <botan/internal/loadstor.h> |
15 | | #include <botan/internal/salsa20.h> |
16 | | #include <botan/internal/timer.h> |
17 | | |
18 | | namespace Botan { |
19 | | |
20 | | namespace { |
21 | | |
22 | 0 | size_t scrypt_memory_usage(size_t N, size_t r, size_t p) { |
23 | 0 | return 128 * r * (N + p); |
24 | 0 | } |
25 | | |
26 | | } // namespace |
27 | | |
28 | 0 | std::string Scrypt_Family::name() const { |
29 | 0 | return "Scrypt"; |
30 | 0 | } |
31 | | |
32 | 0 | std::unique_ptr<PasswordHash> Scrypt_Family::default_params() const { |
33 | 0 | return std::make_unique<Scrypt>(32768, 8, 1); |
34 | 0 | } |
35 | | |
36 | | std::unique_ptr<PasswordHash> Scrypt_Family::tune(size_t output_length, |
37 | | std::chrono::milliseconds msec, |
38 | | size_t max_memory_usage_mb, |
39 | 0 | std::chrono::milliseconds tune_time) const { |
40 | 0 | BOTAN_UNUSED(output_length); |
41 | | |
42 | | /* |
43 | | * Some rough relations between scrypt parameters and runtime. |
44 | | * Denote here by stime(N,r,p) the msec it takes to run scrypt. |
45 | | * |
46 | | * Emperically for smaller sizes: |
47 | | * stime(N,8*r,p) / stime(N,r,p) is ~ 6-7 |
48 | | * stime(N,r,8*p) / stime(N,r,8*p) is ~ 7 |
49 | | * stime(2*N,r,p) / stime(N,r,p) is ~ 2 |
50 | | * |
51 | | * Compute stime(8192,1,1) as baseline and extrapolate |
52 | | */ |
53 | | |
54 | | // We include a bit of slop here (the + 512) to handle the fact that scrypt's |
55 | | // memory consumption is modified by all three parameters, and otherwise we |
56 | | // stop before hitting the desired target. |
57 | |
|
58 | 0 | const size_t max_memory_usage = max_memory_usage_mb * 1024 * 1024 + 512; |
59 | | // Starting parameters |
60 | 0 | size_t N = 8 * 1024; |
61 | 0 | size_t r = 1; |
62 | 0 | size_t p = 1; |
63 | |
|
64 | 0 | Timer timer("Scrypt"); |
65 | |
|
66 | 0 | auto pwdhash = this->from_params(N, r, p); |
67 | |
|
68 | 0 | timer.run_until_elapsed(tune_time, [&]() { |
69 | 0 | uint8_t output[32] = {0}; |
70 | 0 | pwdhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); |
71 | 0 | }); |
72 | | |
73 | | // No timer events seems strange, perhaps something is wrong - give |
74 | | // up on this and just return default params |
75 | 0 | if(timer.events() == 0) { |
76 | 0 | return default_params(); |
77 | 0 | } |
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 * 8, p) <= max_memory_usage) { |
89 | 0 | if(target_nsec / est_nsec >= 5) { |
90 | 0 | r *= 8; |
91 | 0 | est_nsec *= 5; |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | // Now double N as many times as we can |
96 | |
|
97 | 0 | while(max_memory_usage == 0 || scrypt_memory_usage(N * 2, r, p) <= max_memory_usage) { |
98 | 0 | if(target_nsec / est_nsec >= 2) { |
99 | 0 | N *= 2; |
100 | 0 | est_nsec *= 2; |
101 | 0 | } else { |
102 | 0 | break; |
103 | 0 | } |
104 | 0 | } |
105 | | |
106 | | // If we have extra runtime budget, increment p |
107 | |
|
108 | 0 | if(target_nsec / est_nsec > 2) { |
109 | 0 | p *= std::min<size_t>(1024, static_cast<size_t>(target_nsec / est_nsec)); |
110 | 0 | } |
111 | |
|
112 | 0 | return std::make_unique<Scrypt>(N, r, p); |
113 | 0 | } |
114 | | |
115 | 510 | std::unique_ptr<PasswordHash> Scrypt_Family::from_params(size_t N, size_t r, size_t p) const { |
116 | 510 | return std::make_unique<Scrypt>(N, r, p); |
117 | 510 | } |
118 | | |
119 | 0 | std::unique_ptr<PasswordHash> Scrypt_Family::from_iterations(size_t iter) const { |
120 | 0 | const size_t r = 8; |
121 | 0 | const size_t p = 1; |
122 | |
|
123 | 0 | size_t N = 8192; |
124 | |
|
125 | 0 | if(iter > 50000) { |
126 | 0 | N = 16384; |
127 | 0 | } |
128 | 0 | if(iter > 100000) { |
129 | 0 | N = 32768; |
130 | 0 | } |
131 | 0 | if(iter > 150000) { |
132 | 0 | N = 65536; |
133 | 0 | } |
134 | |
|
135 | 0 | return std::make_unique<Scrypt>(N, r, p); |
136 | 0 | } |
137 | | |
138 | 510 | Scrypt::Scrypt(size_t N, size_t r, size_t p) : m_N(N), m_r(r), m_p(p) { |
139 | 510 | if(!is_power_of_2(N)) { |
140 | 158 | throw Invalid_Argument("Scrypt N parameter must be a power of 2"); |
141 | 158 | } |
142 | | |
143 | 352 | if(p == 0 || p > 1024) { |
144 | 66 | throw Invalid_Argument("Invalid or unsupported scrypt p"); |
145 | 66 | } |
146 | 286 | if(r == 0 || r > 256) { |
147 | 38 | throw Invalid_Argument("Invalid or unsupported scrypt r"); |
148 | 38 | } |
149 | 248 | if(N < 1 || N > 4194304) { |
150 | 0 | throw Invalid_Argument("Invalid or unsupported scrypt N"); |
151 | 0 | } |
152 | 248 | } |
153 | | |
154 | 0 | std::string Scrypt::to_string() const { |
155 | 0 | return fmt("Scrypt({},{},{})", m_N, m_r, m_p); |
156 | 0 | } |
157 | | |
158 | 0 | size_t Scrypt::total_memory_usage() const { |
159 | 0 | const size_t N = memory_param(); |
160 | 0 | const size_t p = parallelism(); |
161 | 0 | const size_t r = iterations(); |
162 | |
|
163 | 0 | return scrypt_memory_usage(N, r, p); |
164 | 0 | } |
165 | | |
166 | | namespace { |
167 | | |
168 | 4.68k | void scryptBlockMix(size_t r, uint8_t* B, uint8_t* Y) { |
169 | 4.68k | uint32_t B32[16]; |
170 | 4.68k | secure_vector<uint8_t> X(64); |
171 | 4.68k | copy_mem(X.data(), &B[(2 * r - 1) * 64], 64); |
172 | | |
173 | 47.5k | for(size_t i = 0; i != 2 * r; i++) { |
174 | 42.8k | xor_buf(X.data(), &B[64 * i], 64); |
175 | 42.8k | load_le<uint32_t>(B32, X.data(), 16); |
176 | 42.8k | Salsa20::salsa_core(X.data(), B32, 8); |
177 | 42.8k | copy_mem(&Y[64 * i], X.data(), 64); |
178 | 42.8k | } |
179 | | |
180 | 26.1k | for(size_t i = 0; i < r; ++i) { |
181 | 21.4k | copy_mem(&B[i * 64], &Y[(i * 2) * 64], 64); |
182 | 21.4k | } |
183 | | |
184 | 26.1k | for(size_t i = 0; i < r; ++i) { |
185 | 21.4k | copy_mem(&B[(i + r) * 64], &Y[(i * 2 + 1) * 64], 64); |
186 | 21.4k | } |
187 | 4.68k | } |
188 | | |
189 | 659 | void scryptROMmix(size_t r, size_t N, uint8_t* B, secure_vector<uint8_t>& V) { |
190 | 659 | const size_t S = 128 * r; |
191 | | |
192 | 2.99k | for(size_t i = 0; i != N; ++i) { |
193 | 2.34k | copy_mem(&V[S * i], B, S); |
194 | 2.34k | scryptBlockMix(r, B, &V[N * S]); |
195 | 2.34k | } |
196 | | |
197 | 2.99k | for(size_t i = 0; i != N; ++i) { |
198 | | // compiler doesn't know here that N is power of 2 |
199 | 2.34k | const size_t j = load_le<uint32_t>(&B[(2 * r - 1) * 64], 0) & (N - 1); |
200 | 2.34k | xor_buf(B, &V[j * S], S); |
201 | 2.34k | scryptBlockMix(r, B, &V[N * S]); |
202 | 2.34k | } |
203 | 659 | } |
204 | | |
205 | | } // namespace |
206 | | |
207 | | void Scrypt::derive_key(uint8_t output[], |
208 | | size_t output_len, |
209 | | const char* password, |
210 | | size_t password_len, |
211 | | const uint8_t salt[], |
212 | 248 | size_t salt_len) const { |
213 | 248 | const size_t N = memory_param(); |
214 | 248 | const size_t p = parallelism(); |
215 | 248 | const size_t r = iterations(); |
216 | | |
217 | 248 | const size_t S = 128 * r; |
218 | 248 | secure_vector<uint8_t> B(p * S); |
219 | | // temp space |
220 | 248 | secure_vector<uint8_t> V((N + 1) * S); |
221 | | |
222 | 248 | auto hmac_sha256 = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); |
223 | | |
224 | 248 | try { |
225 | 248 | hmac_sha256->set_key(cast_char_ptr_to_uint8(password), password_len); |
226 | 248 | } catch(Invalid_Key_Length&) { |
227 | 38 | throw Invalid_Argument("Scrypt cannot accept passphrases of the provided length"); |
228 | 38 | } |
229 | | |
230 | 210 | pbkdf2(*hmac_sha256, B.data(), B.size(), salt, salt_len, 1); |
231 | | |
232 | | // these can be parallel |
233 | 869 | for(size_t i = 0; i != p; ++i) { |
234 | 659 | scryptROMmix(r, N, &B[128 * r * i], V); |
235 | 659 | } |
236 | | |
237 | 210 | pbkdf2(*hmac_sha256, output, output_len, B.data(), B.size(), 1); |
238 | 210 | } |
239 | | |
240 | | } // namespace Botan |