• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 18986954165

31 Oct 2025 10:44PM UTC coverage: 90.673% (+0.003%) from 90.67%
18986954165

Pull #5124

github

web-flow
Merge 978a2de3b into 72715a0cf
Pull Request #5124: Cooperative Cancellation in PasswordHash

100530 of 110871 relevant lines covered (90.67%)

12356863.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.7
/src/tests/test_pbkdf.cpp
1
/*
2
* (C) 2014,2015,2019 Jack Lloyd
3
* (C) 2018 Ribose Inc
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_PBKDF)
11
   #include <botan/pbkdf.h>
12
   #include <botan/pwdhash.h>
13
#endif
14

15
#if defined(BOTAN_HAS_RFC4880)
16
   #include <botan/rfc4880.h>
17
#endif
18

19
#if defined(BOTAN_HAS_THREAD_UTILS)
20
   #include <botan/internal/thread_pool.h>
21
   #include <stop_token>
22
#endif
23

24
namespace Botan_Tests {
25

26
namespace {
27

28
#if defined(BOTAN_HAS_PBKDF)
29
class PBKDF_KAT_Tests final : public Text_Based_Test {
×
30
   public:
31
      PBKDF_KAT_Tests() : Text_Based_Test("pbkdf", "Iterations,Salt,Passphrase,Output") {}
2✔
32

33
      Test::Result run_one_test(const std::string& pbkdf_name, const VarMap& vars) override {
28✔
34
         const size_t iterations = vars.get_req_sz("Iterations");
28✔
35
         const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
28✔
36
         const std::string passphrase = vars.get_req_str("Passphrase");
28✔
37
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
28✔
38
         const size_t outlen = expected.size();
28✔
39

40
         Test::Result result(pbkdf_name);
56✔
41
         auto pbkdf = Botan::PBKDF::create(pbkdf_name);
28✔
42

43
         if(!pbkdf) {
28✔
44
            result.note_missing(pbkdf_name);
×
45
            return result;
46
         }
47

48
         result.test_eq("Expected name", pbkdf->name(), pbkdf_name);
56✔
49

50
         const Botan::secure_vector<uint8_t> derived =
28✔
51
            pbkdf->derive_key(outlen, passphrase, salt.data(), salt.size(), iterations).bits_of();
28✔
52
         result.test_eq("derived key", derived, expected);
56✔
53

54
         auto pwdhash_fam = Botan::PasswordHashFamily::create(pbkdf_name);
28✔
55

56
         if(!pwdhash_fam) {
28✔
57
            result.note_missing("No PasswordHashFamily for " + pbkdf_name);
×
58
            return result;
×
59
         }
60

61
         auto pwdhash = pwdhash_fam->from_params(iterations);
28✔
62

63
         std::vector<uint8_t> pwdhash_derived(outlen);
28✔
64
         pwdhash->hash(pwdhash_derived, passphrase, salt);
56✔
65

66
         result.test_eq("pwdhash derived key", pwdhash_derived, expected);
56✔
67

68
         return result;
28✔
69
      }
196✔
70
};
71

72
BOTAN_REGISTER_SMOKE_TEST("pbkdf", "pbkdf_kat", PBKDF_KAT_Tests);
73

74
class Pwdhash_Tests : public Test {
×
75
   public:
76
      std::vector<Test::Result> run() override {
1✔
77
         std::vector<Test::Result> results;
1✔
78

79
         const std::vector<std::string> all_pwdhash = {
1✔
80
            "Scrypt", "PBKDF2(SHA-256)", "OpenPGP-S2K(SHA-384)", "Argon2d", "Argon2i", "Argon2id", "Bcrypt-PBKDF"};
1✔
81

82
         const auto run_time = std::chrono::milliseconds(3);
1✔
83
         const auto tune_time = std::chrono::milliseconds(1);
1✔
84
         const size_t max_mem = 32;
1✔
85

86
         for(const std::string& pwdhash : all_pwdhash) {
8✔
87
            Test::Result result("Pwdhash " + pwdhash);
7✔
88
            auto pwdhash_fam = Botan::PasswordHashFamily::create(pwdhash);
7✔
89

90
            if(pwdhash_fam) {
7✔
91
               result.start_timer();
7✔
92

93
               const std::vector<uint8_t> salt(8);
7✔
94
               const std::string password = "test";
7✔
95

96
               auto tuned_pwhash = pwdhash_fam->tune(32, run_time, max_mem, tune_time);
7✔
97

98
               std::vector<uint8_t> output1(32);
7✔
99
               tuned_pwhash->hash(output1, password, salt);
14✔
100

101
               std::unique_ptr<Botan::PasswordHash> pwhash;
7✔
102

103
               if(pwdhash_fam->name() == "Scrypt" || pwdhash_fam->name().starts_with("Argon2")) {
7✔
104
                  pwhash = pwdhash_fam->from_params(
4✔
105
                     tuned_pwhash->memory_param(), tuned_pwhash->iterations(), tuned_pwhash->parallelism());
8✔
106
               } else {
107
                  pwhash = pwdhash_fam->from_params(tuned_pwhash->iterations());
3✔
108
               }
109

110
               std::vector<uint8_t> output2(32);
7✔
111
               pwhash->hash(output2, password, salt);
14✔
112

113
               result.test_eq("PasswordHash produced same output when run with same params", output1, output2);
14✔
114

115
               auto default_pwhash = pwdhash_fam->default_params();
7✔
116
               std::vector<uint8_t> output3(32);
7✔
117
               default_pwhash->hash(output3, password, salt);
14✔
118

119
               result.end_timer();
7✔
120
            } else {
42✔
121
               result.test_note("No such algo " + pwdhash);
×
122
            }
123

124
            results.push_back(result);
7✔
125
         }
7✔
126

127
         return results;
1✔
128
      }
1✔
129
};
130

131
BOTAN_REGISTER_TEST("pbkdf", "pwdhash", Pwdhash_Tests);
132

133
   #if defined(BOTAN_HAS_THREAD_UTILS)
134
class Pwdhash_StopToken_Test final : public Test {
×
135
   public:
136
      std::vector<Test::Result> run() override {
1✔
137
         std::vector<Test::Result> results;
1✔
138

139
         const std::vector<std::string> all_pwdhash = {
1✔
140
            "Scrypt", "PBKDF2(SHA-256)", "Argon2d", "Argon2i", "Argon2id", "Bcrypt-PBKDF"};
1✔
141
         // Private thread pool to guarantee thread availability for cancellation.
142
         // We need just 1 thread as the cancellation tests are executed serially.
143
         Botan::Thread_Pool thread_pool(1);
1✔
144

145
         for(const std::string& algo_spec : all_pwdhash) {
7✔
146
            Test::Result result(algo_spec + " stop_token cancellation");
6✔
147

148
            auto fam = Botan::PasswordHashFamily::create(algo_spec);
6✔
149
            if(!fam) {
6✔
150
               result.test_note(algo_spec + " family unavailable");
×
151
               results.push_back(result);
×
152
               continue;
×
153
            }
154

155
            result.start_timer();
6✔
156

157
            // Test will be cancelled after 500ms below. If cancellation fails, test runs for 10s.
158
            const auto run_time = std::chrono::milliseconds(10000);
6✔
159
            const auto tune_time = std::chrono::milliseconds(100);
6✔
160
            const size_t max_mem = 32;
6✔
161
            auto pwdhash = fam->tune(32, run_time, max_mem, tune_time);
6✔
162

163
            const std::string password = "cancel-me";
12✔
164
            const std::vector<uint8_t> salt(16, 0xAA);
6✔
165
            std::vector<uint8_t> out(32);
6✔
166

167
            std::stop_source src;
6✔
168

169
            // Helper thread that will request cancellation
170
            auto future = thread_pool.run([&src]() {
18✔
171
               const auto stop_time = std::chrono::milliseconds(500);
6✔
172
               std::this_thread::sleep_for(stop_time);
6✔
173
               src.request_stop();  // fire the cancellation
6✔
174
            });
6✔
175

176
            // Run the derivation on the main thread and evaluate the result
177
            try {
6✔
178
               pwdhash->derive_key(
18✔
179
                  out.data(), out.size(), password.c_str(), password.size(), salt.data(), salt.size(), src.get_token());
6✔
180
               // If we reach this line, the stop token was ignored
181
               result.test_failure("Derivation completed without observing stop token");
×
182
            } catch(const Botan::Operation_Canceled& e) {
6✔
183
               // Expected – password hash saw the stop token and threw
184
               result.test_success("Cancellation raised Botan::Operation_Canceled: " + std::string(e.what()));
12✔
185
            } catch(const std::exception& e) {
6✔
186
               result.test_failure("Unexpected std::exception", e.what());
×
187
            } catch(...) {
×
188
               result.test_failure("Non-standard exception thrown on cancellation");
×
189
            }
×
190

191
            future.get();  // ensure the canceller thread finished
6✔
192

193
            result.end_timer();
6✔
194
            results.push_back(result);
6✔
195
         }
30✔
196
         return results;
1✔
197
      }
1✔
198
};
199

200
BOTAN_REGISTER_TEST("pbkdf", "pwdhash_stop_token", Pwdhash_StopToken_Test);
201
   #endif
202

203
#endif
204

205
#if defined(BOTAN_HAS_PBKDF_BCRYPT)
206

207
class Bcrypt_PBKDF_KAT_Tests final : public Text_Based_Test {
×
208
   public:
209
      Bcrypt_PBKDF_KAT_Tests() : Text_Based_Test("bcrypt_pbkdf.vec", "Passphrase,Salt,Iterations,Output") {}
2✔
210

211
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
36✔
212
         const size_t rounds = vars.get_req_sz("Iterations");
36✔
213
         const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
36✔
214
         const std::string passphrase = vars.get_req_str("Passphrase");
36✔
215
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
36✔
216

217
         Test::Result result("bcrypt PBKDF");
36✔
218

219
         auto pwdhash_fam = Botan::PasswordHashFamily::create("Bcrypt-PBKDF");
36✔
220

221
         if(!pwdhash_fam) {
36✔
222
            result.test_failure("Bcrypt-PBKDF is missing PasswordHashFamily");
×
223
            return result;
×
224
         }
225

226
         auto pwdhash = pwdhash_fam->from_iterations(rounds);
36✔
227

228
         std::vector<uint8_t> derived(expected.size());
36✔
229
         pwdhash->hash(derived, passphrase, salt);
72✔
230

231
         result.test_eq("derived key", derived, expected);
72✔
232

233
         return result;
36✔
234
      }
180✔
235
};
236

237
BOTAN_REGISTER_TEST("pbkdf", "bcrypt_pbkdf", Bcrypt_PBKDF_KAT_Tests);
238

239
#endif
240

241
#if defined(BOTAN_HAS_SCRYPT)
242

243
class Scrypt_KAT_Tests final : public Text_Based_Test {
×
244
   public:
245
      Scrypt_KAT_Tests() : Text_Based_Test("scrypt.vec", "Passphrase,Salt,N,R,P,Output") {}
2✔
246

247
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
15✔
248
         const size_t N = vars.get_req_sz("N");
15✔
249
         const size_t R = vars.get_req_sz("R");
15✔
250
         const size_t P = vars.get_req_sz("P");
15✔
251
         const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
15✔
252
         const std::string passphrase = vars.get_req_str("Passphrase");
15✔
253
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
15✔
254

255
         Test::Result result("scrypt");
15✔
256

257
         if(N >= 1048576 && Test::run_long_tests() == false) {
15✔
258
            return result;
259
         }
260

261
         auto pwdhash_fam = Botan::PasswordHashFamily::create("Scrypt");
15✔
262

263
         if(!pwdhash_fam) {
15✔
264
            result.test_failure("Scrypt is missing PasswordHashFamily");
×
265
            return result;
×
266
         }
267

268
         auto pwdhash = pwdhash_fam->from_params(N, R, P);
15✔
269

270
         std::vector<uint8_t> pwdhash_derived(expected.size());
15✔
271
         pwdhash->hash(pwdhash_derived, passphrase, salt);
30✔
272

273
         result.test_eq("pwdhash derived key", pwdhash_derived, expected);
30✔
274

275
         return result;
15✔
276
      }
75✔
277
};
278

279
BOTAN_REGISTER_TEST("pbkdf", "scrypt", Scrypt_KAT_Tests);
280

281
#endif
282

283
#if defined(BOTAN_HAS_ARGON2)
284

285
class Argon2_KAT_Tests final : public Text_Based_Test {
×
286
   public:
287
      Argon2_KAT_Tests() : Text_Based_Test("argon2.vec", "Passphrase,Salt,P,M,T,Output", "Secret,AD") {}
2✔
288

289
      Test::Result run_one_test(const std::string& mode, const VarMap& vars) override {
1,071✔
290
         const size_t P = vars.get_req_sz("P");
1,071✔
291
         const size_t M = vars.get_req_sz("M");
1,071✔
292
         const size_t T = vars.get_req_sz("T");
1,071✔
293
         const std::vector<uint8_t> key = vars.get_opt_bin("Secret");
1,071✔
294
         const std::vector<uint8_t> ad = vars.get_opt_bin("AD");
1,071✔
295
         const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
1,071✔
296
         const std::vector<uint8_t> passphrase = vars.get_req_bin("Passphrase");
1,071✔
297
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
1,071✔
298

299
         Test::Result result(mode);
2,142✔
300

301
         auto pwdhash_fam = Botan::PasswordHashFamily::create(mode);
1,071✔
302

303
         if(!pwdhash_fam) {
1,071✔
304
            result.test_failure("Argon2 is missing PasswordHashFamily");
×
305
            return result;
×
306
         }
307

308
         auto pwdhash = pwdhash_fam->from_params(M, T, P);
1,071✔
309

310
         const std::string passphrase_str(passphrase.begin(), passphrase.end());
2,142✔
311

312
         std::vector<uint8_t> pwdhash_derived(expected.size());
1,071✔
313
         pwdhash->hash(pwdhash_derived, passphrase_str, salt, ad, key);
2,142✔
314

315
         result.test_eq("pwdhash derived key", pwdhash_derived, expected);
2,142✔
316

317
         return result;
1,071✔
318
      }
6,654✔
319
};
320

321
BOTAN_REGISTER_SERIALIZED_TEST("pbkdf", "argon2", Argon2_KAT_Tests);
322

323
#endif
324

325
#if defined(BOTAN_HAS_PGP_S2K)
326

327
class PGP_S2K_Iter_Test final : public Test {
×
328
   public:
329
      std::vector<Test::Result> run() override {
1✔
330
         Test::Result result("PGP_S2K iteration encoding");
1✔
331

332
         // The maximum representable iteration count
333
         const size_t max_iter = 65011712;
1✔
334

335
         result.test_eq("Encoding of large value accepted", Botan::RFC4880_encode_count(max_iter * 2), size_t(255));
1✔
336
         result.test_eq("Encoding of small value accepted", Botan::RFC4880_encode_count(0), size_t(0));
1✔
337

338
         for(size_t c = 0; c != 256; ++c) {
257✔
339
            const size_t dec = Botan::RFC4880_decode_count(static_cast<uint8_t>(c));
256✔
340
            const size_t comp_dec = (16 + (c & 0x0F)) << ((c >> 4) + 6);
256✔
341
            result.test_eq("Decoded value matches PGP formula", dec, comp_dec);
256✔
342

343
            const size_t enc = Botan::RFC4880_encode_count(comp_dec);
256✔
344
            result.test_eq("Encoded value matches PGP formula", enc, c);
512✔
345
         }
346

347
         uint8_t last_enc = 0;
348

349
         for(size_t i = 0; i <= max_iter; i += 64) {
1,015,810✔
350
            const uint8_t enc = Botan::RFC4880_encode_count(i);
1,015,809✔
351
            result.test_lte("Encoded value non-decreasing", last_enc, enc);
1,015,809✔
352

353
            /*
354
            The iteration count as encoded may not be exactly the
355
            value requested, but should never be less
356
            */
357
            const size_t dec = Botan::RFC4880_decode_count(enc);
1,015,809✔
358
            result.test_gte("Decoded value is >= requested", dec, i);
1,015,809✔
359

360
            last_enc = enc;
1,015,809✔
361
         }
362

363
         return std::vector<Test::Result>{result};
3✔
364
      }
2✔
365
};
366

367
BOTAN_REGISTER_TEST("pbkdf", "pgp_s2k_iter", PGP_S2K_Iter_Test);
368

369
#endif
370

371
}  // namespace
372

373
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc