/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/time_utils.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 | | // This is zero if max_memory_usage_mb == 0 (unbounded) |
55 | 0 | const size_t max_memory_usage = max_memory_usage_mb * 1024 * 1024; |
56 | | |
57 | | // Starting parameters |
58 | 0 | size_t N = 8 * 1024; |
59 | 0 | size_t r = 1; |
60 | 0 | size_t p = 1; |
61 | |
|
62 | 0 | auto pwdhash = this->from_params(N, r, p); |
63 | |
|
64 | 0 | const uint64_t measured_time = measure_cost(tune_time, [&]() { |
65 | 0 | uint8_t output[32] = {0}; |
66 | 0 | pwdhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); |
67 | 0 | }); |
68 | |
|
69 | 0 | const uint64_t target_nsec = msec.count() * static_cast<uint64_t>(1000000); |
70 | |
|
71 | 0 | uint64_t est_nsec = measured_time; |
72 | | |
73 | | // In below code we invoke scrypt_memory_usage with p == 0 as p contributes |
74 | | // (very slightly) to memory consumption, but N is the driving factor. |
75 | | // Including p leads to using an N half as large as what the user would expect. |
76 | | |
77 | | // First increase r by 8x if possible |
78 | 0 | if(max_memory_usage == 0 || scrypt_memory_usage(N, r * 8, 0) <= max_memory_usage) { |
79 | 0 | if(target_nsec / est_nsec >= 5) { |
80 | 0 | r *= 8; |
81 | 0 | est_nsec *= 5; |
82 | 0 | } |
83 | 0 | } |
84 | | |
85 | | // Now double N as many times as we can |
86 | 0 | while(max_memory_usage == 0 || scrypt_memory_usage(N * 2, r, 0) <= max_memory_usage) { |
87 | 0 | if(target_nsec / est_nsec >= 2) { |
88 | 0 | N *= 2; |
89 | 0 | est_nsec *= 2; |
90 | 0 | } else { |
91 | 0 | break; |
92 | 0 | } |
93 | 0 | } |
94 | | |
95 | | // If we have extra runtime budget, increment p |
96 | 0 | if(target_nsec / est_nsec >= 2) { |
97 | 0 | p *= std::min<size_t>(1024, static_cast<size_t>(target_nsec / est_nsec)); |
98 | 0 | } |
99 | |
|
100 | 0 | return std::make_unique<Scrypt>(N, r, p); |
101 | 0 | } |
102 | | |
103 | 0 | std::unique_ptr<PasswordHash> Scrypt_Family::from_params(size_t N, size_t r, size_t p) const { |
104 | 0 | return std::make_unique<Scrypt>(N, r, p); |
105 | 0 | } |
106 | | |
107 | 0 | std::unique_ptr<PasswordHash> Scrypt_Family::from_iterations(size_t iter) const { |
108 | 0 | const size_t r = 8; |
109 | 0 | const size_t p = 1; |
110 | |
|
111 | 0 | size_t N = 8192; |
112 | |
|
113 | 0 | if(iter > 50000) { |
114 | 0 | N = 16384; |
115 | 0 | } |
116 | 0 | if(iter > 100000) { |
117 | 0 | N = 32768; |
118 | 0 | } |
119 | 0 | if(iter > 150000) { |
120 | 0 | N = 65536; |
121 | 0 | } |
122 | |
|
123 | 0 | return std::make_unique<Scrypt>(N, r, p); |
124 | 0 | } |
125 | | |
126 | 0 | Scrypt::Scrypt(size_t N, size_t r, size_t p) : m_N(N), m_r(r), m_p(p) { |
127 | 0 | if(!is_power_of_2(N)) { |
128 | 0 | throw Invalid_Argument("Scrypt N parameter must be a power of 2"); |
129 | 0 | } |
130 | | |
131 | 0 | if(p == 0 || p > 1024) { |
132 | 0 | throw Invalid_Argument("Invalid or unsupported scrypt p"); |
133 | 0 | } |
134 | 0 | if(r == 0 || r > 256) { |
135 | 0 | throw Invalid_Argument("Invalid or unsupported scrypt r"); |
136 | 0 | } |
137 | 0 | if(N < 1 || N > 4194304) { |
138 | 0 | throw Invalid_Argument("Invalid or unsupported scrypt N"); |
139 | 0 | } |
140 | 0 | } |
141 | | |
142 | 0 | std::string Scrypt::to_string() const { |
143 | 0 | return fmt("Scrypt({},{},{})", m_N, m_r, m_p); |
144 | 0 | } |
145 | | |
146 | 0 | size_t Scrypt::total_memory_usage() const { |
147 | 0 | const size_t N = memory_param(); |
148 | 0 | const size_t p = parallelism(); |
149 | 0 | const size_t r = iterations(); |
150 | |
|
151 | 0 | return scrypt_memory_usage(N, r, p); |
152 | 0 | } |
153 | | |
154 | | namespace { |
155 | | |
156 | 0 | void scryptBlockMix(size_t r, uint8_t* B, uint8_t* Y) { |
157 | 0 | uint32_t B32[16]; |
158 | 0 | secure_vector<uint8_t> X(64); |
159 | 0 | copy_mem(X.data(), &B[(2 * r - 1) * 64], 64); |
160 | |
|
161 | 0 | for(size_t i = 0; i != 2 * r; i++) { |
162 | 0 | xor_buf(X.data(), &B[64 * i], 64); |
163 | 0 | load_le<uint32_t>(B32, X.data(), 16); |
164 | 0 | Salsa20::salsa_core(X.data(), B32, 8); |
165 | 0 | copy_mem(&Y[64 * i], X.data(), 64); |
166 | 0 | } |
167 | |
|
168 | 0 | for(size_t i = 0; i < r; ++i) { |
169 | 0 | copy_mem(&B[i * 64], &Y[(i * 2) * 64], 64); |
170 | 0 | } |
171 | |
|
172 | 0 | for(size_t i = 0; i < r; ++i) { |
173 | 0 | copy_mem(&B[(i + r) * 64], &Y[(i * 2 + 1) * 64], 64); |
174 | 0 | } |
175 | 0 | } |
176 | | |
177 | 0 | void scryptROMmix(size_t r, size_t N, uint8_t* B, secure_vector<uint8_t>& V) { |
178 | 0 | const size_t S = 128 * r; |
179 | |
|
180 | 0 | for(size_t i = 0; i != N; ++i) { |
181 | 0 | copy_mem(&V[S * i], B, S); |
182 | 0 | scryptBlockMix(r, B, &V[N * S]); |
183 | 0 | } |
184 | |
|
185 | 0 | for(size_t i = 0; i != N; ++i) { |
186 | | // compiler doesn't know here that N is power of 2 |
187 | 0 | const size_t j = load_le<uint32_t>(&B[(2 * r - 1) * 64], 0) & (N - 1); |
188 | 0 | xor_buf(B, &V[j * S], S); |
189 | 0 | scryptBlockMix(r, B, &V[N * S]); |
190 | 0 | } |
191 | 0 | } |
192 | | |
193 | | } // namespace |
194 | | |
195 | | void Scrypt::derive_key(uint8_t output[], |
196 | | size_t output_len, |
197 | | const char* password, |
198 | | size_t password_len, |
199 | | const uint8_t salt[], |
200 | 0 | size_t salt_len) const { |
201 | 0 | const size_t N = memory_param(); |
202 | 0 | const size_t p = parallelism(); |
203 | 0 | const size_t r = iterations(); |
204 | |
|
205 | 0 | const size_t S = 128 * r; |
206 | 0 | secure_vector<uint8_t> B(p * S); |
207 | | // temp space |
208 | 0 | secure_vector<uint8_t> V((N + 1) * S); |
209 | |
|
210 | 0 | auto hmac_sha256 = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); |
211 | |
|
212 | 0 | try { |
213 | 0 | hmac_sha256->set_key(cast_char_ptr_to_uint8(password), password_len); |
214 | 0 | } catch(Invalid_Key_Length&) { |
215 | 0 | throw Invalid_Argument("Scrypt cannot accept passphrases of the provided length"); |
216 | 0 | } |
217 | | |
218 | 0 | pbkdf2(*hmac_sha256, B.data(), B.size(), salt, salt_len, 1); |
219 | | |
220 | | // these can be parallel |
221 | 0 | for(size_t i = 0; i != p; ++i) { |
222 | 0 | scryptROMmix(r, N, &B[128 * r * i], V); |
223 | 0 | } |
224 | |
|
225 | 0 | pbkdf2(*hmac_sha256, output, output_len, B.data(), B.size(), 1); |
226 | 0 | } |
227 | | |
228 | | } // namespace Botan |