Botan 3.13.0
Crypto and TLS for C&
argon2pwhash.cpp
Go to the documentation of this file.
1/**
2* (C) 2019 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#include <botan/argon2.h>
8
9#include <botan/assert.h>
10#include <botan/exceptn.h>
11#include <botan/internal/fmt.h>
12#include <botan/internal/time_utils.h>
13#include <algorithm>
14#include <limits>
15
16namespace Botan {
17
18namespace {
19
20std::string argon2_family_name(uint8_t f) {
21 switch(f) {
22 case 0:
23 return "Argon2d";
24 case 1:
25 return "Argon2i";
26 case 2:
27 return "Argon2id";
28 default:
29 throw Invalid_Argument("Unknown Argon2 parameter");
30 }
31}
32
33constexpr size_t MAX_ARGON_MEMORY_GB = sizeof(size_t) == 4 ? 2 : 8;
34
35} // namespace
36
37Argon2::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) {
38 BOTAN_ARG_CHECK(m_family == 0 || m_family == 1 || m_family == 2, "Invalid Argon2 family parameter");
39 BOTAN_ARG_CHECK(m_p >= 1 && m_p <= 128, "Invalid Argon2 threads parameter");
40 BOTAN_ARG_CHECK(m_M >= 8 * m_p && m_M <= MAX_ARGON_MEMORY_GB * 1024 * 1024, "Invalid Argon2 M parameter");
41 BOTAN_ARG_CHECK(m_t >= 1 && m_t <= std::numeric_limits<uint32_t>::max(), "Invalid Argon2 t parameter");
42}
43
44void Argon2::derive_key(uint8_t output[],
45 size_t output_len,
46 const char* password,
47 size_t password_len,
48 const uint8_t salt[],
49 size_t salt_len) const {
50 argon2(output, output_len, password, password_len, salt, salt_len, nullptr, 0, nullptr, 0);
51}
52
53void Argon2::derive_key(uint8_t output[],
54 size_t output_len,
55 const char* password,
56 size_t password_len,
57 const uint8_t salt[],
58 size_t salt_len,
59 const uint8_t ad[],
60 size_t ad_len,
61 const uint8_t key[],
62 size_t key_len) const {
63 argon2(output, output_len, password, password_len, salt, salt_len, key, key_len, ad, ad_len);
64}
65
66std::string Argon2::to_string() const {
67 return fmt("{}({},{},{})", argon2_family_name(m_family), m_M, m_t, m_p);
68}
69
70Argon2_Family::Argon2_Family(uint8_t family) : m_family(family) {
71 if(m_family != 0 && m_family != 1 && m_family != 2) {
72 throw Invalid_Argument("Unknown Argon2 family identifier");
73 }
74}
75
76std::string Argon2_Family::name() const {
77 return argon2_family_name(m_family);
78}
79
80std::unique_ptr<PasswordHash> Argon2_Family::tune_params(size_t /*output_length*/,
81 uint64_t desired_msec,
82 std::optional<size_t> max_memory,
83 uint64_t tune_msec) const {
84 // If not set use 256 MB as default max
85 const size_t max_kib = std::min(MAX_ARGON_MEMORY_GB * 1024 * 1024, max_memory.value_or(256) * 1024);
86
87 // Tune with a large memory otherwise we measure cache vs RAM speeds and underestimate
88 // costs for larger params. Default is 36 MiB, or use 128 for long times.
89 const size_t tune_M = (desired_msec >= 200 ? 128 : 36) * 1024;
90 const size_t p = 1;
91 size_t t = 1;
92
93 size_t M = 4 * 1024;
94
95 auto pwhash = this->from_params(tune_M, t, p);
96
97 auto tune_fn = [&]() {
98 uint8_t output[64] = {0};
99 pwhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0);
100 };
101
102 const uint64_t measured_time = measure_cost(tune_msec, tune_fn) / (tune_M / M);
103
104 const uint64_t target_nsec = desired_msec * static_cast<uint64_t>(1000000);
105
106 /*
107 * Argon2 scaling rules:
108 * k*M, k*t, k*p all increase cost by about k
109 *
110 * First preference is to increase M up to max allowed value.
111 * Any remaining time budget is spent on increasing t.
112 */
113
114 uint64_t est_nsec = measured_time;
115
116 if(est_nsec < target_nsec && M < max_kib) {
117 const uint64_t desired_cost_increase = (target_nsec + est_nsec - 1) / est_nsec;
118 const uint64_t mem_headroom = max_kib / M;
119
120 const uint64_t M_mult = std::min(desired_cost_increase, mem_headroom);
121 M *= static_cast<size_t>(M_mult);
122 est_nsec *= M_mult;
123 }
124
125 if(est_nsec < target_nsec / 2) {
126 const uint64_t desired_cost_increase = (target_nsec + est_nsec - 1) / est_nsec;
127 t *= static_cast<size_t>(desired_cost_increase);
128 }
129
130 return this->from_params(M, t, p);
131}
132
133std::unique_ptr<PasswordHash> Argon2_Family::default_params() const {
134 return this->from_params(128 * 1024, 1, 1);
135}
136
137std::unique_ptr<PasswordHash> Argon2_Family::from_iterations(size_t iter) const {
138 /*
139 These choices are arbitrary, but should not change in future
140 releases since they will break applications expecting deterministic
141 mapping from iteration count to params
142 */
143 const size_t M = iter;
144 const size_t t = 1;
145 const size_t p = 1;
146 return this->from_params(M, t, p);
147}
148
149std::unique_ptr<PasswordHash> Argon2_Family::from_params(size_t M, size_t t, size_t p) const {
150 return std::make_unique<Argon2>(m_family, M, t, p);
151}
152
153} // namespace Botan
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
std::unique_ptr< PasswordHash > from_params(size_t M, size_t t, size_t p) const override
std::string name() const override
BOTAN_FUTURE_EXPLICIT Argon2_Family(uint8_t family)
std::unique_ptr< PasswordHash > tune_params(size_t output_len, uint64_t desired_runtime_msec, std::optional< size_t > max_memory, uint64_t tune_msec) const override
std::unique_ptr< PasswordHash > default_params() const override
std::unique_ptr< PasswordHash > from_iterations(size_t iter) const override
size_t p() const
Definition argon2.h:55
size_t t() const
Definition argon2.h:53
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 override
std::string to_string() const override
size_t M() const
Definition argon2.h:51
Argon2(uint8_t family, size_t M, size_t t, size_t p)
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
uint64_t measure_cost(uint64_t trial_msec, F func)
Definition time_utils.h:19