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

randombit / botan / 18978562474

31 Oct 2025 04:18PM UTC coverage: 90.672% (+0.003%) from 90.669%
18978562474

Pull #5124

github

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

100535 of 110878 relevant lines covered (90.67%)

12292430.55 hits per line

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

87.05
/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)", "OpenPGP-S2K(SHA-384)", "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) {
8✔
146
            Test::Result result(algo_spec + " stop_token cancellation");
7✔
147

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

155
            result.start_timer();
7✔
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);
7✔
159
            const auto tune_time = std::chrono::milliseconds(100);
7✔
160
            const size_t max_mem = 32;
7✔
161
            auto pwdhash = fam->tune(32, run_time, max_mem, tune_time);
7✔
162

163
            // Temporary until all password hashes support stop_token
164
            if(!pwdhash->supports_cooperative_cancellation()) {
7✔
165
               result.test_note(algo_spec + " does not support cooperative cancellation");
1✔
166
               result.end_timer();
1✔
167
               results.push_back(result);
1✔
168
               continue;
1✔
169
            }
170

171
            const std::string password = "cancel-me";
6✔
172
            const std::vector<uint8_t> salt(16, 0xAA);
6✔
173
            std::vector<uint8_t> out(32);
6✔
174

175
            std::stop_source src;
6✔
176

177
            // Helper thread that will request cancellation
178
            auto future = thread_pool.run([&src]() {
18✔
179
               const auto stop_time = std::chrono::milliseconds(500);
6✔
180
               std::this_thread::sleep_for(stop_time);
6✔
181
               src.request_stop();  // fire the cancellation
6✔
182
            });
6✔
183

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

199
            future.get();  // ensure the canceller thread finished
6✔
200

201
            result.end_timer();
6✔
202
            results.push_back(result);
6✔
203
         }
32✔
204
         return results;
1✔
205
      }
1✔
206
};
207

208
BOTAN_REGISTER_TEST("pbkdf", "pwdhash_stop_token", Pwdhash_StopToken_Test);
209
   #endif
210

211
#endif
212

213
#if defined(BOTAN_HAS_PBKDF_BCRYPT)
214

215
class Bcrypt_PBKDF_KAT_Tests final : public Text_Based_Test {
×
216
   public:
217
      Bcrypt_PBKDF_KAT_Tests() : Text_Based_Test("bcrypt_pbkdf.vec", "Passphrase,Salt,Iterations,Output") {}
2✔
218

219
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
36✔
220
         const size_t rounds = vars.get_req_sz("Iterations");
36✔
221
         const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
36✔
222
         const std::string passphrase = vars.get_req_str("Passphrase");
36✔
223
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
36✔
224

225
         Test::Result result("bcrypt PBKDF");
36✔
226

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

229
         if(!pwdhash_fam) {
36✔
230
            result.test_failure("Bcrypt-PBKDF is missing PasswordHashFamily");
×
231
            return result;
×
232
         }
233

234
         auto pwdhash = pwdhash_fam->from_iterations(rounds);
36✔
235

236
         std::vector<uint8_t> derived(expected.size());
36✔
237
         pwdhash->hash(derived, passphrase, salt);
72✔
238

239
         result.test_eq("derived key", derived, expected);
72✔
240

241
         return result;
36✔
242
      }
180✔
243
};
244

245
BOTAN_REGISTER_TEST("pbkdf", "bcrypt_pbkdf", Bcrypt_PBKDF_KAT_Tests);
246

247
#endif
248

249
#if defined(BOTAN_HAS_SCRYPT)
250

