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