Botan 3.13.0
Crypto and TLS for C&
scrypt.cpp
Go to the documentation of this file.
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/int_utils.h>
15#include <botan/internal/loadstor.h>
16#include <botan/internal/mem_utils.h>
17#include <botan/internal/salsa20.h>
18#include <botan/internal/time_utils.h>
19#include <array>
20
21namespace Botan {
22
23namespace {
24
25constexpr size_t MAX_SCRYPT_N = 4194304;
26constexpr size_t MAX_SCRYPT_MEMORY_GB = sizeof(size_t) == 4 ? 2 : 8;
27constexpr size_t MAX_SCRYPT_MEMORY_BYTES = MAX_SCRYPT_MEMORY_GB * 1024 * 1024 * 1024 + 2 * 1024 * 1024;
28
29std::optional<size_t> scrypt_memory_usage(size_t N, size_t r, size_t p) {
30 // 128 * r * (N + p) rejecting on overflow
31 const auto block_size = checked_mul(static_cast<size_t>(128), r);
32 const auto blocks = checked_add(N, p);
33 if(block_size && blocks) {
34 return checked_mul(block_size.value(), blocks.value());
35 } else {
36 return {};
37 }
38}
39
40} // namespace
41
42std::string Scrypt_Family::name() const {
43 return "Scrypt";
44}
45
46std::unique_ptr<PasswordHash> Scrypt_Family::default_params() const {
47 return std::make_unique<Scrypt>(32768, 8, 1);
48}
49
50std::unique_ptr<PasswordHash> Scrypt_Family::tune_params(size_t /*output_length*/,
51 uint64_t desired_msec,
52 std::optional<size_t> max_memory,
53 uint64_t tuning_msec) const {
54 /*
55 * Some rough relations between scrypt parameters and runtime.
56 * Denote here by stime(N,r,p) the msec it takes to run scrypt.
57 *
58 * Empirically for smaller sizes:
59 * stime(N,8*r,p) / stime(N,r,p) is ~ 6-7
60 * stime(N,r,8*p) / stime(N,r,p) is ~ 7
61 * stime(2*N,r,p) / stime(N,r,p) is ~ 2
62 *
63 * Compute stime(8192,1,1) as baseline and extrapolate
64 */
65
66 // If max_memory is nullopt or zero this becomes zero and is ignored
67 const size_t max_memory_bytes = std::min(MAX_SCRYPT_MEMORY_BYTES, max_memory.value_or(0) * 1024 * 1024);
68
69 // In below code we invoke scrypt_memory_usage with p == 0 as p contributes
70 // (very slightly) to memory consumption, but N is the driving factor.
71 // Including p leads to using an N half as large as what the user would expect.
72
73 auto scrypt_parameters_acceptable = [&](size_t N, size_t r) -> bool {
74 if(N > MAX_SCRYPT_N) {
75 return false;
76 }
77 if(const auto consumed = scrypt_memory_usage(N, r, 0)) {
78 if(max_memory_bytes > 0 && *consumed > max_memory_bytes) {
79 return false;
80 } else {
81 return true;
82 }
83 } else {
84 return false;
85 }
86 };
87
88 // Starting parameters
89 size_t N = 8 * 1024;
90 size_t r = 1;
91 size_t p = 1;
92
93 auto pwdhash = this->from_params(N, r, p);
94
95 const uint64_t measured_time = measure_cost(tuning_msec, [&]() {
96 uint8_t output[32] = {0};
97 pwdhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0);
98 });
99
100 const uint64_t target_nsec = desired_msec * static_cast<uint64_t>(1000000);
101
102 uint64_t est_nsec = measured_time;
103
104 // First increase r by 8x if possible
105 if(scrypt_parameters_acceptable(N, r * 8)) {
106 if(target_nsec / est_nsec >= 5) {
107 r *= 8;
108 est_nsec *= 5;
109 }
110 }
111
112 // Now double N as many times as we can
113 while(scrypt_parameters_acceptable(N * 2, r)) {
114 if(target_nsec / est_nsec >= 2) {
115 N *= 2;
116 est_nsec *= 2;
117 } else {
118 break;
119 }
120 }
121
122 // If we have extra runtime budget, increment p
123 if(target_nsec / est_nsec >= 2) {
124 p *= std::min<size_t>(1024, static_cast<size_t>(target_nsec / est_nsec));
125 }
126
127 return std::make_unique<Scrypt>(N, r, p);
128}
129
130std::unique_ptr<PasswordHash> Scrypt_Family::from_params(size_t N, size_t r, size_t p) const {
131 return std::make_unique<Scrypt>(N, r, p);
132}
133
134std::unique_ptr<PasswordHash> Scrypt_Family::from_iterations(size_t iter) const {
135 const size_t r = 8;
136 const size_t p = 1;
137
138 size_t N = 8192;
139
140 if(iter > 50000) {
141 N = 16384;
142 }
143 if(iter > 100000) {
144 N = 32768;
145 }
146 if(iter > 150000) {
147 N = 65536;
148 }
149
150 return std::make_unique<Scrypt>(N, r, p);
151}
152
153Scrypt::Scrypt(size_t N, size_t r, size_t p) : m_N(N), m_r(r), m_p(p) {
154 if(!is_power_of_2(N)) {
155 throw Invalid_Argument("Scrypt N parameter must be a power of 2");
156 }
157
158 if(p == 0 || p > 1024) {
159 throw Invalid_Argument("Invalid or unsupported scrypt p");
160 }
161 if(r == 0 || r > 256) {
162 throw Invalid_Argument("Invalid or unsupported scrypt r");
163 }
164 if(N < 1 || N > MAX_SCRYPT_N) {
165 throw Invalid_Argument("Invalid or unsupported scrypt N");
166 }
167
168 if(const auto memory_usage = scrypt_memory_usage(N, r, p)) {
169 if(memory_usage > MAX_SCRYPT_MEMORY_BYTES) {
170 throw Invalid_Argument("Scrypt parameters exceed maximum allowed memory limit");
171 }
172 } else {
173 throw Invalid_Argument("Scrypt parameters are too large for this platform");
174 }
175}
176
177std::string Scrypt::to_string() const {
178 return fmt("Scrypt({},{},{})", m_N, m_r, m_p);
179}
180
182 const size_t N = memory_param();
183 const size_t p = parallelism();
184 const size_t r = iterations();
185
186 const auto consumption = scrypt_memory_usage(N, r, p);
187 BOTAN_ASSERT_NOMSG(consumption.has_value());
188 return consumption.value();
189}
190
191namespace {
192
193void scryptBlockMix(size_t r, uint8_t* B, uint8_t* Y) {
194 uint32_t B32[16];
195 std::array<uint8_t, 64> X{};
196 copy_mem(X.data(), &B[(2 * r - 1) * 64], 64);
197
198 for(size_t i = 0; i != 2 * r; i++) {
199 xor_buf(X.data(), &B[64 * i], 64);
200 load_le<uint32_t>(B32, X.data(), 16);
201 Salsa20::salsa_core(X.data(), B32, 8);
202 copy_mem(&Y[64 * i], X.data(), 64);
203 }
204
205 for(size_t i = 0; i < r; ++i) {
206 copy_mem(&B[i * 64], &Y[(i * 2) * 64], 64);
207 }
208
209 for(size_t i = 0; i < r; ++i) {
210 copy_mem(&B[(i + r) * 64], &Y[(i * 2 + 1) * 64], 64);
211 }
212}
213
214void scryptROMmix(size_t r, size_t N, uint8_t* B, secure_vector<uint8_t>& V) {
215 const size_t S = 128 * r;
216
217 for(size_t i = 0; i != N; ++i) {
218 copy_mem(&V[S * i], B, S);
219 scryptBlockMix(r, B, &V[N * S]);
220 }
221
222 for(size_t i = 0; i != N; ++i) {
223 // compiler doesn't know here that N is power of 2
224 const size_t j = load_le<uint32_t>(&B[(2 * r - 1) * 64], 0) & (N - 1);
225 xor_buf(B, &V[j * S], S);
226 scryptBlockMix(r, B, &V[N * S]);
227 }
228}
229
230} // namespace
231
232void Scrypt::derive_key(uint8_t output[],
233 size_t output_len,
234 const char* password,
235 size_t password_len,
236 const uint8_t salt[],
237 size_t salt_len) const {
238 if(output_len == 0) {
239 return;
240 }
241
242 const size_t N = memory_param();
243 const size_t p = parallelism();
244 const size_t r = iterations();
245
246 const size_t S = mul_or_throw(size_t(128), r, "Scrypt S size overflow");
247 secure_vector<uint8_t> B(mul_or_throw(p, S, "Scrypt B size overflow"));
248 // temp space
249 secure_vector<uint8_t> V(mul_or_throw(N + 1, S, "Scrypt V size overflow"));
250
251 auto hmac_sha256 = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)");
252
253 try {
254 hmac_sha256->set_key(as_span_of_bytes(password, password_len));
255 } catch(Invalid_Key_Length&) {
256 throw Invalid_Argument("Scrypt cannot accept passphrases of the provided length");
257 }
258
259 pbkdf2(*hmac_sha256, B.data(), B.size(), salt, salt_len, 1);
260
261 // these can be parallel
262 for(size_t i = 0; i != p; ++i) {
263 scryptROMmix(r, N, &B[128 * r * i], V);
264 }
265
266 pbkdf2(*hmac_sha256, output, output_len, B.data(), B.size(), 1);
267}
268
269} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
static std::unique_ptr< MessageAuthenticationCode > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition mac.cpp:149
static void salsa_core(uint8_t output[64], const uint32_t input[16], size_t rounds)
Definition salsa20.cpp:79
std::unique_ptr< PasswordHash > from_params(size_t N, size_t r, size_t p) const override
Definition scrypt.cpp:130
std::unique_ptr< PasswordHash > tune_params(size_t output_len, uint64_t desired_runtime_msec, std::optional< size_t > max_memory, uint64_t tune_msec) const override
Definition scrypt.cpp:50
std::unique_ptr< PasswordHash > from_iterations(size_t iter) const override
Definition scrypt.cpp:134
std::string name() const override
Definition scrypt.cpp:42
std::unique_ptr< PasswordHash > default_params() const override
Definition scrypt.cpp:46
std::string to_string() const override
Definition scrypt.cpp:177
size_t memory_param() const override
Definition scrypt.h:41
size_t iterations() const override
Definition scrypt.h:37
size_t parallelism() const override
Definition scrypt.h:39
size_t total_memory_usage() const override
Definition scrypt.cpp:181
Scrypt(size_t N, size_t r, size_t p)
Definition scrypt.cpp:153
void derive_key(uint8_t out[], size_t out_len, const char *password, size_t password_len, const uint8_t salt[], size_t salt_len) const override
Definition scrypt.cpp:232
BOTAN_FORCE_INLINE constexpr bool is_power_of_2(T arg)
Definition bit_ops.h:62
constexpr std::optional< T > checked_add(T a, T b)
Definition int_utils.h:19
constexpr T mul_or_throw(T a, T b, std::string_view msg)
Definition int_utils.h:81
std::span< const uint8_t > as_span_of_bytes(const char *s, size_t len)
Definition mem_utils.h:59
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
constexpr std::optional< T > checked_mul(T a, T b)
Definition int_utils.h:46
constexpr auto load_le(ParamTs &&... params)
Definition loadstor.h:495
constexpr void xor_buf(ranges::contiguous_output_range< uint8_t > auto &&out, ranges::contiguous_range< uint8_t > auto &&in)
Definition mem_ops.h:403
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128
size_t pbkdf2(MessageAuthenticationCode &prf, uint8_t out[], size_t out_len, std::string_view password, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec)
Definition pbkdf2.cpp:73
uint64_t measure_cost(uint64_t trial_msec, F func)
Definition time_utils.h:19