diff options
author | Jack Lloyd <[email protected]> | 2019-05-30 19:19:06 -0400 |
---|---|---|
committer | Jack Lloyd <[email protected]> | 2019-05-30 19:19:06 -0400 |
commit | 1b44def2aec87601cd9ffbd2362ab59127bfc2ca (patch) | |
tree | 1fe1ca47c79d499bff621fc3816973458d7466bd /src/lib/pbkdf | |
parent | 22440b7644c9ff90610cb5db3dfcaf931aff34af (diff) | |
parent | bbc6fc46949c4db5ecd0ef720b32b8fa90ed9a8d (diff) |
Merge GH #1987 Argon2 improvements
Diffstat (limited to 'src/lib/pbkdf')
-rw-r--r-- | src/lib/pbkdf/argon2/argon2.h | 78 | ||||
-rw-r--r-- | src/lib/pbkdf/argon2/argon2fmt.cpp | 125 | ||||
-rw-r--r-- | src/lib/pbkdf/argon2/argon2pwhash.cpp | 151 | ||||
-rw-r--r-- | src/lib/pbkdf/pwdhash.cpp | 19 |
4 files changed, 371 insertions, 2 deletions
diff --git a/src/lib/pbkdf/argon2/argon2.h b/src/lib/pbkdf/argon2/argon2.h index 27a6a3220..e0fe1d83d 100644 --- a/src/lib/pbkdf/argon2/argon2.h +++ b/src/lib/pbkdf/argon2/argon2.h @@ -1,5 +1,5 @@ /** -* (C) 2018 Jack Lloyd +* (C) 2018,2019 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ @@ -7,10 +7,70 @@ #ifndef BOTAN_ARGON2_H_ #define BOTAN_ARGON2_H_ -#include <botan/types.h> +#include <botan/pwdhash.h> namespace Botan { +class RandomNumberGenerator; + +/** +* Argon2 key derivation function +*/ +class BOTAN_PUBLIC_API(2,11) Argon2 final : public PasswordHash + { + public: + Argon2(uint8_t family, size_t M, size_t t, size_t p); + + Argon2(const Argon2& other) = default; + Argon2& operator=(const Argon2&) = default; + + /** + * Derive a new key under the current Argon2 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 M() const { return m_M; } + size_t t() const { return m_t; } + size_t p() const { return m_p; } + + size_t iterations() const override { return t(); } + + size_t parallelism() const override { return p(); } + + size_t memory_param() const override { return M(); } + + size_t total_memory_usage() const override { return M() * 1024; } + + private: + uint8_t m_family; + size_t m_M, m_t, m_p; + }; + +class BOTAN_PUBLIC_API(2,11) Argon2_Family final : public PasswordHashFamily + { + public: + Argon2_Family(uint8_t family); + + 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 M, size_t t, size_t p) const override; + private: + const uint8_t m_family; + }; + /** * Argon2 key derivation function * @@ -31,6 +91,20 @@ void BOTAN_PUBLIC_API(2,11) argon2(uint8_t output[], size_t output_len, const uint8_t ad[], size_t ad_len, size_t y, size_t p, size_t M, size_t t); +std::string BOTAN_PUBLIC_API(2,11) + argon2_generate_pwhash(const char* password, size_t password_len, + RandomNumberGenerator& rng, + size_t p, size_t M, size_t t, + size_t y = 2, size_t salt_len = 16, size_t output_len = 32); + +/** +* Check a previously created password hash +* @param password the password to check against +* @param hash the stored hash to check against +*/ +bool BOTAN_PUBLIC_API(2,11) argon2_check_pwhash(const char* password, size_t password_len, + const std::string& hash); + } #endif diff --git a/src/lib/pbkdf/argon2/argon2fmt.cpp b/src/lib/pbkdf/argon2/argon2fmt.cpp new file mode 100644 index 000000000..f64cd2bbd --- /dev/null +++ b/src/lib/pbkdf/argon2/argon2fmt.cpp @@ -0,0 +1,125 @@ +/** +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/argon2.h> +#include <botan/rng.h> +#include <botan/base64.h> +#include <botan/parsing.h> +#include <sstream> + +namespace Botan { + +namespace { + +std::string strip_padding(std::string s) + { + while(s.size() > 0 && s[s.size()-1] == '=') + s.resize(s.size() - 1); + return s; + } + +} + +std::string argon2_generate_pwhash(const char* password, size_t password_len, + RandomNumberGenerator& rng, + size_t p, size_t M, size_t t, + size_t y, size_t salt_len, size_t output_len) + { + std::vector<uint8_t> salt(salt_len); + rng.randomize(salt.data(), salt.size()); + + std::vector<uint8_t> output(output_len); + argon2(output.data(), output.size(), + password, password_len, + salt.data(), salt.size(), + nullptr, 0, + nullptr, 0, + y, p, M, t); + + std::ostringstream oss; + + if(y == 0) + oss << "$argon2d$"; + else if(y == 1) + oss << "$argon2i$"; + else + oss << "$argon2id$"; + + oss << "v=19$m=" << M << ",t=" << t << ",p=" << p << "$"; + oss << strip_padding(base64_encode(salt)) << "$" << strip_padding(base64_encode(output)); + + return oss.str(); + } + +bool argon2_check_pwhash(const char* password, size_t password_len, + const std::string& input_hash) + { + const std::vector<std::string> parts = split_on(input_hash, '$'); + + if(parts.size() != 5) + return false; + + size_t family = 0; + + if(parts[0] == "argon2d") + family = 0; + else if(parts[0] == "argon2i") + family = 1; + else if(parts[0] == "argon2id") + family = 2; + else + return false; + + if(parts[1] != "v=19") + return false; + + const std::vector<std::string> params = split_on(parts[2], ','); + + if(params.size() != 3) + return false; + + size_t M = 0, t = 0, p = 0; + + for(auto param_str : params) + { + const std::vector<std::string> param = split_on(param_str, '='); + + if(param.size() != 2) + return false; + + const std::string key = param[0]; + const size_t val = to_u32bit(param[1]); + if(key == "m") + M = val; + else if(key == "t") + t = val; + else if(key == "p") + p = val; + else + return false; + } + + std::vector<uint8_t> salt(base64_decode_max_output(parts[3].size())); + salt.resize(base64_decode(salt.data(), parts[3], false)); + + std::vector<uint8_t> hash(base64_decode_max_output(parts[4].size())); + hash.resize(base64_decode(hash.data(), parts[4], false)); + + if(hash.size() < 4) + return false; + + std::vector<uint8_t> generated(hash.size()); + argon2(generated.data(), generated.size(), + password, password_len, + salt.data(), salt.size(), + nullptr, 0, + nullptr, 0, + family, p, M, t); + + return constant_time_compare(generated.data(), hash.data(), generated.size()); + } + +} diff --git a/src/lib/pbkdf/argon2/argon2pwhash.cpp b/src/lib/pbkdf/argon2/argon2pwhash.cpp new file mode 100644 index 000000000..c31c90878 --- /dev/null +++ b/src/lib/pbkdf/argon2/argon2pwhash.cpp @@ -0,0 +1,151 @@ +/** +* (C) 2019 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/argon2.h> +#include <botan/exceptn.h> +#include <botan/internal/timer.h> + +namespace Botan { + +Argon2::Argon2(uint8_t family, size_t M, size_t t, size_t p) : + m_family(family), + m_M(M), + m_t(t), + m_p(p) + {} + +void Argon2::derive_key(uint8_t output[], size_t output_len, + const char* password, const size_t password_len, + const uint8_t salt[], size_t salt_len) const + { + argon2(output, output_len, + password, password_len, + salt, salt_len, + nullptr, 0, + nullptr, 0, + m_family, m_p, m_M, m_t); + } + +namespace { + +std::string argon2_family_name(uint8_t f) + { + switch(f) + { + case 0: + return "Argon2d"; + case 1: + return "Argon2i"; + case 2: + return "Argon2id"; + default: + throw Invalid_Argument("Unknown Argon2 parameter"); + } + } + +} + +std::string Argon2::to_string() const + { + return argon2_family_name(m_family) + "(" + + std::to_string(m_M) + "," + + std::to_string(m_t) + "," + + std::to_string(m_p) + ")"; + } + +Argon2_Family::Argon2_Family(uint8_t family) : m_family(family) + { + if(m_family != 0 && m_family != 1 && m_family != 2) + throw Invalid_Argument("Unknown Argon2 family identifier"); + } + +std::string Argon2_Family::name() const + { + return argon2_family_name(m_family); + } + +std::unique_ptr<PasswordHash> Argon2_Family::tune(size_t /*output_length*/, + std::chrono::milliseconds msec, + size_t max_memory) const + { + const size_t max_kib = (max_memory == 0) ? 256*1024 : max_memory*1024; + + // Tune with a large memory otherwise we measure cache vs RAM speeds and underestimate + // costs for larger params + size_t M = 32*1024; // in KiB + size_t p = 1; + size_t t = 1; + + Timer timer("Argon2"); + const auto tune_time = BOTAN_PBKDF_TUNING_TIME; + + timer.run_until_elapsed(tune_time, [&]() { + uint8_t output[32] = { 0 }; + argon2(output, sizeof(output), "test", 4, nullptr, 0, nullptr, 0, nullptr, 0, m_family, p, M, t); + }); + + if(timer.events() == 0 || timer.value() == 0) + return default_params(); + + const uint64_t measured_time = timer.value() / timer.events(); + + const uint64_t target_nsec = msec.count() * static_cast<uint64_t>(1000000); + + /* + * 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. + */ + + uint64_t est_nsec = measured_time; + + if(est_nsec < target_nsec && M < max_kib) + { + const size_t desired_cost_increase = ((target_nsec + est_nsec - 1) / est_nsec); + const size_t mem_headroom = max_kib / M; + + const size_t M_mult = std::min(desired_cost_increase, mem_headroom); + M *= M_mult; + est_nsec *= M_mult; + } + + if(est_nsec < target_nsec) + { + const size_t desired_cost_increase = ((target_nsec + est_nsec - 1) / est_nsec); + t *= desired_cost_increase; + } + + return this->from_params(M, t, p); + } + +std::unique_ptr<PasswordHash> Argon2_Family::default_params() const + { + return this->from_params(64*1024*1024, 3, 4); + } + +std::unique_ptr<PasswordHash> Argon2_Family::from_iterations(size_t iter) const + { + /* + These choices are arbitrary, but should not change in future + releases since they will break applications expecting deterministic + mapping from iteration count to params + */ + const size_t M = iter; + const size_t t = 3; + const size_t p = 1; + return this->from_params(M, t, p); + } + +std::unique_ptr<PasswordHash> Argon2_Family::from_params(size_t M, size_t t, size_t p) const + { + return std::unique_ptr<PasswordHash>(new Argon2(m_family, M, t, p)); + } + +} diff --git a/src/lib/pbkdf/pwdhash.cpp b/src/lib/pbkdf/pwdhash.cpp index 610ae7ac7..f5ee26c6a 100644 --- a/src/lib/pbkdf/pwdhash.cpp +++ b/src/lib/pbkdf/pwdhash.cpp @@ -20,6 +20,10 @@ #include <botan/scrypt.h> #endif +#if defined(BOTAN_HAS_ARGON2) + #include <botan/argon2.h> +#endif + namespace Botan { std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string& algo_spec, @@ -52,6 +56,21 @@ std::unique_ptr<PasswordHashFamily> PasswordHashFamily::create(const std::string } #endif +#if defined(BOTAN_HAS_ARGON2) + if(req.algo_name() == "Argon2d") + { + return std::unique_ptr<PasswordHashFamily>(new Argon2_Family(0)); + } + else if(req.algo_name() == "Argon2i") + { + return std::unique_ptr<PasswordHashFamily>(new Argon2_Family(1)); + } + else if(req.algo_name() == "Argon2id") + { + return std::unique_ptr<PasswordHashFamily>(new Argon2_Family(2)); + } +#endif + #if defined(BOTAN_HAS_PGP_S2K) if(req.algo_name() == "OpenPGP-S2K" && req.arg_count() == 1) { |