251
class Scrypt_KAT_Tests final : public Text_Based_Test {
×
252
   public:
253
      Scrypt_KAT_Tests() : Text_Based_Test("scrypt.vec", "Passphrase,Salt,N,R,P,Output") {}
2✔
254

255
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
15✔
256
         const size_t N = vars.get_req_sz("N");
15✔
257
         const size_t R = vars.get_req_sz("R");
15✔
258
         const size_t P = vars.get_req_sz("P");
15✔
259
         const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
15✔
260
         const std::string passphrase = vars.get_req_str("Passphrase");
15✔
261
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
15✔
262

263
         Test::Result result("scrypt");
15✔
264

265
         if(N >= 1048576 && Test::run_long_tests() == false) {
15✔
266
            return result;
267
         }
268

269
         auto pwdhash_fam = Botan::PasswordHashFamily::create("Scrypt");
15✔
270

271
         if(!pwdhash_fam) {
15✔
272
            result.test_failure("Scrypt is missing PasswordHashFamily");
×
273
            return result;
×
274
         }
275

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

278
         std::vector<uint8_t> pwdhash_derived(expected.size());
15✔
279
         pwdhash->hash(pwdhash_derived, passphrase, salt);
30✔
280

281
         result.test_eq("pwdhash derived key", pwdhash_derived, expected);
30✔
282

283
         return result;
15✔
284
      }
75✔
285
};
286

287
BOTAN_REGISTER_TEST("pbkdf", "scrypt", Scrypt_KAT_Tests);
288

289
#endif
290

291
#if defined(BOTAN_HAS_ARGON2)
292

293
class Argon2_KAT_Tests final : public Text_Based_Test {
×
294
   public:
295
      Argon2_KAT_Tests() : Text_Based_Test("argon2.vec", "Passphrase,Salt,P,M,T,Output", "Secret,AD") {}
2✔
296

297
      Test::Result run_one_test(const std::string& mode, const VarMap& vars) override {
1,071✔
298
         const size_t P = vars.get_req_sz("P");
1,071✔
299
         const size_t M = vars.get_req_sz("M");
1,071✔
300
         const size_t T = vars.get_req_sz("T");
1,071✔
301
         const std::vector<uint8_t> key = vars.get_opt_bin("Secret");
1,071✔
302
         const std::vector<uint8_t> ad = vars.get_opt_bin("AD");
1,071✔
303
         const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
1,071✔
304
         const std::vector<uint8_t> passphrase = vars.get_req_bin("Passphrase");
1,071✔
305
         const std::vector<uint8_t> expected = vars.get_req_bin("Output");
1,071✔
306

307
         Test::Result result(mode);
2,142✔
308

309
         auto pwdhash_fam = Botan::PasswordHashFamily::create(mode);
1,071✔
310

311
         if(!pwdhash_fam) {
1,071✔
312
            result.test_failure("Argon2 is missing PasswordHashFamily");
×
313
            return result;
×
314
         }
315

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

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

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

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

325
         return result;
1,071✔
326
      }
6,654✔
327
};
328

329
BOTAN_REGISTER_SERIALIZED_TEST("pbkdf", "argon2", Argon2_KAT_Tests);
330

331
#endif
332

333
#if defined(BOTAN_HAS_PGP_S2K)
334

335
class PGP_S2K_Iter_Test final : public Test {
×
336
   public:
337
      std::vector<Test::Result> run() override {
1✔
338
         Test::Result result("PGP_S2K iteration encoding");
1✔
339

340
         // The maximum representable iteration count
341
         const size_t max_iter = 65011712;
1✔
342

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

346
         for(size_t c = 0; c != 256; ++c) {
257✔
347
            const size_t dec = Botan::RFC4880_decode_count(static_cast<uint8_t>(c));
256✔
348
            const size_t comp_dec = (16 + (c & 0x0F)) << ((c >> 4) + 6);
256✔
349
            result.test_eq("Decoded value matches PGP formula", dec, comp_dec);
256✔
350

351
            const size_t enc = Botan::RFC4880_encode_count(comp_dec);
256✔
352
            result.test_eq("Encoded value matches PGP formula", enc, c);
512✔
353
         }
354

355
         uint8_t last_enc = 0;
356

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

361
            /*
362
            The iteration count as encoded may not be exactly the
363
            value requested, but should never be less
364
            */
365
            const size_t dec = Botan::RFC4880_decode_count(enc);
1,015,809✔
366
            result.test_gte("Decoded value is >= requested", dec, i);
1,015,809✔
367

368
            last_enc = enc;
1,015,809✔
369
         }
370

371
         return std::vector<Test::Result>{result};
3✔
372
      }
2✔
373
};
374

375
BOTAN_REGISTER_TEST("pbkdf", "pgp_s2k_iter", PGP_S2K_Iter_Test);
376

377
#endif
378

379
}  // namespace
380

381
}  // 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