From 8e21b6dfb9ebe0d6407d45222c002f1e5e9afd6c Mon Sep 17 00:00:00 2001 From: Jack Lloyd Date: Mon, 2 Dec 2024 17:30:14 -0500 Subject: [PATCH] Add simplified measurement loop for password hashes --- src/lib/pbkdf/argon2/argon2pwhash.cpp | 22 ++++------- src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp | 14 ++----- src/lib/pbkdf/pbkdf2/pbkdf2.cpp | 12 +----- src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp | 9 ++--- src/lib/pbkdf/scrypt/scrypt.cpp | 15 +------- src/lib/utils/info.txt | 1 + src/lib/utils/time_utils.h | 42 +++++++++++++++++++++ 7 files changed, 62 insertions(+), 53 deletions(-) create mode 100644 src/lib/utils/time_utils.h diff --git a/src/lib/pbkdf/argon2/argon2pwhash.cpp b/src/lib/pbkdf/argon2/argon2pwhash.cpp index cc103cd1823..407f9a6c8f1 100644 --- a/src/lib/pbkdf/argon2/argon2pwhash.cpp +++ b/src/lib/pbkdf/argon2/argon2pwhash.cpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include @@ -85,22 +85,16 @@ std::unique_ptr Argon2_Family::tune(size_t /*output_length*/, const size_t p = 1; size_t t = 1; - Timer timer("Argon2"); + size_t M = 4 * 1024; auto pwhash = this->from_params(tune_M, t, p); - timer.run_until_elapsed(tune_time, [&]() { + auto tune_fn = [&]() { uint8_t output[64] = {0}; pwhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); - }); - - if(timer.events() == 0 || timer.value() == 0) { - return default_params(); - } + }; - size_t M = 4 * 1024; - - const uint64_t measured_time = timer.value() / (timer.events() * (tune_M / M)); + const uint64_t measured_time = measure_cost(tune_time, tune_fn) / (tune_M / m); const uint64_t target_nsec = msec.count() * static_cast(1000000); @@ -108,10 +102,8 @@ std::unique_ptr Argon2_Family::tune(size_t /*output_length*/, * Argon2 scaling rules: * k*M, k*t, k*p all increase cost by about k * - * Since we don't even take advantage of p > 1, we prefer increasing - * t or M instead. - * - * If possible to increase M, prefer that. + * First preference is to increase M up to max allowed value. + * Any remaining time budget is spent on increasing t. */ uint64_t est_nsec = measured_time; diff --git a/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp b/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp index 0b58d908610..1cf9029ffbe 100644 --- a/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp +++ b/src/lib/pbkdf/bcrypt_pbkdf/bcrypt_pbkdf.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace Botan { @@ -30,8 +30,6 @@ std::unique_ptr Bcrypt_PBKDF_Family::tune(size_t output_length, std::chrono::milliseconds msec, size_t /*max_memory*/, std::chrono::milliseconds tune_time) const { - Timer timer("Bcrypt_PBKDF"); - const size_t blocks = (output_length + 32 - 1) / 32; if(blocks == 0) { @@ -42,16 +40,12 @@ std::unique_ptr Bcrypt_PBKDF_Family::tune(size_t output_length, auto pwhash = this->from_iterations(starting_iter); - timer.run_until_elapsed(tune_time, [&]() { + auto tune_fn = [&]() { uint8_t output[32] = {0}; pwhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); - }); - - if(timer.events() < blocks || timer.value() == 0) { - return default_params(); - } + }; - const uint64_t measured_time = timer.value() / (timer.events() / blocks); + const uint64_t measured_time = measure_cost(tune_time, tune_fn) / blocks; const uint64_t target_nsec = msec.count() * static_cast(1000000); diff --git a/src/lib/pbkdf/pbkdf2/pbkdf2.cpp b/src/lib/pbkdf/pbkdf2/pbkdf2.cpp index 1a164d03805..5672948d928 100644 --- a/src/lib/pbkdf/pbkdf2/pbkdf2.cpp +++ b/src/lib/pbkdf/pbkdf2/pbkdf2.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include namespace Botan { @@ -40,22 +40,14 @@ size_t tune_pbkdf2(MessageAuthenticationCode& prf, // Short output ensures we only need a single PBKDF2 block - Timer timer("PBKDF2"); - prf.set_key(nullptr, 0); - timer.run_until_elapsed(tune_time, [&]() { + const uint64_t duration_nsec = measure_cost(tune_time, [&]() { uint8_t out[12] = {0}; uint8_t salt[12] = {0}; pbkdf2(prf, out, sizeof(out), salt, sizeof(salt), trial_iterations); }); - if(timer.events() == 0) { - return trial_iterations; - } - - const uint64_t duration_nsec = timer.value() / timer.events(); - const uint64_t desired_nsec = static_cast(msec.count()) * 1000000; if(duration_nsec > desired_nsec) { diff --git a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp index 0cf798f78a1..95a21617b9b 100644 --- a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp +++ b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include namespace Botan { @@ -95,13 +95,12 @@ std::unique_ptr RFC4880_S2K_Family::tune(size_t output_len, std::chrono::milliseconds msec, size_t /*max_memory_usage_mb*/, std::chrono::milliseconds tune_time) const { - const size_t buf_size = 1024; + constexpr size_t buf_size = 1024; std::vector buffer(buf_size); - Timer timer("RFC4880_S2K", buf_size); - timer.run_until_elapsed(tune_time, [&]() { m_hash->update(buffer); }); + const uint64_t measured_nsec = measure_cost(tune_time, [&]() { m_hash->update(buffer); }); - const double hash_bytes_per_second = timer.bytes_per_second(); + const double hash_bytes_per_second = (buf_size * 1000000000.0) / measured_nsec; const uint64_t desired_nsec = msec.count() * 1000000; const size_t hash_size = m_hash->output_length(); diff --git a/src/lib/pbkdf/scrypt/scrypt.cpp b/src/lib/pbkdf/scrypt/scrypt.cpp index 2d8fa733c47..d6e18392cb9 100644 --- a/src/lib/pbkdf/scrypt/scrypt.cpp +++ b/src/lib/pbkdf/scrypt/scrypt.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include namespace Botan { @@ -59,24 +59,13 @@ std::unique_ptr Scrypt_Family::tune(size_t output_length, size_t r = 1; size_t p = 1; - Timer timer("Scrypt"); - auto pwdhash = this->from_params(N, r, p); - timer.run_until_elapsed(tune_time, [&]() { + const uint64_t measured_time = measure_cost(tune_time, [&]() { uint8_t output[32] = {0}; pwdhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); }); - // No timer events seems strange, perhaps something is wrong - give - // up on this and just return default params - if(timer.events() == 0) { - return default_params(); - } - - // nsec per eval of scrypt with initial params - const uint64_t measured_time = timer.value() / timer.events(); - const uint64_t target_nsec = msec.count() * static_cast(1000000); uint64_t est_nsec = measured_time; diff --git a/src/lib/utils/info.txt b/src/lib/utils/info.txt index d0a27c87c4d..6ab0aedd501 100644 --- a/src/lib/utils/info.txt +++ b/src/lib/utils/info.txt @@ -46,6 +46,7 @@ rotate.h rounding.h scan_name.h stl_util.h +time_utils.h diff --git a/src/lib/utils/time_utils.h b/src/lib/utils/time_utils.h new file mode 100644 index 00000000000..5df8801736f --- /dev/null +++ b/src/lib/utils/time_utils.h @@ -0,0 +1,42 @@ +/** +* (C) 2024 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TIME_UTILS_H_ +#define BOTAN_TIME_UTILS_H_ + +#include +#include + +namespace Botan { + +template +uint64_t measure_cost(std::chrono::milliseconds trial_msec, F func) { + const uint64_t trial_nsec = std::chrono::duration_cast(trial_msec).count(); + + uint64_t total_nsec = 0; + uint64_t trials = 0; + + auto trial_start = OS::get_system_timestamp_ns(); + + for(;;) { + const auto start = OS::get_system_timestamp_ns(); + func(); + const auto end = OS::get_system_timestamp_ns(); + + if(end >= start) { + total_nsec += (end - start); + trials += 1; + + if((end - trial_start) >= trial_nsec) { + return (total_nsec / trials); + } + } + } +} + +} // namespace Botan + +#endif