Coverage Report

Created: 2026-02-07 07:14

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