diff options
author | Jack Lloyd <[email protected]> | 2018-09-13 11:50:17 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2018-09-13 11:50:17 -0400 |
commit | 880b809330a9ebb0c41de8b1a145287ccab37e4d (patch) | |
tree | 7eff1543c2405a8bdd4f5fa134aa0fdf23cef830 /src | |
parent | 2c75e41d1051b9e2eb73a669510e7d8a74e9f0d3 (diff) | |
parent | 6073c71c5c2f4a01e42439ccb2d7d424b6489413 (diff) |
Merge GH #1670 New password hashing interface
Diffstat (limited to 'src')
-rw-r--r-- | src/cli/pbkdf.cpp | 99 | ||||
-rw-r--r-- | src/cli/speed.cpp | 324 | ||||
-rw-r--r-- | src/lib/ffi/ffi.h | 57 | ||||
-rw-r--r-- | src/lib/ffi/ffi_kdf.cpp | 94 | ||||
-rw-r--r-- | src/lib/pbkdf/info.txt | 3 | ||||
-rw-r--r-- | src/lib/pbkdf/pbkdf.h | 3 | ||||
-rw-r--r-- | src/lib/pbkdf/pbkdf2/info.txt | 2 | ||||
-rw-r--r-- | src/lib/pbkdf/pbkdf2/pbkdf2.cpp | 224 | ||||
-rw-r--r-- | src/lib/pbkdf/pbkdf2/pbkdf2.h | 78 | ||||
-rw-r--r-- | src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp | 212 | ||||
-rw-r--r-- | src/lib/pbkdf/pgp_s2k/pgp_s2k.h | 90 | ||||
-rw-r--r-- | src/lib/pbkdf/pwdhash.cpp | 88 | ||||
-rw-r--r-- | src/lib/pbkdf/pwdhash.h | 162 | ||||
-rw-r--r-- | src/lib/pbkdf/scrypt/info.txt | 2 | ||||
-rw-r--r-- | src/lib/pbkdf/scrypt/scrypt.cpp | 258 | ||||
-rw-r--r-- | src/lib/pbkdf/scrypt/scrypt.h | 98 | ||||
-rw-r--r-- | src/lib/pubkey/pbes2/pbes2.cpp | 64 | ||||
-rw-r--r-- | src/lib/utils/info.txt | 3 | ||||
-rw-r--r-- | src/lib/utils/timer.cpp | 118 | ||||
-rw-r--r-- | src/lib/utils/timer.h | 216 | ||||
-rwxr-xr-x | src/scripts/test_cli.py | 29 | ||||
-rw-r--r-- | src/tests/test_ffi.cpp | 32 | ||||
-rw-r--r-- | src/tests/test_pbkdf.cpp | 106 |
23 files changed, 1763 insertions, 599 deletions
diff --git a/src/cli/pbkdf.cpp b/src/cli/pbkdf.cpp new file mode 100644 index 000000000..c2e4112b6 --- /dev/null +++ b/src/cli/pbkdf.cpp @@ -0,0 +1,99 @@ +/* +* (C) 2018 Ribose Inc +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include "cli.h" + +#if defined(BOTAN_HAS_PBKDF) + #include <botan/pwdhash.h> + #include <botan/internal/os_utils.h> +#endif + +namespace Botan_CLI { + +#if defined(BOTAN_HAS_PBKDF) + +class PBKDF_Tune final : public Command + { + public: + PBKDF_Tune() : Command("pbkdf_tune --algo=Scrypt --max-mem=160 --output-len=32 --check *times") {} + + std::string group() const override + { + return "passhash"; + } + + std::string description() const override + { + return "Tune a PBKDF algo"; + } + + void go() override + { + const size_t output_len = get_arg_sz("output-len"); + const std::string algo = get_arg("algo"); + const size_t max_mem = get_arg_sz("max-mem"); + const bool check_time = flag_set("check"); + + std::unique_ptr<Botan::PasswordHashFamily> pwdhash_fam = + Botan::PasswordHashFamily::create(algo); + + if(!pwdhash_fam) + throw CLI_Error_Unsupported("Password hashing", algo); + + for(const std::string& time : get_arg_list("times")) + { + std::unique_ptr<Botan::PasswordHash> pwhash; + + if(time == "default") + { + pwhash = pwdhash_fam->default_params(); + } + else + { + size_t msec = 0; + try + { + msec = std::stoul(time); + } + catch(std::exception&) + { + throw CLI_Usage_Error("Unknown time value '" + time + "' for pbkdf_tune"); + } + + pwhash = pwdhash_fam->tune(output_len, std::chrono::milliseconds(msec), max_mem); + } + + output() << "For " << time << " ms selected " << pwhash->to_string(); + + if(pwhash->total_memory_usage() > 0) + { + output() << " using " << pwhash->total_memory_usage()/(1024*1024) << " MiB"; + } + + if(check_time) + { + std::vector<uint8_t> outbuf(output_len); + const uint8_t salt[8] = { 0 }; + + const uint64_t start_ns = Botan::OS::get_system_timestamp_ns(); + pwhash->derive_key(outbuf.data(), outbuf.size(), + "test", 4, salt, sizeof(salt)); + const uint64_t end_ns = Botan::OS::get_system_timestamp_ns(); + const uint64_t dur_ns = end_ns - start_ns; + + output() << " took " << (dur_ns / 1000000.0) << " msec to compute"; + } + + output() << "\n"; + } + } + }; + +BOTAN_REGISTER_COMMAND("pbkdf_tune", PBKDF_Tune); + +#endif + +} diff --git a/src/cli/speed.cpp b/src/cli/speed.cpp index db195ac48..80fe6591b 100644 --- a/src/cli/speed.cpp +++ b/src/cli/speed.cpp @@ -21,6 +21,7 @@ #include <botan/parsing.h> #include <botan/cpuid.h> #include <botan/internal/os_utils.h> +#include <botan/internal/timer.h> #include <botan/version.h> #if defined(BOTAN_HAS_BIGINT) @@ -125,304 +126,9 @@ namespace Botan_CLI { -namespace { - -class Timer final - { - public: - Timer(const std::string& name, - const std::string& provider, - const std::string& doing, - uint64_t event_mult, - size_t buf_size, - double clock_cycle_ratio, - uint64_t clock_speed) - : m_name(name + ((provider.empty() || provider == "base") ? "" : " [" + provider + "]")) - , m_doing(doing) - , m_buf_size(buf_size) - , m_event_mult(event_mult) - , m_clock_cycle_ratio(clock_cycle_ratio) - , m_clock_speed(clock_speed) - {} - - Timer(const Timer& other) = default; - - static uint64_t get_system_timestamp_ns() - { - return Botan::OS::get_system_timestamp_ns(); - } - - static uint64_t get_cpu_cycle_counter() - { - return Botan::OS::get_processor_timestamp(); - } - - void start() - { - stop(); - m_timer_start = Timer::get_system_timestamp_ns(); - m_cpu_cycles_start = Timer::get_cpu_cycle_counter(); - } - - void stop(); - - bool under(std::chrono::milliseconds msec) - { - return (milliseconds() < msec.count()); - } - - class Timer_Scope final - { - public: - explicit Timer_Scope(Timer& timer) - : m_timer(timer) - { - m_timer.start(); - } - ~Timer_Scope() - { - try - { - m_timer.stop(); - } - catch(...) {} - } - private: - Timer& m_timer; - }; - - template<typename F> - auto run(F f) -> decltype(f()) - { - Timer_Scope timer(*this); - return f(); - } - - template<typename F> - void run_until_elapsed(std::chrono::milliseconds msec, F f) - { - while(this->under(msec)) - { - run(f); - } - } - - uint64_t value() const - { - return m_time_used; - } - - double seconds() const - { - return milliseconds() / 1000.0; - } - - double milliseconds() const - { - return value() / 1000000.0; - } - - double ms_per_event() const - { - return milliseconds() / events(); - } - - uint64_t cycles_consumed() const - { - if(m_clock_speed != 0) - { - return (static_cast<double>(m_clock_speed) * value()) / 1000; - } - return m_cpu_cycles_used; - } - - uint64_t events() const - { - return m_event_count * m_event_mult; - } - - const std::string& get_name() const - { - return m_name; - } - - const std::string& doing() const - { - return m_doing; - } - - size_t buf_size() const - { - return m_buf_size; - } - - double bytes_per_second() const - { - return seconds() > 0.0 ? events() / seconds() : 0.0; - } - - double events_per_second() const - { - return seconds() > 0.0 ? events() / seconds() : 0.0; - } - - double seconds_per_event() const - { - return events() > 0 ? seconds() / events() : 0.0; - } - - void set_custom_msg(const std::string& s) - { - m_custom_msg = s; - } - - bool operator<(const Timer& other) const - { - if(this->doing() != other.doing()) - return (this->doing() < other.doing()); - - return (this->get_name() < other.get_name()); - } - - std::string to_string() const - { - if(m_custom_msg.size() > 0) - { - return m_custom_msg; - } - else if(this->buf_size() == 0) - { - return result_string_ops(); - } - else - { - return result_string_bps(); - } - } - - private: - std::string result_string_bps() const; - std::string result_string_ops() const; - - // const data - std::string m_name, m_doing; - size_t m_buf_size; - uint64_t m_event_mult; - double m_clock_cycle_ratio; - uint64_t m_clock_speed; - - // set at runtime - std::string m_custom_msg; - uint64_t m_time_used = 0, m_timer_start = 0; - uint64_t m_event_count = 0; - - uint64_t m_max_time = 0, m_min_time = 0; - uint64_t m_cpu_cycles_start = 0, m_cpu_cycles_used = 0; - }; - -void Timer::stop() - { - if(m_timer_start) - { - const uint64_t now = Timer::get_system_timestamp_ns(); - - if(now > m_timer_start) - { - uint64_t dur = now - m_timer_start; - - m_time_used += dur; - - if(m_cpu_cycles_start != 0) - { - uint64_t cycles_taken = Timer::get_cpu_cycle_counter() - m_cpu_cycles_start; - if(cycles_taken > 0) - { - m_cpu_cycles_used += static_cast<size_t>(cycles_taken * m_clock_cycle_ratio); - } - } - - if(m_event_count == 0) - { - m_min_time = m_max_time = dur; - } - else - { - m_max_time = std::max(m_max_time, dur); - m_min_time = std::min(m_min_time, dur); - } - } - - m_timer_start = 0; - ++m_event_count; - } - } +using Botan::Timer; -std::string Timer::result_string_bps() const - { - const size_t MiB = 1024 * 1024; - - const double MiB_total = static_cast<double>(events()) / MiB; - const double MiB_per_sec = MiB_total / seconds(); - - std::ostringstream oss; - oss << get_name(); - - if(!doing().empty()) - { - oss << " " << doing(); - } - - if(buf_size() > 0) - { - oss << " buffer size " << buf_size() << " bytes:"; - } - - if(events() == 0) - oss << " " << "N/A"; - else - oss << " " << std::fixed << std::setprecision(3) << MiB_per_sec << " MiB/sec"; - - if(cycles_consumed() != 0) - { - const double cycles_per_byte = static_cast<double>(cycles_consumed()) / events(); - oss << " " << std::fixed << std::setprecision(2) << cycles_per_byte << " cycles/byte"; - } - - oss << " (" << MiB_total << " MiB in " << milliseconds() << " ms)\n"; - - return oss.str(); - } - -std::string Timer::result_string_ops() const - { - std::ostringstream oss; - - oss << get_name() << " "; - - if(events() == 0) - { - oss << "no events\n"; - } - else - { - oss << static_cast<uint64_t>(events_per_second()) - << ' ' << doing() << "/sec; " - << std::setprecision(2) << std::fixed - << ms_per_event() << " ms/op"; - - if(cycles_consumed() != 0) - { - const double cycles_per_op = static_cast<double>(cycles_consumed()) / events(); - const size_t precision = (cycles_per_op < 10000) ? 2 : 0; - oss << " " << std::fixed << std::setprecision(precision) << cycles_per_op << " cycles/op"; - } - - oss << " (" << events() << " " << (events() == 1 ? "op" : "ops") - << " in " << milliseconds() << " ms)\n"; - } - - return oss.str(); - } +namespace { class JSON_Output final { @@ -1935,11 +1641,11 @@ class Speed final : public Command } } - void bench_pk_sig(const Botan::Private_Key& key, - const std::string& nm, - const std::string& provider, - const std::string& padding, - std::chrono::milliseconds msec) + size_t bench_pk_sig(const Botan::Private_Key& key, + const std::string& nm, + const std::string& provider, + const std::string& padding, + std::chrono::milliseconds msec) { std::vector<uint8_t> message, signature, bad_signature; @@ -1989,8 +1695,12 @@ class Speed final : public Command } } + const size_t events = std::min(sig_timer->events(), ver_timer->events()); + record_result(sig_timer); record_result(ver_timer); + + return events; } #endif @@ -2224,7 +1934,8 @@ class Speed final : public Command })); record_result(keygen_timer); - bench_pk_sig(*key, params, provider, "", msec); + if(bench_pk_sig(*key, params, provider, "", msec) == 1) + break; } } #endif @@ -2282,13 +1993,14 @@ class Speed final : public Command for(size_t N : { 8192, 16384, 32768, 65536 }) { - for(size_t r : { 1, 8 }) + for(size_t r : { 1, 8, 16 }) { - for(size_t p : { 1, 4, 8 }) + for(size_t p : { 1, 4 }) { std::unique_ptr<Timer> scrypt_timer = make_timer( "scrypt-" + std::to_string(N) + "-" + - std::to_string(r) + "-" + std::to_string(p)); + std::to_string(r) + "-" + std::to_string(p) + + " (" + std::to_string(Botan::scrypt_memory_usage(N, r, p) / (1024*1024)) + " MiB)"); uint8_t out[64]; uint8_t salt[8]; diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 57631bc28..ff7086488 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -514,7 +514,7 @@ BOTAN_PUBLIC_API(2,0) int botan_cipher_destroy(botan_cipher_t cipher); /* * Derive a key from a passphrase for a number of iterations -* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2" +* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2(SHA-256)" * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from @@ -531,7 +531,7 @@ BOTAN_PUBLIC_API(2,0) int botan_pbkdf(const char* pbkdf_algo, /** * Derive a key from a passphrase, running until msec time has elapsed. -* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2" +* @param pbkdf_algo PBKDF algorithm, e.g., "PBKDF2(SHA-256)" * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from @@ -550,6 +550,58 @@ BOTAN_PUBLIC_API(2,0) int botan_pbkdf_timed(const char* pbkdf_algo, size_t* out_iterations_used); +/* +* Derive a key from a passphrase +* @param algo PBKDF algorithm, e.g., "PBKDF2(SHA-256)" or "Scrypt" +* @param param1 the first PBKDF algorithm parameter +* @param param2 the second PBKDF algorithm parameter (may be zero if unneeded) +* @param param3 the third PBKDF algorithm parameter (may be zero if unneeded) +* @param out buffer to store the derived key, must be of out_len bytes +* @param out_len the desired length of the key to produce +* @param passphrase the password to derive the key from +* @param salt a randomly chosen salt +* @param salt_len length of salt in bytes +* @return 0 on success, a negative value on failure +*/ +int BOTAN_PUBLIC_API(2,8) botan_pwdhash( + const char* algo, + size_t param1, + size_t param2, + size_t param3, + uint8_t out[], + size_t out_len, + const char* passphrase, + size_t passphrase_len, + const uint8_t salt[], + size_t salt_len); + +/* +* Derive a key from a passphrase +* @param pbkdf_algo PBKDF algorithm, e.g., "Scrypt" or "PBKDF2(SHA-256)" +* @param msec the desired runtime in milliseconds +* @param param1 will be set to the first password hash parameter +* @param param2 will be set to the second password hash parameter +* @param param3 will be set to the third password hash parameter +* @param out buffer to store the derived key, must be of out_len bytes +* @param out_len the desired length of the key to produce +* @param passphrase the password to derive the key from +* @param salt a randomly chosen salt +* @param salt_len length of salt in bytes +* @return 0 on success, a negative value on failure +*/ +int BOTAN_PUBLIC_API(2,8) botan_pwdhash_timed( + const char* algo, + uint32_t msec, + size_t* param1, + size_t* param2, + size_t* param3, + uint8_t out[], + size_t out_len, + const char* passphrase, + size_t passphrase_len, + const uint8_t salt[], + size_t salt_len); + /** * Derive a key using scrypt */ @@ -557,6 +609,7 @@ BOTAN_PUBLIC_API(2,8) int botan_scrypt(uint8_t out[], size_t out_len, const char* passphrase, const uint8_t salt[], size_t salt_len, size_t N, size_t r, size_t p); + /** * Derive a key * @param kdf_algo KDF algorithm, e.g., "SP800-56C" diff --git a/src/lib/ffi/ffi_kdf.cpp b/src/lib/ffi/ffi_kdf.cpp index c63406625..b72fe935e 100644 --- a/src/lib/ffi/ffi_kdf.cpp +++ b/src/lib/ffi/ffi_kdf.cpp @@ -22,29 +22,97 @@ extern "C" { using namespace Botan_FFI; -int botan_pbkdf(const char* pbkdf_algo, uint8_t out[], size_t out_len, +int botan_pbkdf(const char* algo, uint8_t out[], size_t out_len, const char* pass, const uint8_t salt[], size_t salt_len, size_t iterations) { - return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int { - std::unique_ptr<Botan::PBKDF> pbkdf(Botan::get_pbkdf(pbkdf_algo)); - pbkdf->pbkdf_iterations(out, out_len, pass, salt, salt_len, iterations); - return BOTAN_FFI_SUCCESS; - }); + return botan_pwdhash(algo, + iterations, + 0, + 0, + out, out_len, + pass, std::strlen(pass), + salt, salt_len); } -int botan_pbkdf_timed(const char* pbkdf_algo, +int botan_pbkdf_timed(const char* algo, uint8_t out[], size_t out_len, const char* password, const uint8_t salt[], size_t salt_len, size_t ms_to_run, size_t* iterations_used) { + return botan_pwdhash_timed(algo, + static_cast<uint32_t>(ms_to_run), + iterations_used, + nullptr, + nullptr, + out, out_len, + password, std::strlen(password), + salt, salt_len); + } + +int botan_pwdhash( + const char* algo, + size_t param1, + size_t param2, + size_t param3, + uint8_t out[], + size_t out_len, + const char* password, + size_t password_len, + const uint8_t salt[], + size_t salt_len) + { + return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int { + auto pwdhash_fam = Botan::PasswordHashFamily::create(algo); + + if(!pwdhash_fam) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + + auto pwdhash = pwdhash_fam->from_params(param1, param2, param3); + + pwdhash->derive_key(out, out_len, + password, password_len, + salt, salt_len); + + return BOTAN_FFI_SUCCESS; + }); + } + +int botan_pwdhash_timed( + const char* algo, + uint32_t msec, + size_t* param1, + size_t* param2, + size_t* param3, + uint8_t out[], + size_t out_len, + const char* password, + size_t password_len, + const uint8_t salt[], + size_t salt_len) + { return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int { - std::unique_ptr<Botan::PBKDF> pbkdf(Botan::get_pbkdf(pbkdf_algo)); - pbkdf->pbkdf_timed(out, out_len, password, salt, salt_len, - std::chrono::milliseconds(ms_to_run), - *iterations_used); + + auto pwdhash_fam = Botan::PasswordHashFamily::create(algo); + + if(!pwdhash_fam) + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; + + auto pwdhash = pwdhash_fam->tune(out_len, std::chrono::milliseconds(msec)); + + if(param1) + *param1 = pwdhash->iterations(); + if(param2) + *param2 = pwdhash->parallelism(); + if(param3) + *param3 = pwdhash->memory_param(); + + pwdhash->derive_key(out, out_len, + password, password_len, + salt, salt_len); + return BOTAN_FFI_SUCCESS; }); } @@ -63,13 +131,13 @@ int botan_kdf(const char* kdf_algo, } int botan_scrypt(uint8_t out[], size_t out_len, - const char* passphrase, + const char* password, const uint8_t salt[], size_t salt_len, size_t N, size_t r, size_t p) { #if defined(BOTAN_HAS_SCRYPT) return ffi_guard_thunk(BOTAN_CURRENT_FUNCTION, [=]() -> int { - Botan::scrypt(out, out_len, passphrase, salt, salt_len, N, r, p); + Botan::scrypt(out, out_len, password, strlen(password), salt, salt_len, N, r, p); return BOTAN_FFI_SUCCESS; }); #else diff --git a/src/lib/pbkdf/info.txt b/src/lib/pbkdf/info.txt index 48c6b56e6..650414f41 100644 --- a/src/lib/pbkdf/info.txt +++ b/src/lib/pbkdf/info.txt @@ -1,5 +1,5 @@ <defines> -PBKDF -> 20150626 +PBKDF -> 20180902 </defines> <requires> @@ -8,5 +8,6 @@ hash </requires> <header:public> +pwdhash.h pbkdf.h </header:public> diff --git a/src/lib/pbkdf/pbkdf.h b/src/lib/pbkdf/pbkdf.h index 7d3bceffc..9692b2546 100644 --- a/src/lib/pbkdf/pbkdf.h +++ b/src/lib/pbkdf/pbkdf.h @@ -17,6 +17,9 @@ namespace Botan { * Base class for PBKDF (password based key derivation function) * implementations. Converts a password into a key using a salt * and iterated hashing to make brute force attacks harder. +* +* Starting in 2.8 this functionality is also offered by PasswordHash. +* The PBKDF interface may be removed in a future release. */ class BOTAN_PUBLIC_API(2,0) PBKDF { diff --git a/src/lib/pbkdf/pbkdf2/info.txt b/src/lib/pbkdf/pbkdf2/info.txt index bc5c2e491..b6c6fb4e6 100644 --- a/src/lib/pbkdf/pbkdf2/info.txt +++ b/src/lib/pbkdf/pbkdf2/info.txt @@ -1,5 +1,5 @@ <defines> -PBKDF2 -> 20131128 +PBKDF2 -> 20180902 </defines> <requires> diff --git a/src/lib/pbkdf/pbkdf2/pbkdf2.cpp b/src/lib/pbkdf/pbkdf2/pbkdf2.cpp index cc2982f6e..65e8a9503 100644 --- a/src/lib/pbkdf/pbkdf2/pbkdf2.cpp +++ b/src/lib/pbkdf/pbkdf2/pbkdf2.cpp @@ -1,6 +1,7 @@ /* * PBKDF2 * (C) 1999-2007 Jack Lloyd +* (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -8,41 +9,115 @@ #include <botan/pbkdf2.h> #include <botan/exceptn.h> #include <botan/internal/rounding.h> +#include <botan/internal/timer.h> namespace Botan { +namespace { + +void pbkdf2_set_key(MessageAuthenticationCode& prf, + const char* password, + size_t password_len) + { + try + { + prf.set_key(cast_char_ptr_to_uint8(password), password_len); + } + catch(Invalid_Key_Length&) + { + throw Exception("PBKDF2 cannot accept passphrase of the given size"); + } + } + +} + size_t pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, - const std::string& passphrase, + const std::string& password, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) { - clear_mem(out, out_len); - - if(out_len == 0) - return 0; - - try + if(iterations == 0) { - prf.set_key(cast_char_ptr_to_uint8(passphrase.data()), passphrase.size()); - } - catch(Invalid_Key_Length&) - { - throw Exception("PBKDF2 with " + prf.name() + - " cannot accept passphrases of length " + - std::to_string(passphrase.size())); + iterations = PBKDF2(prf, out_len, msec).iterations(); } + PBKDF2 pbkdf2(prf, iterations); + + pbkdf2.derive_key(out, out_len, + password.c_str(), password.size(), + salt, salt_len); + + return iterations; + } + +namespace { + +size_t tune_pbkdf2(MessageAuthenticationCode& prf, + size_t output_length, + uint32_t msec) + { const size_t prf_sz = prf.output_length(); + BOTAN_ASSERT_NOMSG(prf_sz > 0); secure_vector<uint8_t> U(prf_sz); - const size_t blocks_needed = round_up(out_len, prf_sz) / prf_sz; + const size_t trial_iterations = 10000; + + // Short output ensures we only need a single PBKDF2 block + + Timer timer("PBKDF2"); + + const std::chrono::milliseconds tune_msec(30); + + prf.set_key(nullptr, 0); + + timer.run_until_elapsed(tune_msec, [&]() { + uint8_t out[16] = { 0 }; + uint8_t salt[16] = { 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<uint64_t>(msec) * 1000000; + + if(duration_nsec > desired_nsec) + return trial_iterations; + + const size_t blocks_needed = (output_length + prf_sz - 1) / prf_sz; + + const size_t multiplier = (desired_nsec / duration_nsec / blocks_needed); + + if(multiplier == 0) + return trial_iterations; + else + return trial_iterations * multiplier; + } + +} + +void pbkdf2(MessageAuthenticationCode& prf, + uint8_t out[], + size_t out_len, + const uint8_t salt[], + size_t salt_len, + size_t iterations) + { + clear_mem(out, out_len); + + if(out_len == 0) + return; + + const size_t prf_sz = prf.output_length(); + BOTAN_ASSERT_NOMSG(prf_sz > 0); - std::chrono::microseconds usec_per_block = - std::chrono::duration_cast<std::chrono::microseconds>(msec) / blocks_needed; + secure_vector<uint8_t> U(prf_sz); uint32_t counter = 1; while(out_len) @@ -55,64 +130,93 @@ pbkdf2(MessageAuthenticationCode& prf, xor_buf(out, U.data(), prf_output); - if(iterations == 0) + for(size_t i = 1; i != iterations; ++i) { - /* - If no iterations set, run the first block to calibrate based - on how long hashing takes on whatever machine we're running on. - */ - - const auto start = std::chrono::high_resolution_clock::now(); - - iterations = 1; // the first iteration we did above - - while(true) - { - prf.update(U); - prf.final(U.data()); - xor_buf(out, U.data(), prf_output); - iterations++; - - /* - Only break on relatively 'even' iterations. For one it - avoids confusion, and likely some broken implementations - break on getting completely randomly distributed values - */ - if(iterations % 10000 == 0) - { - auto time_taken = std::chrono::high_resolution_clock::now() - start; - auto usec_taken = std::chrono::duration_cast<std::chrono::microseconds>(time_taken); - if(usec_taken > usec_per_block) - break; - } - } - } - else - { - for(size_t i = 1; i != iterations; ++i) - { - prf.update(U); - prf.final(U.data()); - xor_buf(out, U.data(), prf_output); - } + prf.update(U); + prf.final(U.data()); + xor_buf(out, U.data(), prf_output); } out_len -= prf_output; out += prf_output; } - - return iterations; } +// PBKDF interface size_t PKCS5_PBKDF2::pbkdf(uint8_t key[], size_t key_len, - const std::string& passphrase, + const std::string& password, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const { - return pbkdf2(*m_mac.get(), key, key_len, passphrase, salt, salt_len, iterations, msec); + if(iterations == 0) + { + iterations = PBKDF2(*m_mac, key_len, msec).iterations(); + } + + PBKDF2 pbkdf2(*m_mac, iterations); + + pbkdf2.derive_key(key, key_len, + password.c_str(), password.size(), + salt, salt_len); + + return iterations; + } + +std::string PKCS5_PBKDF2::name() const + { + return "PBKDF2(" + m_mac->name() + ")"; + } + +PBKDF* PKCS5_PBKDF2::clone() const + { + return new PKCS5_PBKDF2(m_mac->clone()); } +// PasswordHash interface + +PBKDF2::PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec) : + m_prf(prf.clone()), + m_iterations(tune_pbkdf2(*m_prf, olen, msec.count())) + {} + +std::string PBKDF2::to_string() const + { + return "PBKDF2(" + m_prf->name() + "," + std::to_string(m_iterations) + ")"; + } + +void PBKDF2::derive_key(uint8_t out[], size_t out_len, + const char* password, const size_t password_len, + const uint8_t salt[], size_t salt_len) const + { + pbkdf2_set_key(*m_prf, password, password_len); + pbkdf2(*m_prf, out, out_len, salt, salt_len, m_iterations); + } + +std::string PBKDF2_Family::name() const + { + return "PBKDF2(" + m_prf->name() + ")"; + } + +std::unique_ptr<PasswordHash> PBKDF2_Family::tune(size_t output_len, std::chrono::milliseconds msec, size_t) const + { + return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, output_len, msec)); + } + +std::unique_ptr<PasswordHash> PBKDF2_Family::default_params() const + { + return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, 150000)); + } + +std::unique_ptr<PasswordHash> PBKDF2_Family::from_params(size_t iter, size_t, size_t) const + { + return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, iter)); + } + +std::unique_ptr<PasswordHash> PBKDF2_Family::from_iterations(size_t iter) const + { + return std::unique_ptr<PasswordHash>(new PBKDF2(*m_prf, iter)); + } } diff --git a/src/lib/pbkdf/pbkdf2/pbkdf2.h b/src/lib/pbkdf/pbkdf2/pbkdf2.h index ea357cac0..72637bc30 100644 --- a/src/lib/pbkdf/pbkdf2/pbkdf2.h +++ b/src/lib/pbkdf/pbkdf2/pbkdf2.h @@ -1,6 +1,7 @@ /* * PBKDF2 * (C) 1999-2007,2012 Jack Lloyd +* (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -9,6 +10,7 @@ #define BOTAN_PBKDF2_H_ #include <botan/pbkdf.h> +#include <botan/pwdhash.h> #include <botan/mac.h> namespace Botan { @@ -22,20 +24,76 @@ BOTAN_PUBLIC_API(2,0) size_t pbkdf2(MessageAuthenticationCode& prf, std::chrono::milliseconds msec); /** -* PKCS #5 PBKDF2 +* Perform PBKDF2. The prf is assumed to be keyed already. +*/ +BOTAN_PUBLIC_API(2,8) void pbkdf2(MessageAuthenticationCode& prf, + uint8_t out[], size_t out_len, + const uint8_t salt[], size_t salt_len, + size_t iterations); + +/** +* PBKDF2 +*/ +class BOTAN_PUBLIC_API(2,8) PBKDF2 final : public PasswordHash + { + public: + PBKDF2(const MessageAuthenticationCode& prf, size_t iter) : + m_prf(prf.clone()), + m_iterations(iter) + {} + + PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec); + + size_t iterations() const override { return m_iterations; } + + std::string to_string() const override; + + void derive_key(uint8_t out[], size_t out_len, + const char* password, const size_t password_len, + const uint8_t salt[], size_t salt_len) const override; + private: + std::unique_ptr<MessageAuthenticationCode> m_prf; + size_t m_iterations; + }; + +/** +* Family of PKCS #5 PBKDF2 operations +*/ +class BOTAN_PUBLIC_API(2,8) PBKDF2_Family final : public PasswordHashFamily + { + public: + PBKDF2_Family(MessageAuthenticationCode* prf) : m_prf(prf) {} + + std::string name() const override; + + std::unique_ptr<PasswordHash> tune(size_t output_len, + std::chrono::milliseconds msec, + size_t max_memory) const override; + + /** + * Return some default parameter set for this PBKDF that should be good + * enough for most users. The value returned may change over time as + * processing power and attacks improve. + */ + std::unique_ptr<PasswordHash> default_params() const override; + + std::unique_ptr<PasswordHash> from_iterations(size_t iter) const override; + + std::unique_ptr<PasswordHash> from_params( + size_t iter, size_t, size_t) const override; + private: + std::unique_ptr<MessageAuthenticationCode> m_prf; + }; + +/** +* PKCS #5 PBKDF2 (old interface) */ class BOTAN_PUBLIC_API(2,0) PKCS5_PBKDF2 final : public PBKDF { public: - std::string name() const override - { - return "PBKDF2(" + m_mac->name() + ")"; - } - - PBKDF* clone() const override - { - return new PKCS5_PBKDF2(m_mac->clone()); - } + std::string name() const override; + + PBKDF* clone() const override; size_t pbkdf(uint8_t output_buf[], size_t output_len, const std::string& passphrase, diff --git a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp index df659de3c..3f2c564c8 100644 --- a/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp +++ b/src/lib/pbkdf/pgp_s2k/pgp_s2k.cpp @@ -1,64 +1,26 @@ /* * OpenPGP S2K * (C) 1999-2007,2017 Jack Lloyd +* (C) 2018 Ribose Inc * * Distributed under the terms of the Botan license */ #include <botan/pgp_s2k.h> #include <botan/exceptn.h> +#include <botan/internal/timer.h> namespace Botan { -/* -PGP stores the iteration count as a single byte -Thus it can only actually take on one of 256 values, based on the -formula in RFC 4880 section 3.6.1.3 -*/ -static const uint32_t OPENPGP_S2K_ITERS[256] = { - 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, - 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2176, 2304, 2432, - 2560, 2688, 2816, 2944, 3072, 3200, 3328, 3456, 3584, 3712, - 3840, 3968, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, - 6144, 6400, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8704, - 9216, 9728, 10240, 10752, 11264, 11776, 12288, 12800, 13312, - 13824, 14336, 14848, 15360, 15872, 16384, 17408, 18432, 19456, - 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672, - 29696, 30720, 31744, 32768, 34816, 36864, 38912, 40960, 43008, - 45056, 47104, 49152, 51200, 53248, 55296, 57344, 59392, 61440, - 63488, 65536, 69632, 73728, 77824, 81920, 86016, 90112, 94208, - 98304, 102400, 106496, 110592, 114688, 118784, 122880, 126976, - 131072, 139264, 147456, 155648, 163840, 172032, 180224, 188416, - 196608, 204800, 212992, 221184, 229376, 237568, 245760, 253952, - 262144, 278528, 294912, 311296, 327680, 344064, 360448, 376832, - 393216, 409600, 425984, 442368, 458752, 475136, 491520, 507904, - 524288, 557056, 589824, 622592, 655360, 688128, 720896, 753664, - 786432, 819200, 851968, 884736, 917504, 950272, 983040, 1015808, - 1048576, 1114112, 1179648, 1245184, 1310720, 1376256, 1441792, - 1507328, 1572864, 1638400, 1703936, 1769472, 1835008, 1900544, - 1966080, 2031616, 2097152, 2228224, 2359296, 2490368, 2621440, - 2752512, 2883584, 3014656, 3145728, 3276800, 3407872, 3538944, - 3670016, 3801088, 3932160, 4063232, 4194304, 4456448, 4718592, - 4980736, 5242880, 5505024, 5767168, 6029312, 6291456, 6553600, - 6815744, 7077888, 7340032, 7602176, 7864320, 8126464, 8388608, - 8912896, 9437184, 9961472, 10485760, 11010048, 11534336, - 12058624, 12582912, 13107200, 13631488, 14155776, 14680064, - 15204352, 15728640, 16252928, 16777216, 17825792, 18874368, - 19922944, 20971520, 22020096, 23068672, 24117248, 25165824, - 26214400, 27262976, 28311552, 29360128, 30408704, 31457280, - 32505856, 33554432, 35651584, 37748736, 39845888, 41943040, - 44040192, 46137344, 48234496, 50331648, 52428800, 54525952, - 56623104, 58720256, 60817408, 62914560, 65011712 }; - -//static -uint8_t OpenPGP_S2K::encode_count(size_t desired_iterations) +uint8_t RFC4880_encode_count(size_t desired_iterations) { /* Only 256 different iterations are actually representable in OpenPGP format ... */ for(size_t c = 0; c < 256; ++c) { - const uint32_t decoded_iter = OPENPGP_S2K_ITERS[c]; + // TODO could binary search + const uint32_t decoded_iter = RFC4880_decode_count(c); if(decoded_iter >= desired_iterations) return static_cast<uint8_t>(c); } @@ -66,37 +28,75 @@ uint8_t OpenPGP_S2K::encode_count(size_t desired_iterations) return 255; } -//static -size_t OpenPGP_S2K::decode_count(uint8_t iter) +size_t RFC4880_decode_count(uint8_t iter) { + /* + PGP stores the iteration count as a single byte + Thus it can only actually take on one of 256 values, based on the + formula in RFC 4880 section 3.6.1.3 + */ + static const uint32_t OPENPGP_S2K_ITERS[256] = { + 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, + 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2176, 2304, 2432, + 2560, 2688, 2816, 2944, 3072, 3200, 3328, 3456, 3584, 3712, + 3840, 3968, 4096, 4352, 4608, 4864, 5120, 5376, 5632, 5888, + 6144, 6400, 6656, 6912, 7168, 7424, 7680, 7936, 8192, 8704, + 9216, 9728, 10240, 10752, 11264, 11776, 12288, 12800, 13312, + 13824, 14336, 14848, 15360, 15872, 16384, 17408, 18432, 19456, + 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672, + 29696, 30720, 31744, 32768, 34816, 36864, 38912, 40960, 43008, + 45056, 47104, 49152, 51200, 53248, 55296, 57344, 59392, 61440, + 63488, 65536, 69632, 73728, 77824, 81920, 86016, 90112, 94208, + 98304, 102400, 106496, 110592, 114688, 118784, 122880, 126976, + 131072, 139264, 147456, 155648, 163840, 172032, 180224, 188416, + 196608, 204800, 212992, 221184, 229376, 237568, 245760, 253952, + 262144, 278528, 294912, 311296, 327680, 344064, 360448, 376832, + 393216, 409600, 425984, 442368, 458752, 475136, 491520, 507904, + 524288, 557056, 589824, 622592, 655360, 688128, 720896, 753664, + 786432, 819200, 851968, 884736, 917504, 950272, 983040, 1015808, + 1048576, 1114112, 1179648, 1245184, 1310720, 1376256, 1441792, + 1507328, 1572864, 1638400, 1703936, 1769472, 1835008, 1900544, + 1966080, 2031616, 2097152, 2228224, 2359296, 2490368, 2621440, + 2752512, 2883584, 3014656, 3145728, 3276800, 3407872, 3538944, + 3670016, 3801088, 3932160, 4063232, 4194304, 4456448, 4718592, + 4980736, 5242880, 5505024, 5767168, 6029312, 6291456, 6553600, + 6815744, 7077888, 7340032, 7602176, 7864320, 8126464, 8388608, + 8912896, 9437184, 9961472, 10485760, 11010048, 11534336, + 12058624, 12582912, 13107200, 13631488, 14155776, 14680064, + 15204352, 15728640, 16252928, 16777216, 17825792, 18874368, + 19922944, 20971520, 22020096, 23068672, 24117248, 25165824, + 26214400, 27262976, 28311552, 29360128, 30408704, 31457280, + 32505856, 33554432, 35651584, 37748736, 39845888, 41943040, + 44040192, 46137344, 48234496, 50331648, 52428800, 54525952, + 56623104, 58720256, 60817408, 62914560, 65011712 }; + return OPENPGP_S2K_ITERS[iter]; } -size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len, - const std::string& passphrase, - const uint8_t salt[], size_t salt_len, - size_t iterations, - std::chrono::milliseconds msec) const - { - if(iterations == 0 && msec.count() > 0) // FIXME - throw Not_Implemented("OpenPGP_S2K does not implemented timed KDF"); +namespace { +void pgp_s2k(HashFunction& hash, + uint8_t output_buf[], size_t output_len, + const char* password, const size_t password_size, + const uint8_t salt[], size_t salt_len, + size_t iterations) + { if(iterations > 1 && salt_len == 0) - throw Invalid_Argument("OpenPGP_S2K requires a salt in iterated mode"); + throw Invalid_Argument("OpenPGP S2K requires a salt in iterated mode"); - secure_vector<uint8_t> input_buf(salt_len + passphrase.size()); + secure_vector<uint8_t> input_buf(salt_len + password_size); if(salt_len > 0) { copy_mem(&input_buf[0], salt, salt_len); } - if(passphrase.empty() == false) + if(password_size > 0) { copy_mem(&input_buf[salt_len], - cast_char_ptr_to_uint8(passphrase.data()), - passphrase.size()); + cast_char_ptr_to_uint8(password), + password_size); } - secure_vector<uint8_t> hash_buf(m_hash->output_length()); + secure_vector<uint8_t> hash_buf(hash.output_length()); size_t pass = 0; size_t generated = 0; @@ -108,7 +108,7 @@ size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len, // Preload some number of zero bytes (empty first iteration) std::vector<uint8_t> zero_padding(pass); - m_hash->update(zero_padding); + hash.update(zero_padding); // The input is always fully processed even if iterations is very small if(input_buf.empty() == false) @@ -117,18 +117,106 @@ size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len, while(left > 0) { const size_t input_to_take = std::min(left, input_buf.size()); - m_hash->update(input_buf.data(), input_to_take); + hash.update(input_buf.data(), input_to_take); left -= input_to_take; } } - m_hash->final(hash_buf.data()); + hash.final(hash_buf.data()); copy_mem(output_buf + generated, hash_buf.data(), output_this_pass); generated += output_this_pass; ++pass; } + } + +} + +size_t OpenPGP_S2K::pbkdf(uint8_t output_buf[], size_t output_len, + const std::string& password, + const uint8_t salt[], size_t salt_len, + size_t iterations, + std::chrono::milliseconds msec) const + { + std::unique_ptr<PasswordHash> pwdhash; + + if(iterations == 0) + { + RFC4880_S2K_Family s2k_params(m_hash->clone()); + iterations = s2k_params.tune(output_len, msec, 0)->iterations(); + } + + pgp_s2k(*m_hash, output_buf, output_len, + password.c_str(), password.size(), + salt, salt_len, + iterations); return iterations; } +std::string RFC4880_S2K_Family::name() const + { + return "OpenPGP-S2K(" + m_hash->name() + ")"; + } + +std::unique_ptr<PasswordHash> RFC4880_S2K_Family::tune(size_t output_len, std::chrono::milliseconds msec, size_t) const + { + const std::chrono::milliseconds tune_time = std::chrono::milliseconds(30); + + const size_t buf_size = 1024; + Botan::secure_vector<uint8_t> buffer(buf_size); + + Timer timer("RFC4880_S2K", buf_size); + timer.run_until_elapsed(tune_time, [&]() { + m_hash->update(buffer); + }); + + // bug in Timer::bytes_per_second? + const double hash_bytes_per_second = buf_size * timer.bytes_per_second(); + const uint64_t desired_nsec = msec.count() * 1000000; + + const size_t hash_size = m_hash->output_length(); + const size_t blocks_required = (output_len <= hash_size ? 1 : (output_len + hash_size - 1) / hash_size); + + const double bytes_to_be_hashed = (hash_bytes_per_second * (desired_nsec / 1000000000.0)) / blocks_required; + const size_t iterations = RFC4880_round_iterations(bytes_to_be_hashed); + + return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), iterations)); + } + +std::unique_ptr<PasswordHash> RFC4880_S2K_Family::from_params(size_t iter, size_t, size_t) const + { + return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), iter)); + } + +std::unique_ptr<PasswordHash> RFC4880_S2K_Family::default_params() const + { + return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), 50331648)); + } + +std::unique_ptr<PasswordHash> RFC4880_S2K_Family::from_iterations(size_t iter) const + { + return std::unique_ptr<PasswordHash>(new RFC4880_S2K(m_hash->clone(), iter)); + } + +RFC4880_S2K::RFC4880_S2K(HashFunction* hash, size_t iterations) : + m_hash(hash), + m_iterations(iterations) + { + } + +std::string RFC4880_S2K::to_string() const + { + return "OpenPGP-S2K(" + m_hash->name() + "," + std::to_string(m_iterations) + ")"; + } + +void RFC4880_S2K::derive_key(uint8_t out[], size_t out_len, + const char* password, const size_t password_len, + const uint8_t salt[], size_t salt_len) const + { + pgp_s2k(*m_hash, out, out_len, + password, password_len, + salt, salt_len, + m_iterations); + } + } diff --git a/src/lib/pbkdf/pgp_s2k/pgp_s2k.h b/src/lib/pbkdf/pgp_s2k/pgp_s2k.h index 02b228258..820fa3801 100644 --- a/src/lib/pbkdf/pgp_s2k/pgp_s2k.h +++ b/src/lib/pbkdf/pgp_s2k/pgp_s2k.h @@ -1,6 +1,7 @@ /* * OpenPGP PBKDF * (C) 1999-2007,2017 Jack Lloyd +* (C) 2018 Ribose Inc * * Distributed under the terms of the Botan license */ @@ -9,11 +10,31 @@ #define BOTAN_OPENPGP_S2K_H_ #include <botan/pbkdf.h> +#include <botan/pwdhash.h> #include <botan/hash.h> namespace Botan { /** +* RFC 4880 encodes the iteration count to a single-byte value +*/ +uint8_t BOTAN_PUBLIC_API(2,8) RFC4880_encode_count(size_t iterations); + +/** +* Decode the iteration count from RFC 4880 encoding +*/ +size_t BOTAN_PUBLIC_API(2,8) RFC4880_decode_count(uint8_t encoded_iter); + +/** +* Round an arbitrary iteration count to next largest iteration count +* supported by RFC4880 encoding. +*/ +inline size_t RFC4880_round_iterations(size_t iterations) + { + return RFC4880_decode_count(RFC4880_encode_count(iterations)); + } + +/** * OpenPGP's S2K * * See RFC 4880 sections 3.7.1.1, 3.7.1.2, and 3.7.1.3 @@ -57,10 +78,75 @@ class BOTAN_PUBLIC_API(2,2) OpenPGP_S2K final : public PBKDF /** * RFC 4880 encodes the iteration count to a single-byte value */ - static uint8_t encode_count(size_t iterations); + static uint8_t encode_count(size_t iterations) + { + return RFC4880_encode_count(iterations); + } + + static size_t decode_count(uint8_t encoded_iter) + { + return RFC4880_decode_count(encoded_iter); + } + + private: + std::unique_ptr<HashFunction> m_hash; + }; + +/** +* OpenPGP's S2K +* +* See RFC 4880 sections 3.7.1.1, 3.7.1.2, and 3.7.1.3 +* If the salt is empty and iterations == 1, "simple" S2K is used +* If the salt is non-empty and iterations == 1, "salted" S2K is used +* If the salt is non-empty and iterations > 1, "iterated" S2K is used +* +* Note that unlike PBKDF2, OpenPGP S2K's "iterations" are defined as +* the number of bytes hashed. +*/ +class BOTAN_PUBLIC_API(2,8) RFC4880_S2K final : public PasswordHash + { + public: + /** + * @param hash the hash function to use + * @param the iterations to use (this is rounded due to PGP formatting) + */ + RFC4880_S2K(HashFunction* hash, size_t iterations); + + std::string to_string() const override; + + size_t iterations() const override { return m_iterations; } + + void derive_key(uint8_t out[], size_t out_len, + const char* password, const size_t password_len, + const uint8_t salt[], size_t salt_len) const; + + private: + std::unique_ptr<HashFunction> m_hash; + size_t m_iterations; + }; + +class BOTAN_PUBLIC_API(2,8) RFC4880_S2K_Family final : public PasswordHashFamily + { + public: + RFC4880_S2K_Family(HashFunction* hash) : m_hash(hash) {} + + std::string name() const override; + + std::unique_ptr<PasswordHash> tune(size_t output_len, + std::chrono::milliseconds msec, + size_t max_mem) const override; + + /** + * Return some default parameter set for this PBKDF that should be good + * enough for most users. The value returned may change over time as + * processing power and attacks improve. + */ + std::unique_ptr<PasswordHash> default_params() const override; - static size_t decode_count(uint8_t encoded_iter); + std::unique_ptr<PasswordHash> from_iterations(size_t iter) const override; + std::unique_ptr<PasswordHash> from_params( + size_t iter, size_t, size_t) const override; private: std::unique_ptr<HashFunction> m_hash; }; diff --git a/src/lib/pbkdf/pwdhash.cpp b/src/lib/pbkdf/pwdhash.cpp new file mode 100644 index 000000000..783ac7066 --- /dev/null +++ b/src/lib/pbkdf/pwdhash.cpp @@ -0,0 +1,88 @@ +/* +* (C) 2018 Ribose Inc +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/pbkdf.h> +#include <botan/exceptn.h> +#include <botan/scan_name.h> + +#if defined(BOTAN_HAS_PBKDF2) + #include <botan/pbkdf2.h> +#endif + +#if defined(BOTAN_HAS_PGP_S2K) + #include <botan/pgp_s2k.h> +#endif + +#if defined(BOTAN_HAS_SCRYPT) + #include <botan/scrypt.h> +#endif + +namespace Botan { + +std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string& algo_spec, + const std::string& provider) + { + const SCAN_Name req(algo_spec); + +#if defined(BOTAN_HAS_PBKDF2) + if(req.algo_name() == "PBKDF2") + { + // TODO OpenSSL + + if(provider.empty() || provider == "base") + { + if(auto mac = MessageAuthenticationCode::create(req.arg(0))) + return std::unique_ptr<PasswordHashFamily>(new PBKDF2_Family(mac.release())); + + if(auto mac = MessageAuthenticationCode::create("HMAC(" + req.arg(0) + ")")) + return std::unique_ptr<PasswordHashFamily>(new PBKDF2_Family(mac.release())); + } + + return nullptr; + } +#endif + +#if defined(BOTAN_HAS_SCRYPT) + if(req.algo_name() == "Scrypt") + { + return std::unique_ptr<PasswordHashFamily>(new Scrypt_Family); + } +#endif + +#if defined(BOTAN_HAS_PGP_S2K) + if(req.algo_name() == "OpenPGP-S2K" && req.arg_count() == 1) + { + if(auto hash = HashFunction::create(req.arg(0))) + { + return std::unique_ptr<PasswordHashFamily>(new RFC4880_S2K_Family(hash.release())); + } + } +#endif + + BOTAN_UNUSED(req); + BOTAN_UNUSED(provider); + + return nullptr; + } + +//static +std::unique_ptr<PasswordHashFamily> +PasswordHashFamily::create_or_throw(const std::string& algo, + const std::string& provider) + { + if(auto pbkdf = PasswordHashFamily::create(algo, provider)) + { + return pbkdf; + } + throw Lookup_Error("PasswordHashFamily", algo, provider); + } + +std::vector<std::string> PasswordHashFamily::providers(const std::string& algo_spec) + { + return probe_providers_of<PasswordHashFamily>(algo_spec, { "base", "openssl" }); + } + +} diff --git a/src/lib/pbkdf/pwdhash.h b/src/lib/pbkdf/pwdhash.h new file mode 100644 index 000000000..d4a205340 --- /dev/null +++ b/src/lib/pbkdf/pwdhash.h @@ -0,0 +1,162 @@ +/* +* (C) 2018 Ribose Inc +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_PWDHASH_H_ +#define BOTAN_PWDHASH_H_ + +#include <botan/types.h> +#include <string> +#include <memory> +#include <vector> +#include <chrono> + +namespace Botan { + +/** +* Base class for password based key derivation functions. +* +* Converts a password into a key using a salt and iterated hashing to +* make brute force attacks harder. +*/ +class BOTAN_PUBLIC_API(2,8) PasswordHash + { + public: + virtual ~PasswordHash() = default; + + virtual std::string to_string() const = 0; + + /** + * Most password hashes have some notion of iterations. + */ + virtual size_t iterations() const = 0; + + /** + * Some password hashing algorithms have a parameter which controls how + * much memory is used. If not supported by some algorithm, returns 0. + */ + virtual size_t memory_param() const { return 0; } + + /** + * Some password hashing algorithms have a parallelism parameter. + * If the algorithm does not support this notion, then the + * function returns zero. This allows distinguishing between a + * password hash which just does not support parallel operation, + * vs one that does support parallel operation but which has been + * configured to use a single lane. + */ + virtual size_t parallelism() const { return 0; } + + /** + * Returns an estimate of the total memory usage required to perform this + * key derivation. + * + * If this algorithm uses a small and constant amount of memory, with no + * effort made towards being memory hard, this function returns 0. + */ + virtual size_t total_memory_usage() const { return 0; } + + /** + * Derive a key from a password + * + * @param out buffer to store the derived key, must be of out_len bytes + * @param out_len the desired length of the key to produce + * @param password the password to derive the key from + * @param password_len the length of password in bytes + * @param salt a randomly chosen salt + * @param salt_len length of salt in bytes + * + * This function is const, but is not thread safe. Different threads should + * either use unique objects, or serialize all access. + */ + virtual 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 = 0; + }; + +class BOTAN_PUBLIC_API(2,8) PasswordHashFamily + { + public: + /** + * Create an instance based on a name + * If provider is empty then best available is chosen. + * @param algo_spec algorithm name + * @param provider provider implementation to choose + * @return a null pointer if the algo/provider combination cannot be found + */ + static std::unique_ptr<PasswordHashFamily> create(const std::string& algo_spec, + const std::string& provider = ""); + + /** + * Create an instance based on a name, or throw if the + * algo/provider combination cannot be found. If provider is + * empty then best available is chosen. + */ + static std::unique_ptr<PasswordHashFamily> + create_or_throw(const std::string& algo_spec, + const std::string& provider = ""); + + /** + * @return list of available providers for this algorithm, empty if not available + */ + static std::vector<std::string> providers(const std::string& algo_spec); + + virtual ~PasswordHashFamily() = default; + + /** + * @return name of this PasswordHash + */ + virtual std::string name() const = 0; + + /** + * Return a new parameter set tuned for this machine + * @param output_length how long the output length will be + * @param msec the desired execution time in milliseconds + * + * @param max_memory_usage some password hash functions can use a tunable + * amount of memory, in this case max_memory_usage limits the amount of RAM + * the returned parameters will require, in mebibytes (2**20 bytes). It may + * require some small amount above the request. Set to zero to place no + * limit at all. + */ + virtual std::unique_ptr<PasswordHash> tune(size_t output_len, + std::chrono::milliseconds msec, + size_t max_memory_usage_mb = 0) const = 0; + + /** + * Return some default parameter set for this PBKDF that should be good + * enough for most users. The value returned may change over time as + * processing power and attacks improve. + */ + virtual std::unique_ptr<PasswordHash> default_params() const = 0; + + /** + * Return a parameter chosen based on a rough approximation with the + * specified iteration count. The exact value this returns for a particular + * algorithm may change from over time. Think of it as an alternative to + * tune, where time is expressed in terms of PBKDF2 iterations rather than + * milliseconds. + */ + virtual std::unique_ptr<PasswordHash> from_iterations(size_t iterations) const = 0; + + /** + * Create a password hash using some scheme specific format. + * Eg PBKDF2 and PGP-S2K set iterations in i1 + * Scrypt uses N,r,p in i{1-3} + * Bcrypt-PBKDF just has iterations + * Argon2{i,d,id} would use iterations, memory, parallelism for i{1-3}, + * and Argon2 type is part of the family. + * + * Values not needed should be set to 0 + */ + virtual std::unique_ptr<PasswordHash> from_params( + size_t i1, + size_t i2 = 0, + size_t i3 = 0) const = 0; + }; + +} + +#endif diff --git a/src/lib/pbkdf/scrypt/info.txt b/src/lib/pbkdf/scrypt/info.txt index cd1551d3b..3ba48fd72 100644 --- a/src/lib/pbkdf/scrypt/info.txt +++ b/src/lib/pbkdf/scrypt/info.txt @@ -1,5 +1,5 @@ <defines> -SCRYPT -> 20180515 +SCRYPT -> 20180902 </defines> <requires> diff --git a/src/lib/pbkdf/scrypt/scrypt.cpp b/src/lib/pbkdf/scrypt/scrypt.cpp index e258f391d..826438926 100644 --- a/src/lib/pbkdf/scrypt/scrypt.cpp +++ b/src/lib/pbkdf/scrypt/scrypt.cpp @@ -1,5 +1,6 @@ /** * (C) 2018 Jack Lloyd +* (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -8,10 +9,157 @@ #include <botan/pbkdf2.h> #include <botan/salsa20.h> #include <botan/loadstor.h> +#include <botan/exceptn.h> #include <botan/internal/bit_ops.h> +#include <botan/internal/timer.h> +#include <sstream> namespace Botan { +std::string Scrypt_Family::name() const + { + return "Scrypt"; + } + +std::unique_ptr<PasswordHash> Scrypt_Family::default_params() const + { + return std::unique_ptr<PasswordHash>(new Scrypt(32768, 8, 1)); + } + +std::unique_ptr<PasswordHash> Scrypt_Family::tune(size_t output_length, + std::chrono::milliseconds msec, + size_t max_memory_usage_mb) const + { + BOTAN_UNUSED(output_length); + + /* + * Some rough relations between scrypt parameters and runtime. + * Denote here by stime(N,r,p) the msec it takes to run scrypt. + * + * Emperically for smaller sizes: + * stime(N,8*r,p) / stime(N,r,p) is ~ 6-7 + * stime(N,r,8*p) / stime(N,r,8*p) is ~ 7 + * stime(2*N,r,p) / stime(N,r,p) is ~ 2 + * + * Compute stime(8192,1,1) as baseline and extrapolate + */ + + const size_t max_memory_usage = max_memory_usage_mb * 1024 * 1024; + // Starting parameters + size_t N = 8192; + size_t r = 1; + size_t p = 1; + + Timer timer("Scrypt", 0); + const std::chrono::milliseconds tune_msec(30); + + timer.run_until_elapsed(tune_msec, [&]() { + uint8_t output[32] = { 0 }; + scrypt(output, sizeof(output), "test", 4, nullptr, 0, N, r, p); + }); + + // 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<uint64_t>(1000000); + + uint64_t est_nsec = measured_time; + + // First move increase r by 8x if possible + + if(max_memory_usage == 0 || scrypt_memory_usage(N, r, p)*8 < max_memory_usage) + { + if(target_nsec / est_nsec >= 5) + { + r *= 8; + est_nsec *= 5; + } + } + + // Now double N as many times as we can + + while(max_memory_usage == 0 || scrypt_memory_usage(N, r, p)*2 < max_memory_usage) + { + if(target_nsec / est_nsec >= 2) + { + N *= 2; + est_nsec *= 2; + } + else + break; + } + + // If we have extra runtime budget, increment p + + if(target_nsec / est_nsec > 2) + p *= std::min<size_t>(1024, (target_nsec / est_nsec)); + + return std::unique_ptr<PasswordHash>(new Scrypt(N, r, p)); + } + +std::unique_ptr<PasswordHash> Scrypt_Family::from_params(size_t N, size_t r, size_t p) const + { + return std::unique_ptr<PasswordHash>(new Scrypt(N, r, p)); + } + +std::unique_ptr<PasswordHash> Scrypt_Family::from_iterations(size_t iter) const + { + const size_t r = 8; + const size_t p = 1; + + size_t N = 8192; + + if(iter > 50000) + N = 16384; + if(iter > 100000) + N = 32768; + if(iter > 150000) + N = 65536; + + return std::unique_ptr<PasswordHash>(new Scrypt(N, r, p)); + } + +Scrypt::Scrypt(size_t N, size_t r, size_t p) : + m_N(N), m_r(r), m_p(p) + { + if(!is_power_of_2(N)) + throw Invalid_Argument("Scrypt N parameter must be a power of 2"); + + if(p == 0 || p > 1024) + throw Invalid_Argument("Invalid or unsupported scrypt p"); + if(r == 0 || r > 256) + throw Invalid_Argument("Invalid or unsupported scrypt r"); + if(N < 1 || N > 4194304) + throw Invalid_Argument("Invalid or unsupported scrypt N"); + } + +std::string Scrypt::to_string() const + { + std::ostringstream oss; + oss << "Scrypt(" << m_N << "," << m_r << "," << m_p << ")"; + return oss.str(); + } + +size_t Scrypt::total_memory_usage() const + { + return scrypt_memory_usage(m_N, m_r, m_p); + } + +void Scrypt::derive_key(uint8_t output[], size_t output_len, + const char* password, size_t password_len, + const uint8_t salt[], size_t salt_len) const + { + scrypt(output, output_len, + password, password_len, + salt, salt_len, + N(), r(), p()); + } + namespace { void scryptBlockMix(size_t r, uint8_t* B, uint8_t* Y) @@ -60,101 +208,31 @@ void scryptROMmix(size_t r, size_t N, uint8_t* B, secure_vector<uint8_t>& V) } -Scrypt_Params::Scrypt_Params(size_t N, size_t r, size_t p) : - m_N(N), m_r(r), m_p(p) - { - BOTAN_ARG_CHECK(p <= 128, "Invalid scrypt p"); - BOTAN_ARG_CHECK(N <= 4194304 && is_power_of_2(N), "Invalid scrypt N"); - BOTAN_ARG_CHECK(r <= 64, "Invalid scrypt r"); - } - -Scrypt_Params::Scrypt_Params(std::chrono::milliseconds msec) - { - /* - This mapping is highly subjective and machine specific - For simplicity we fix r=8 and p=4 - */ - m_r = 8; - m_p = 4; - - if(msec.count() <= 10) - { - m_N = 4096; - m_p = 1; - } - else if(msec.count() <= 50) - { - m_N = 8192; - } - else if(msec.count() <= 100) - { - m_N = 16384; - } - else if(msec.count() <= 500) - { - m_N = 32768; - } - else - { - m_N = 65536; - } - } - -Scrypt_Params::Scrypt_Params(size_t iterations) - { - // This mapping is highly subjective and machine specific - m_r = 8; - m_p = 4; - - if(iterations < 1000) - { - m_N = 8192; - } - else if(iterations < 5000) - { - m_N = 16384; - } - else if(iterations < 10000) - { - m_N = 32768; - } - else - { - m_N = 65536; - } - } - -void scrypt(uint8_t output[], size_t output_len, - const std::string& password, - const uint8_t salt[], size_t salt_len, - const Scrypt_Params& params) - { - scrypt(output, output_len, password, salt, salt_len, - params.N(), params.r(), params.p()); - } - void scrypt(uint8_t output[], size_t output_len, - const std::string& password, + const char* password, size_t password_len, const uint8_t salt[], size_t salt_len, size_t N, size_t r, size_t p) { - // Upper bounds here are much lower than scrypt maximums yet seem sufficient - BOTAN_ARG_CHECK(p <= 128, "Invalid scrypt p"); - BOTAN_ARG_CHECK(N <= 4194304 && is_power_of_2(N), "Invalid scrypt N"); - BOTAN_ARG_CHECK(r <= 64, "Invalid scrypt r"); - const size_t S = 128 * r; secure_vector<uint8_t> B(p * S); + // temp space + secure_vector<uint8_t> V((N+1) * S); - PKCS5_PBKDF2 pbkdf2(MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)").release()); + auto hmac_sha256 = MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); - pbkdf2.pbkdf(B.data(), B.size(), - password, - salt, salt_len, - 1, std::chrono::milliseconds(0)); + try + { + hmac_sha256->set_key(cast_char_ptr_to_uint8(password), password_len); + } + catch(Invalid_Key_Length&) + { + throw Exception("Scrypt cannot accept passphrases of the provided length"); + } - // temp space - secure_vector<uint8_t> V((N+1) * S); + pbkdf2(*hmac_sha256.get(), + B.data(), B.size(), + salt, salt_len, + 1); // these can be parallel for(size_t i = 0; i != p; ++i) @@ -162,10 +240,10 @@ void scrypt(uint8_t output[], size_t output_len, scryptROMmix(r, N, &B[128*r*i], V); } - pbkdf2.pbkdf(output, output_len, - password, - B.data(), B.size(), - 1, std::chrono::milliseconds(0)); + pbkdf2(*hmac_sha256.get(), + output, output_len, + B.data(), B.size(), + 1); } } diff --git a/src/lib/pbkdf/scrypt/scrypt.h b/src/lib/pbkdf/scrypt/scrypt.h index 199caa4c8..42d0f0a3b 100644 --- a/src/lib/pbkdf/scrypt/scrypt.h +++ b/src/lib/pbkdf/scrypt/scrypt.h @@ -1,5 +1,6 @@ /** * (C) 2018 Jack Lloyd +* (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -7,30 +8,87 @@ #ifndef BOTAN_SCRYPT_H_ #define BOTAN_SCRYPT_H_ -#include <botan/types.h> -#include <chrono> -#include <string> +#include <botan/pwdhash.h> namespace Botan { -class Scrypt_Params +/** +* Scrypt key derivation function (RFC 7914) +*/ +class BOTAN_PUBLIC_API(2,8) Scrypt final : public PasswordHash { public: - Scrypt_Params(size_t N, size_t r, size_t p); + Scrypt(size_t N, size_t r, size_t p); - Scrypt_Params(std::chrono::milliseconds msec); + Scrypt(const Scrypt& other) = default; + Scrypt& operator=(const Scrypt&) = default; - Scrypt_Params(size_t iterations); + /** + * Derive a new key under the current Scrypt parameter set + */ + void derive_key(uint8_t out[], size_t out_len, + const char* password, const size_t password_len, + const uint8_t salt[], size_t salt_len) const override; + + std::string to_string() const override; size_t N() const { return m_N; } size_t r() const { return m_r; } size_t p() const { return m_p; } + + size_t iterations() const override { return r(); } + + size_t parallelism() const override { return p(); } + + size_t memory_param() const override { return N(); } + + size_t total_memory_usage() const override; + private: size_t m_N, m_r, m_p; }; +class BOTAN_PUBLIC_API(2,8) Scrypt_Family final : public PasswordHashFamily + { + public: + std::string name() const override; + + std::unique_ptr<PasswordHash> tune(size_t output_length, + std::chrono::milliseconds msec, + size_t max_memory) const override; + + std::unique_ptr<PasswordHash> default_params() const override; + + std::unique_ptr<PasswordHash> from_iterations(size_t iter) const override; + + std::unique_ptr<PasswordHash> from_params( + size_t N, size_t r, size_t p) const override; + }; + +/** +* Scrypt key derivation function (RFC 7914) +* +* @param output the output will be placed here +* @param output_len length of output +* @param password the user password +* @param salt the salt +* @param salt_len length of salt +* @param N the CPU/Memory cost parameter, must be power of 2 +* @param r the block size parameter +* @param p the parallelization parameter +* +* Suitable parameters for most uses would be N = 32768, r = 8, p = 1 +* +* Scrypt uses approximately (p + N + 1) * 128 * r bytes of memory +*/ +void BOTAN_PUBLIC_API(2,8) scrypt(uint8_t output[], size_t output_len, + const char* password, size_t password_len, + const uint8_t salt[], size_t salt_len, + size_t N, size_t r, size_t p); + /** * Scrypt key derivation function (RFC 7914) +* Before 2.8 this function was the primary interface for scrypt * * @param output the output will be placed here * @param output_len length of output @@ -41,19 +99,25 @@ class Scrypt_Params * @param r the block size parameter * @param p the parallelization parameter * -* Suitable parameters for most uses would be N = 16384, r = 8, p = 1 +* Suitable parameters for most uses would be N = 32768, r = 8, p = 1 * * Scrypt uses approximately (p + N + 1) * 128 * r bytes of memory */ -void BOTAN_UNSTABLE_API scrypt(uint8_t output[], size_t output_len, - const std::string& password, - const uint8_t salt[], size_t salt_len, - size_t N, size_t r, size_t p); - -void BOTAN_UNSTABLE_API scrypt(uint8_t output[], size_t output_len, - const std::string& password, - const uint8_t salt[], size_t salt_len, - const Scrypt_Params& params); +inline void scrypt(uint8_t output[], size_t output_len, + const std::string& password, + const uint8_t salt[], size_t salt_len, + size_t N, size_t r, size_t p) + { + return scrypt(output, output_len, + password.c_str(), password.size(), + salt, salt_len, + N, r, p); + } + +inline size_t scrypt_memory_usage(size_t N, size_t r, size_t p) + { + return 128 * r * (N + p); + } } diff --git a/src/lib/pubkey/pbes2/pbes2.cpp b/src/lib/pubkey/pbes2/pbes2.cpp index fce225e99..263263843 100644 --- a/src/lib/pubkey/pbes2/pbes2.cpp +++ b/src/lib/pubkey/pbes2/pbes2.cpp @@ -1,6 +1,7 @@ /* * PKCS #5 PBES2 * (C) 1999-2008,2014 Jack Lloyd +* (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -51,13 +52,11 @@ SymmetricKey derive_key(const std::string& passphrase, if(salt.size() < 8) throw Decoding_Error("PBE-PKCS5 v2.0: Encoded salt is too small"); - const std::string prf = OIDS::lookup(prf_algo.get_oid()); - - std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")")); - if(key_length == 0) key_length = default_key_size; + const std::string prf = OIDS::lookup(prf_algo.get_oid()); + std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")")); return pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations); } #if defined(BOTAN_HAS_SCRYPT) @@ -106,24 +105,39 @@ secure_vector<uint8_t> derive_key(const std::string& passphrase, { #if defined(BOTAN_HAS_SCRYPT) - Scrypt_Params params(32768, 8, 4); + std::unique_ptr<PasswordHashFamily> pwhash_fam = PasswordHashFamily::create_or_throw("Scrypt"); + + std::unique_ptr<PasswordHash> pwhash; if(msec_in_iterations_out) - params = Scrypt_Params(std::chrono::milliseconds(*msec_in_iterations_out)); + { + const std::chrono::milliseconds msec(*msec_in_iterations_out); + pwhash = pwhash_fam->tune(key_length, msec); + } else - params = Scrypt_Params(iterations_if_msec_null); + { + pwhash = pwhash_fam->from_iterations(iterations_if_msec_null); + } secure_vector<uint8_t> key(key_length); - scrypt(key.data(), key.size(), passphrase, - salt.data(), salt.size(), params); + pwhash->derive_key(key.data(), key.size(), + passphrase.c_str(), passphrase.size(), + salt.data(), salt.size()); + + const size_t N = pwhash->memory_param(); + const size_t r = pwhash->iterations(); + const size_t p = pwhash->parallelism(); + + if(msec_in_iterations_out) + *msec_in_iterations_out = 0; std::vector<uint8_t> scrypt_params; DER_Encoder(scrypt_params) .start_cons(SEQUENCE) .encode(salt, OCTET_STRING) - .encode(params.N()) - .encode(params.r()) - .encode(params.p()) + .encode(N) + .encode(r) + .encode(p) .encode(key_length) .end_cons(); @@ -136,26 +150,36 @@ secure_vector<uint8_t> derive_key(const std::string& passphrase, else { const std::string prf = "HMAC(" + digest + ")"; + const std::string pbkdf_name = "PBKDF2(" + prf + ")"; - std::unique_ptr<PBKDF> pbkdf(get_pbkdf("PBKDF2(" + prf + ")")); - - size_t iterations = iterations_if_msec_null; + std::unique_ptr<PasswordHashFamily> pwhash_fam = PasswordHashFamily::create(pbkdf_name); + if(!pwhash_fam) + throw Invalid_Argument("Unknown password hash digest " + digest); - secure_vector<uint8_t> key; + std::unique_ptr<PasswordHash> pwhash; if(msec_in_iterations_out) { - std::chrono::milliseconds msec(*msec_in_iterations_out); - key = pbkdf->derive_key(key_length, passphrase, salt.data(), salt.size(), msec, iterations).bits_of(); - *msec_in_iterations_out = iterations; + const std::chrono::milliseconds msec(*msec_in_iterations_out); + pwhash = pwhash_fam->tune(key_length, msec); } else { - key = pbkdf->pbkdf_iterations(key_length, passphrase, salt.data(), salt.size(), iterations); + pwhash = pwhash_fam->from_iterations(iterations_if_msec_null); } + secure_vector<uint8_t> key(key_length); + pwhash->derive_key(key.data(), key.size(), + passphrase.c_str(), passphrase.size(), + salt.data(), salt.size()); + std::vector<uint8_t> pbkdf2_params; + const size_t iterations = pwhash->iterations(); + + if(msec_in_iterations_out) + *msec_in_iterations_out = iterations; + DER_Encoder(pbkdf2_params) .start_cons(SEQUENCE) .encode(salt, OCTET_STRING) diff --git a/src/lib/utils/info.txt b/src/lib/utils/info.txt index fb9325f93..93ae795ef 100644 --- a/src/lib/utils/info.txt +++ b/src/lib/utils/info.txt @@ -1,5 +1,5 @@ <defines> -UTIL_FUNCTIONS -> 20171003 +UTIL_FUNCTIONS -> 20180903 </defines> load_on always @@ -35,6 +35,7 @@ prefetch.h rounding.h safeint.h stl_util.h +timer.h </header:internal> <requires> diff --git a/src/lib/utils/timer.cpp b/src/lib/utils/timer.cpp new file mode 100644 index 000000000..5d64e63fb --- /dev/null +++ b/src/lib/utils/timer.cpp @@ -0,0 +1,118 @@ +/* +* (C) 2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/internal/timer.h> +#include <algorithm> +#include <sstream> +#include <iomanip> + +namespace Botan { + +void Timer::stop() + { + if(m_timer_start) + { + const uint64_t now = Timer::get_system_timestamp_ns(); + + if(now > m_timer_start) + { + uint64_t dur = now - m_timer_start; + + m_time_used += dur; + + if(m_cpu_cycles_start != 0) + { + uint64_t cycles_taken = Timer::get_cpu_cycle_counter() - m_cpu_cycles_start; + if(cycles_taken > 0) + { + m_cpu_cycles_used += static_cast<size_t>(cycles_taken * m_clock_cycle_ratio); + } + } + + if(m_event_count == 0) + { + m_min_time = m_max_time = dur; + } + else + { + m_max_time = std::max(m_max_time, dur); + m_min_time = std::min(m_min_time, dur); + } + } + + m_timer_start = 0; + ++m_event_count; + } + } + +std::string Timer::result_string_bps() const + { + const size_t MiB = 1024 * 1024; + + const double MiB_total = static_cast<double>(events()) / MiB; + const double MiB_per_sec = MiB_total / seconds(); + + std::ostringstream oss; + oss << get_name(); + + if(!doing().empty()) + { + oss << " " << doing(); + } + + if(buf_size() > 0) + { + oss << " buffer size " << buf_size() << " bytes:"; + } + + if(events() == 0) + oss << " " << "N/A"; + else + oss << " " << std::fixed << std::setprecision(3) << MiB_per_sec << " MiB/sec"; + + if(cycles_consumed() != 0) + { + const double cycles_per_byte = static_cast<double>(cycles_consumed()) / events(); + oss << " " << std::fixed << std::setprecision(2) << cycles_per_byte << " cycles/byte"; + } + + oss << " (" << MiB_total << " MiB in " << milliseconds() << " ms)\n"; + + return oss.str(); + } + +std::string Timer::result_string_ops() const + { + std::ostringstream oss; + + oss << get_name() << " "; + + if(events() == 0) + { + oss << "no events\n"; + } + else + { + oss << static_cast<uint64_t>(events_per_second()) + << ' ' << doing() << "/sec; " + << std::setprecision(2) << std::fixed + << ms_per_event() << " ms/op"; + + if(cycles_consumed() != 0) + { + const double cycles_per_op = static_cast<double>(cycles_consumed()) / events(); + const size_t precision = (cycles_per_op < 10000) ? 2 : 0; + oss << " " << std::fixed << std::setprecision(precision) << cycles_per_op << " cycles/op"; + } + + oss << " (" << events() << " " << (events() == 1 ? "op" : "ops") + << " in " << milliseconds() << " ms)\n"; + } + + return oss.str(); + } + +} diff --git a/src/lib/utils/timer.h b/src/lib/utils/timer.h new file mode 100644 index 000000000..6a6b807db --- /dev/null +++ b/src/lib/utils/timer.h @@ -0,0 +1,216 @@ +/* +* (C) 2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_TIMER_H_ +#define BOTAN_TIMER_H_ + +#include <botan/types.h> +#include <botan/internal/os_utils.h> +#include <string> +#include <chrono> + +namespace Botan { + +class BOTAN_TEST_API Timer final + { + public: + Timer(const std::string& name, + const std::string& provider, + const std::string& doing, + uint64_t event_mult, + size_t buf_size, + double clock_cycle_ratio, + uint64_t clock_speed) + : m_name(name + ((provider.empty() || provider == "base") ? "" : " [" + provider + "]")) + , m_doing(doing) + , m_buf_size(buf_size) + , m_event_mult(event_mult) + , m_clock_cycle_ratio(clock_cycle_ratio) + , m_clock_speed(clock_speed) + {} + + Timer(const std::string& name, size_t buf_size = 0) : + Timer(name, "", "", 1, buf_size, 0.0, 0.0) + {} + + Timer(const Timer& other) = default; + + static uint64_t get_system_timestamp_ns() + { + return Botan::OS::get_system_timestamp_ns(); + } + + static uint64_t get_cpu_cycle_counter() + { + return Botan::OS::get_processor_timestamp(); + } + + void start() + { + stop(); + m_timer_start = Timer::get_system_timestamp_ns(); + m_cpu_cycles_start = Timer::get_cpu_cycle_counter(); + } + + void stop(); + + bool under(std::chrono::milliseconds msec) + { + return (milliseconds() < msec.count()); + } + + class Timer_Scope final + { + public: + explicit Timer_Scope(Timer& timer) + : m_timer(timer) + { + m_timer.start(); + } + ~Timer_Scope() + { + try + { + m_timer.stop(); + } + catch(...) {} + } + private: + Timer& m_timer; + }; + + template<typename F> + auto run(F f) -> decltype(f()) + { + Timer_Scope timer(*this); + return f(); + } + + template<typename F> + void run_until_elapsed(std::chrono::milliseconds msec, F f) + { + while(this->under(msec)) + { + run(f); + } + } + + uint64_t value() const + { + return m_time_used; + } + + double seconds() const + { + return milliseconds() / 1000.0; + } + + double milliseconds() const + { + return value() / 1000000.0; + } + + double ms_per_event() const + { + return milliseconds() / events(); + } + + uint64_t cycles_consumed() const + { + if(m_clock_speed != 0) + { + return (static_cast<double>(m_clock_speed) * value()) / 1000; + } + return m_cpu_cycles_used; + } + + uint64_t events() const + { + return m_event_count * m_event_mult; + } + + const std::string& get_name() const + { + return m_name; + } + + const std::string& doing() const + { + return m_doing; + } + + size_t buf_size() const + { + return m_buf_size; + } + + double bytes_per_second() const + { + return seconds() > 0.0 ? events() / seconds() : 0.0; + } + + double events_per_second() const + { + return seconds() > 0.0 ? events() / seconds() : 0.0; + } + + double seconds_per_event() const + { + return events() > 0 ? seconds() / events() : 0.0; + } + + void set_custom_msg(const std::string& s) + { + m_custom_msg = s; + } + + bool operator<(const Timer& other) const + { + if(this->doing() != other.doing()) + return (this->doing() < other.doing()); + + return (this->get_name() < other.get_name()); + } + + std::string to_string() const + { + if(m_custom_msg.size() > 0) + { + return m_custom_msg; + } + else if(this->buf_size() == 0) + { + return result_string_ops(); + } + else + { + return result_string_bps(); + } + } + + private: + std::string result_string_bps() const; + std::string result_string_ops() const; + + // const data + std::string m_name, m_doing; + size_t m_buf_size; + uint64_t m_event_mult; + double m_clock_cycle_ratio; + uint64_t m_clock_speed; + + // set at runtime + std::string m_custom_msg; + uint64_t m_time_used = 0, m_timer_start = 0; + uint64_t m_event_count = 0; + + uint64_t m_max_time = 0, m_min_time = 0; + uint64_t m_cpu_cycles_start = 0, m_cpu_cycles_used = 0; + }; + +} + +#endif diff --git a/src/scripts/test_cli.py b/src/scripts/test_cli.py index 2f5e5bba4..d38a990d9 100755 --- a/src/scripts/test_cli.py +++ b/src/scripts/test_cli.py @@ -46,8 +46,6 @@ def test_cli(cmd, cmd_options, expected_output=None, cmd_input=None): TESTS_RUN += 1 - logging.debug("Running %s %s", cmd, cmd_options) - fixed_drbg_seed = "802" * 32 opt_list = [] @@ -60,6 +58,8 @@ def test_cli(cmd, cmd_options, expected_output=None, cmd_input=None): drbg_options = ['--rng-type=drbg', '--drbg-seed=' + fixed_drbg_seed] cmdline = [CLI_PATH, cmd] + drbg_options + opt_list + logging.debug("Executing '%s'" % (' '.join([CLI_PATH, cmd] + opt_list))) + stdout = None stderr = None @@ -283,6 +283,26 @@ mlLtJ5JvZ0/p6zP3x+Y9yPIrAR8L/acG5ItSrAKXzzuqQQZMv4aN shutil.rmtree(tmp_dir) +def cli_pbkdf_tune_tests(): + if not check_for_command("pbkdf_tune"): + return + + expected = re.compile(r'For (default|[1-9][0-9]*) ms selected Scrypt\([0-9]+,[0-9]+,[0-9]+\) using [0-9]+ MiB') + + output = test_cli("pbkdf_tune", ["--check", "1", "10", "50", "default"], None).split('\n') + + for line in output: + if expected.match(line) is None: + logging.error("Unexpected line '%s'" % (line)) + + expected_pbkdf2 = re.compile(r'For (default|[1-9][0-9]*) ms selected PBKDF2\(HMAC\(SHA-256\),[0-9]+\)') + + output = test_cli("pbkdf_tune", ["--algo=PBKDF2(SHA-256)", "--check", "1", "10", "50", "default"], None).split('\n') + + for line in output: + if expected_pbkdf2.match(line) is None: + logging.error("Unexpected line '%s'" % (line)) + def cli_psk_db_tests(): if not check_for_command("psk_get"): return @@ -601,7 +621,7 @@ def cli_speed_tests(): output = test_cli("speed", ["--msec=%d" % (msec), "scrypt"], None).split('\n') - format_re = re.compile(r'^scrypt-[0-9]+-[0-9]+-[0-9]+ [0-9]+ /sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9]+ ms\)') + format_re = re.compile(r'^scrypt-[0-9]+-[0-9]+-[0-9]+ \([0-9]+ MiB\) [0-9]+ /sec; [0-9]+\.[0-9]+ ms/op .*\([0-9]+ (op|ops) in [0-9]+ ms\)') for line in output: if format_re.match(line) is None: @@ -688,6 +708,7 @@ def main(args=None): cli_pk_encrypt_tests, cli_pk_workfactor_tests, cli_psk_db_tests, + cli_pbkdf_tune_tests, cli_rng_tests, cli_speed_tests, cli_timing_test_tests, @@ -701,7 +722,7 @@ def main(args=None): fn_name = fn.__name__ if test_regex is not None: - if test_regex.match(fn_name) is None: + if test_regex.search(fn_name) is None: continue start = time.time() diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index c409651a9..6687697cb 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -2,6 +2,7 @@ * (C) 2015 Jack Lloyd * (C) 2016 RenĂ© Korthaus * (C) 2018 Ribose Inc, Krzysztof Kwiatkowski +* (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -928,6 +929,16 @@ class FFI_Unit_Tests final : public Test TEST_FFI_OK(botan_scrypt, (output.data(), output.size(), pass, salt, sizeof(salt), 8, 1, 1)); result.test_eq("scrypt output", output, "4B9B888D695288E002CC4F9D90808A4D296A45CE4471AFBB"); + + size_t N, r, p; + TEST_FFI_OK(botan_pwdhash_timed, ("Scrypt", 50, &r, &p, &N, output.data(), output.size(), + "bunny", 5, salt, sizeof(salt))); + + std::vector<uint8_t> cmp(output.size()); + + TEST_FFI_OK(botan_pwdhash, ("Scrypt", N, r, p, cmp.data(), cmp.size(), + "bunny", 5, salt, sizeof(salt))); + result.test_eq("recomputed scrypt", cmp, output); #else TEST_FFI_RC(BOTAN_FFI_ERROR_NOT_IMPLEMENTED, botan_scrypt, (output.data(), output.size(), pass, salt, sizeof(salt), 8, 1, 1)); @@ -1483,11 +1494,26 @@ class FFI_Unit_Tests final : public Test const uint32_t pbkdf_msec = 100; size_t pbkdf_iters_out = 0; +#if defined(BOTAN_HAS_SCRYPT) + const std::string pbe_hash = "Scrypt"; +#else + const std::string pbe_hash = "SHA-512"; +#endif + TEST_FFI_OK(botan_privkey_export_encrypted_pbkdf_msec, (priv, privkey.data(), &privkey_len, rng, "password", - pbkdf_msec, &pbkdf_iters_out, "AES-256/GCM", "SHA-512", 0)); - // PBKDF2 currently always rounds to multiple of 10,000 - result.test_gte("Reasonable KDF iters", pbkdf_iters_out, 1000); + pbkdf_msec, &pbkdf_iters_out, "AES-256/GCM", pbe_hash.c_str(), 0)); + + if(pbe_hash == "Scrypt") + { + result.test_eq("Scrypt iters set to zero in this API", pbkdf_iters_out, 0); + } + else + { + // PBKDF2 currently always rounds to multiple of 10,000 + result.test_eq("Expected PBKDF2 iters", pbkdf_iters_out % 10000, 0); + } + privkey.resize(privkey_len); TEST_FFI_OK(botan_privkey_load, (©, rng, privkey.data(), privkey.size(), "password")); diff --git a/src/tests/test_pbkdf.cpp b/src/tests/test_pbkdf.cpp index eb344b2ea..20a25165b 100644 --- a/src/tests/test_pbkdf.cpp +++ b/src/tests/test_pbkdf.cpp @@ -1,5 +1,6 @@ /* * (C) 2014,2015 Jack Lloyd +* (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -8,6 +9,7 @@ #if defined(BOTAN_HAS_PBKDF) #include <botan/pbkdf.h> + #include <botan/pwdhash.h> #endif #if defined(BOTAN_HAS_PGP_S2K) @@ -49,9 +51,25 @@ class PBKDF_KAT_Tests final : public Text_Based_Test const Botan::secure_vector<uint8_t> derived = pbkdf->derive_key(outlen, passphrase, salt.data(), salt.size(), iterations).bits_of(); - result.test_eq("derived key", derived, expected); + auto pwdhash_fam = Botan::PasswordHashFamily::create(pbkdf_name); + + if(!pwdhash_fam) + { + result.note_missing("No PasswordHashFamily for " + pbkdf_name); + return result; + } + + auto pwdhash = pwdhash_fam->from_params(iterations); + + std::vector<uint8_t> pwdhash_derived(outlen); + pwdhash->derive_key(pwdhash_derived.data(), pwdhash_derived.size(), + passphrase.c_str(), passphrase.size(), + salt.data(), salt.size()); + + result.test_eq("pwdhash derived key", pwdhash_derived, expected); + return result; } @@ -59,6 +77,65 @@ class PBKDF_KAT_Tests final : public Text_Based_Test BOTAN_REGISTER_TEST("pbkdf", PBKDF_KAT_Tests); +class Pwdhash_Tests : public Test + { + public: + std::vector<Test::Result> run() override + { + std::vector<Test::Result> results; + + for(std::string pwdhash : { "Scrypt", "PBKDF2(SHA-256)", "OpenPGP-S2K(SHA-384)"}) + { + Test::Result result("Pwdhash " + pwdhash); + auto pwdhash_fam = Botan::PasswordHashFamily::create(pwdhash); + + if(pwdhash_fam) + { + const std::vector<uint8_t> salt(8); + const std::string password = "test"; + + auto pwdhash_tune = pwdhash_fam->tune(32, std::chrono::milliseconds(50)); + + std::vector<uint8_t> output1(32); + pwdhash_tune->derive_key(output1.data(), output1.size(), + password.c_str(), password.size(), + salt.data(), salt.size()); + + std::unique_ptr<Botan::PasswordHash> pwhash; + + if(pwdhash_fam->name() == "Scrypt") + { + pwhash = pwdhash_fam->from_params(pwdhash_tune->memory_param(), + pwdhash_tune->iterations(), + pwdhash_tune->parallelism()); + } + else + { + pwhash = pwdhash_fam->from_params(pwdhash_tune->iterations()); + } + + std::vector<uint8_t> output2(32); + pwdhash_tune->derive_key(output2.data(), output2.size(), + password.c_str(), password.size(), + salt.data(), salt.size()); + + result.test_eq("PasswordHash produced same output when run with same params", + output1, output2); + + + //auto pwdhash_tuned = pwdhash_fam->tune(32, std::chrono::milliseconds(150)); + + } + + results.push_back(result); + } + + return results; + } + }; + +BOTAN_REGISTER_TEST("pwdhash", Pwdhash_Tests); + #endif #if defined(BOTAN_HAS_SCRYPT) @@ -89,6 +166,23 @@ class Scrypt_KAT_Tests final : public Text_Based_Test result.test_eq("derived key", output, expected); + auto pwdhash_fam = Botan::PasswordHashFamily::create("Scrypt"); + + if(!pwdhash_fam) + { + result.test_failure("Scrypt is missing PasswordHashFamily"); + return result; + } + + auto pwdhash = pwdhash_fam->from_params(N, R, P); + + std::vector<uint8_t> pwdhash_derived(expected.size()); + pwdhash->derive_key(pwdhash_derived.data(), pwdhash_derived.size(), + passphrase.c_str(), passphrase.size(), + salt.data(), salt.size()); + + result.test_eq("pwdhash derived key", pwdhash_derived, expected); + return result; } @@ -111,13 +205,13 @@ class PGP_S2K_Iter_Test final : public Test const size_t max_iter = 65011712; result.test_eq("Encoding of large value accepted", - Botan::OpenPGP_S2K::encode_count(max_iter * 2), size_t(255)); + Botan::RFC4880_encode_count(max_iter * 2), size_t(255)); result.test_eq("Encoding of small value accepted", - Botan::OpenPGP_S2K::encode_count(0), size_t(0)); + Botan::RFC4880_encode_count(0), size_t(0)); for(size_t c = 0; c != 256; ++c) { - const size_t dec = Botan::OpenPGP_S2K::decode_count(static_cast<uint8_t>(c)); + const size_t dec = Botan::RFC4880_decode_count(static_cast<uint8_t>(c)); const size_t comp_dec = (16 + (c & 0x0F)) << ((c >> 4) + 6); result.test_eq("Decoded value matches PGP formula", dec, comp_dec); } @@ -126,14 +220,14 @@ class PGP_S2K_Iter_Test final : public Test for(size_t i = 0; i <= max_iter; i += 64) { - const uint8_t enc = Botan::OpenPGP_S2K::encode_count(i); + const uint8_t enc = Botan::RFC4880_encode_count(i); result.test_lte("Encoded value non-decreasing", last_enc, enc); /* The iteration count as encoded may not be exactly the value requested, but should never be less */ - const size_t dec = Botan::OpenPGP_S2K::decode_count(enc); + const size_t dec = Botan::RFC4880_decode_count(enc); result.test_gte("Decoded value is >= requested", dec, i); last_enc = enc; |