aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cli/argon2.cpp78
-rw-r--r--src/cli/bcrypt.cpp4
-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
-rw-r--r--src/tests/data/passhash/argon2.vec38
-rw-r--r--src/tests/test_passhash.cpp53
8 files changed, 542 insertions, 4 deletions
diff --git a/src/cli/argon2.cpp b/src/cli/argon2.cpp
new file mode 100644
index 000000000..2b07027c3
--- /dev/null
+++ b/src/cli/argon2.cpp
@@ -0,0 +1,78 @@
+/*
+* (C) 2019 Jack Lloyd
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "cli.h"
+
+#if defined(BOTAN_HAS_ARGON2)
+ #include <botan/argon2.h>
+#endif
+
+namespace Botan_CLI {
+
+#if defined(BOTAN_HAS_ARGON2)
+
+class Generate_Argon2 final : public Command
+ {
+ public:
+ Generate_Argon2() : Command("gen_argon2 --mem=65536 --p=1 --t=1 password") {}
+
+ std::string group() const override
+ {
+ return "passhash";
+ }
+
+ std::string description() const override
+ {
+ return "Calculate Argon2 password hash";
+ }
+
+ void go() override
+ {
+ const std::string password = get_passphrase_arg("Passphrase to hash", "password");
+ const size_t M = get_arg_sz("mem");
+ const size_t p = get_arg_sz("p");
+ const size_t t = get_arg_sz("t");
+
+ output() << Botan::argon2_generate_pwhash(password.data(), password.size(), rng(), p, M, t) << "\n";
+ }
+ };
+
+BOTAN_REGISTER_COMMAND("gen_argon2", Generate_Argon2);
+
+class Check_Argon2 final : public Command
+ {
+ public:
+ Check_Argon2() : Command("check_argon2 password hash") {}
+
+ std::string group() const override
+ {
+ return "passhash";
+ }
+
+ std::string description() const override
+ {
+ return "Verify Argon2 password hash";
+ }
+
+ void go() override
+ {
+ const std::string password = get_passphrase_arg("Password to check", "password");
+ const std::string hash = get_arg("hash");
+
+ const bool ok = Botan::argon2_check_pwhash(password.data(), password.size(), hash);
+
+ output() << "Password is " << (ok ? "valid" : "NOT valid") << std::endl;
+
+ if(ok == false)
+ set_return_code(1);
+ }
+ };
+
+BOTAN_REGISTER_COMMAND("check_argon2", Check_Argon2);
+
+#endif // argon2
+
+}
diff --git a/src/cli/bcrypt.cpp b/src/cli/bcrypt.cpp
index 6d7d7124c..68e77b8e6 100644
--- a/src/cli/bcrypt.cpp
+++ b/src/cli/bcrypt.cpp
@@ -26,7 +26,7 @@ class Generate_Bcrypt final : public Command
std::string description() const override
{
- return "Calculate the bcrypt password digest of a given file";
+ return "Calculate bcrypt password hash";
}
void go() override
@@ -60,7 +60,7 @@ class Check_Bcrypt final : public Command
std::string description() const override
{
- return "Checks a given bcrypt hash against hash";
+ return "Verify bcrypt password hash";
}
void go() override
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)
{
diff --git a/src/tests/data/passhash/argon2.vec b/src/tests/data/passhash/argon2.vec
new file mode 100644
index 000000000..6de6724d5
--- /dev/null
+++ b/src/tests/data/passhash/argon2.vec
@@ -0,0 +1,38 @@
+
+[Verify]
+Password = pass
+Passhash = $argon2i$v=19$m=8,t=1,p=1$YWFhYWFhYWE$3ney028aI7naIJ/5U///1ICfSVF0Ta4jh2SpJ1jhsCE
+
+Password = pass
+Passhash = $argon2d$v=19$m=8,t=1,p=1$YWFhYWFhYWE$0WM+IC/fpCF2boiNXmu0lnBXDAKes/BHiYuq9abKsWQ
+
+Password = pass
+Passhash = $argon2id$v=19$m=8,t=1,p=1$YWFhYWFhYWE$tPAla38/iYe0rtvQKVaPv04WYar67QEGlc4fhxU185s
+
+[Generate]
+Password = pass
+Mode = 2
+M = 8
+T = 1
+P = 1
+Salt = 313233343536373839616263646566
+OutLen = 32
+Passhash = $argon2id$v=19$m=8,t=1,p=1$MTIzNDU2Nzg5YWJjZGVm$+iAchMa6urtGUvqS2c2ly5SxSb3Jj9S/nq4SZaIgLaI
+
+Password = pass
+Mode = 0
+M = 8192
+T = 3
+P = 1
+Salt = 313233343536373839616263646566
+OutLen = 32
+Passhash = $argon2d$v=19$m=8192,t=3,p=1$MTIzNDU2Nzg5YWJjZGVm$6C4pewOLgibFqWOo9mKTN2xV8KBRq7wjD8PM7DsoV0k
+
+Password = pass
+Mode = 1
+M = 8192
+T = 3
+P = 1
+Salt = 313233343536373839616263646566
+OutLen = 32
+Passhash = $argon2i$v=19$m=8192,t=3,p=1$MTIzNDU2Nzg5YWJjZGVm$7iO3QHobBZHBgjSM+u92dRHJeKpsMdbZ+sLxPjcm9MI
diff --git a/src/tests/test_passhash.cpp b/src/tests/test_passhash.cpp
index b6bd268b1..0b39e7ffc 100644
--- a/src/tests/test_passhash.cpp
+++ b/src/tests/test_passhash.cpp
@@ -14,6 +14,11 @@
#include <botan/passhash9.h>
#endif
+#if defined(BOTAN_HAS_ARGON2)
+ #include <botan/argon2.h>
+ #include "test_rng.h"
+#endif
+
namespace Botan_Tests {
namespace {
@@ -76,6 +81,54 @@ BOTAN_REGISTER_TEST("bcrypt", Bcrypt_Tests);
#endif
+#if defined(BOTAN_HAS_ARGON2)
+class Argon2_Tests final : public Text_Based_Test
+ {
+ public:
+ Argon2_Tests() : Text_Based_Test("passhash/argon2.vec", "Password,Passhash", "Mode,M,T,P,Salt,OutLen") {}
+
+ Test::Result run_one_test(const std::string& header, const VarMap& vars) override
+ {
+ const std::string password = vars.get_req_str("Password");
+ const std::string passhash = vars.get_req_str("Passhash");
+
+ Test::Result result("Argon2 password hash");
+
+ if(header == "Verify")
+ {
+ const bool accepted = Botan::argon2_check_pwhash(password.data(), password.size(), passhash);
+ result.test_eq("correct hash accepted", accepted, true);
+ }
+ else if(header == "Generate")
+ {
+ const std::vector<uint8_t> salt = vars.get_req_bin("Salt");
+ const size_t y = vars.get_req_sz("Mode");
+ const size_t M = vars.get_req_sz("M");
+ const size_t t = vars.get_req_sz("T");
+ const size_t p = vars.get_req_sz("P");
+ const size_t out_len = vars.get_req_sz("OutLen");
+
+ Fixed_Output_RNG rng(salt);
+
+ const std::string generated = Botan::argon2_generate_pwhash(password.data(), password.size(),
+ rng,
+ p, M, t, y, salt.size(), out_len);
+
+ result.test_eq("expected hash generated", generated, passhash);
+ const bool accepted = Botan::argon2_check_pwhash(password.data(), password.size(), generated);
+ result.test_eq("generated hash accepted", accepted, true);
+ }
+ else
+ throw Test_Error("Unexpected header in Argon2 password hash test file");
+
+ return result;
+ }
+ };
+
+BOTAN_REGISTER_TEST("argon2_pass", Argon2_Tests);
+
+#endif
+
#if defined(BOTAN_HAS_PASSHASH9)
class Passhash9_Tests final : public Text_Based_Test
{