/src/botan/src/lib/pbkdf/argon2/argon2pwhash.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | |
16 | | namespace Botan { |
17 | | |
18 | 287 | 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) { |
19 | 287 | BOTAN_ARG_CHECK(m_p >= 1 && m_p <= 128, "Invalid Argon2 threads parameter"); |
20 | 287 | BOTAN_ARG_CHECK(m_M >= 8 * m_p && m_M <= 8192 * 1024, "Invalid Argon2 M parameter"); |
21 | 287 | BOTAN_ARG_CHECK(m_t >= 1 && m_t <= std::numeric_limits<uint32_t>::max(), "Invalid Argon2 t parameter"); |
22 | 287 | } |
23 | | |
24 | | void Argon2::derive_key(uint8_t output[], |
25 | | size_t output_len, |
26 | | const char* password, |
27 | | size_t password_len, |
28 | | const uint8_t salt[], |
29 | 253 | size_t salt_len) const { |
30 | 253 | argon2(output, output_len, password, password_len, salt, salt_len, nullptr, 0, nullptr, 0); |
31 | 253 | } |
32 | | |
33 | | void Argon2::derive_key(uint8_t output[], |
34 | | size_t output_len, |
35 | | const char* password, |
36 | | size_t password_len, |
37 | | const uint8_t salt[], |
38 | | size_t salt_len, |
39 | | const uint8_t ad[], |
40 | | size_t ad_len, |
41 | | const uint8_t key[], |
42 | 0 | size_t key_len) const { |
43 | 0 | argon2(output, output_len, password, password_len, salt, salt_len, key, key_len, ad, ad_len); |
44 | 0 | } |
45 | | |
46 | | namespace { |
47 | | |
48 | 0 | std::string argon2_family_name(uint8_t f) { |
49 | 0 | switch(f) { |
50 | 0 | case 0: |
51 | 0 | return "Argon2d"; |
52 | 0 | case 1: |
53 | 0 | return "Argon2i"; |
54 | 0 | case 2: |
55 | 0 | return "Argon2id"; |
56 | 0 | default: |
57 | 0 | throw Invalid_Argument("Unknown Argon2 parameter"); |
58 | 0 | } |
59 | 0 | } |
60 | | |
61 | | } // namespace |
62 | | |
63 | 0 | std::string Argon2::to_string() const { |
64 | 0 | return fmt("{}({},{},{})", argon2_family_name(m_family), m_M, m_t, m_p); |
65 | 0 | } |
66 | | |
67 | 287 | Argon2_Family::Argon2_Family(uint8_t family) : m_family(family) { |
68 | 287 | if(m_family != 0 && m_family != 1 && m_family != 2) { |
69 | 0 | throw Invalid_Argument("Unknown Argon2 family identifier"); |
70 | 0 | } |
71 | 287 | } |
72 | | |
73 | 0 | std::string Argon2_Family::name() const { |
74 | 0 | return argon2_family_name(m_family); |
75 | 0 | } |
76 | | |
77 | | std::unique_ptr<PasswordHash> Argon2_Family::tune(size_t /*output_length*/, |
78 | | std::chrono::milliseconds msec, |
79 | | size_t max_memory, |
80 | 0 | std::chrono::milliseconds tune_time) const { |
81 | 0 | const size_t max_kib = (max_memory == 0) ? 256 * 1024 : max_memory * 1024; |
82 | | |
83 | | // Tune with a large memory otherwise we measure cache vs RAM speeds and underestimate |
84 | | // costs for larger params. Default is 36 MiB, or use 128 for long times. |
85 | 0 | const size_t tune_M = (msec >= std::chrono::milliseconds(200) ? 128 : 36) * 1024; |
86 | 0 | const size_t p = 1; |
87 | 0 | size_t t = 1; |
88 | |
|
89 | 0 | size_t M = 4 * 1024; |
90 | |
|
91 | 0 | auto pwhash = this->from_params(tune_M, t, p); |
92 | |
|
93 | 0 | auto tune_fn = [&]() { |
94 | 0 | uint8_t output[64] = {0}; |
95 | 0 | pwhash->derive_key(output, sizeof(output), "test", 4, nullptr, 0); |
96 | 0 | }; |
97 | |
|
98 | 0 | const uint64_t measured_time = measure_cost(tune_time, tune_fn) / (tune_M / M); |
99 | |
|
100 | 0 | const uint64_t target_nsec = msec.count() * static_cast<uint64_t>(1000000); |
101 | | |
102 | | /* |
103 | | * Argon2 scaling rules: |
104 | | * k*M, k*t, k*p all increase cost by about k |
105 | | * |
106 | | * First preference is to increase M up to max allowed value. |
107 | | * Any remaining time budget is spent on increasing t. |
108 | | */ |
109 | |
|
110 | 0 | uint64_t est_nsec = measured_time; |
111 | |
|
112 | 0 | if(est_nsec < target_nsec && M < max_kib) { |
113 | 0 | const uint64_t desired_cost_increase = (target_nsec + est_nsec - 1) / est_nsec; |
114 | 0 | const uint64_t mem_headroom = max_kib / M; |
115 | |
|
116 | 0 | const uint64_t M_mult = std::min(desired_cost_increase, mem_headroom); |
117 | 0 | M *= static_cast<size_t>(M_mult); |
118 | 0 | est_nsec *= M_mult; |
119 | 0 | } |
120 | |
|
121 | 0 | if(est_nsec < target_nsec / 2) { |
122 | 0 | const uint64_t desired_cost_increase = (target_nsec + est_nsec - 1) / est_nsec; |
123 | 0 | t *= static_cast<size_t>(desired_cost_increase); |
124 | 0 | } |
125 | |
|
126 | 0 | return this->from_params(M, t, p); |
127 | 0 | } |
128 | | |
129 | 0 | std::unique_ptr<PasswordHash> Argon2_Family::default_params() const { |
130 | 0 | return this->from_params(128 * 1024, 1, 1); |
131 | 0 | } |
132 | | |
133 | 0 | std::unique_ptr<PasswordHash> Argon2_Family::from_iterations(size_t iter) const { |
134 | | /* |
135 | | These choices are arbitrary, but should not change in future |
136 | | releases since they will break applications expecting deterministic |
137 | | mapping from iteration count to params |
138 | | */ |
139 | 0 | const size_t M = iter; |
140 | 0 | const size_t t = 1; |
141 | 0 | const size_t p = 1; |
142 | 0 | return this->from_params(M, t, p); |
143 | 0 | } |
144 | | |
145 | 287 | std::unique_ptr<PasswordHash> Argon2_Family::from_params(size_t M, size_t t, size_t p) const { |
146 | 287 | return std::make_unique<Argon2>(m_family, M, t, p); |
147 | 287 | } |
148 | | |
149 | | } // namespace Botan |