aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/pbkdf
diff options
context:
space:
mode:
authorJack Lloyd <[email protected]>2019-05-30 19:19:06 -0400
committerJack Lloyd <[email protected]>2019-05-30 19:19:06 -0400
commit1b44def2aec87601cd9ffbd2362ab59127bfc2ca (patch)
tree1fe1ca47c79d499bff621fc3816973458d7466bd /src/lib/pbkdf
parent22440b7644c9ff90610cb5db3dfcaf931aff34af (diff)
parentbbc6fc46949c4db5ecd0ef720b32b8fa90ed9a8d (diff)
Merge GH #1987 Argon2 improvements
Diffstat (limited to 'src/lib/pbkdf')
-rw-r--r--src/lib/pbkdf/argon2/argon2.h78
-rw-r--r--src/lib/pbkdf/argon2/argon2fmt.cpp125
-rw-r--r--src/lib/pbkdf/argon2/argon2pwhash.cpp151
-rw-r--r--src/lib/pbkdf/pwdhash.cpp19
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)
{