Botan 3.13.0
Crypto and TLS for C&
pkcs12_kdf.cpp
Go to the documentation of this file.
1/*
2* PKCS12 KDF
3* (C) 2026 Damiano Mazzella
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/internal/pkcs12_kdf.h>
9
10#include <botan/assert.h>
11#include <botan/exceptn.h>
12#include <botan/hash.h>
13#include <botan/mem_ops.h>
14#include <botan/internal/charset.h>
15#include <botan/internal/fmt.h>
16#include <botan/internal/int_utils.h>
17#include <botan/internal/time_utils.h>
18#include <algorithm>
19#include <array>
20#include <limits>
21
22namespace Botan {
23
24namespace {
25
26/*
27* Repeat data into buf until buf is filled
28*/
29void copy_repeat(std::span<uint8_t> buf, std::span<const uint8_t> data) {
30 if(data.empty()) {
31 clear_mem(buf);
32 return;
33 }
34
35 size_t pos = 0;
36 while(pos < buf.size()) {
37 const size_t to_copy = std::min(data.size(), buf.size() - pos);
38 copy_mem(buf.subspan(pos, to_copy), data.first(to_copy));
39 pos += to_copy;
40 }
41}
42
43/*
44* Big-endian addition: block = (block + addend + 1) mod 2^(len*8)
45*/
46void bigendian_add_one(std::span<uint8_t> block, std::span<const uint8_t> addend) {
47 BOTAN_DEBUG_ASSERT(block.size() == addend.size());
48
49 uint16_t carry = 1;
50 for(size_t k = block.size(); k > 0; --k) {
51 carry += static_cast<uint16_t>(block[k - 1]) + static_cast<uint16_t>(addend[k - 1]);
52 block[k - 1] = static_cast<uint8_t>(carry & 0xFF);
53 carry >>= 8;
54 }
55}
56
57secure_vector<uint8_t> pkcs12_encode_password(std::string_view password) {
58 if(password.empty()) {
59 return secure_vector<uint8_t>({0, 0});
60 }
61
62 std::vector<uint8_t> ucs2 = utf8_to_ucs2(password);
63 secure_vector<uint8_t> result(ucs2.begin(), ucs2.end());
64 secure_scrub_memory(ucs2.data(), ucs2.size());
65 result.push_back(0);
66 result.push_back(0);
67 return result;
68}
69
70void pkcs12_kdf_with_hash(std::span<uint8_t> out,
71 std::span<const uint8_t> pwd_bytes,
72 std::span<const uint8_t> salt,
73 size_t iterations,
74 uint8_t id,
75 HashFunction& hash) {
76 if(iterations == 0) {
77 throw Invalid_Argument("PKCS12-KDF: Invalid iteration count");
78 }
79 if(id < 1 || id > 3) {
80 throw Invalid_Argument("PKCS12-KDF: Invalid id (must be 1=key, 2=IV, or 3=MAC)");
81 }
82
83 // Block size depends on hash algorithm (RFC 7292 uses the hash's block size as v)
84 const size_t v = hash.hash_block_size();
85 if(v == 0) {
86 throw Invalid_Argument(fmt("PKCS12-KDF does not support hash '{}': undefined block size", hash.name()));
87 }
88
89 if(out.empty()) {
90 return;
91 }
92
93 const size_t hash_len = hash.output_length();
94
95 const size_t pwd_len = pwd_bytes.size();
96 const size_t salt_len = salt.size();
97
98 // Round len up to a multiple of v, checking for overflow. Dividing before
99 // the multiply avoids overflow when computing the number of blocks.
100 auto round_up_to_v = [v](size_t len) -> size_t {
101 if(len == 0) {
102 return 0;
103 }
104 const size_t blocks = (len / v) + (len % v != 0 ? 1 : 0);
105 return mul_or_throw(blocks, v, "PKCS12-KDF: input too large");
106 };
107
108 // Calculate sizes (must be multiple of v)
109 const size_t S_len = round_up_to_v(salt_len);
110 const size_t P_len = round_up_to_v(pwd_len);
111 const size_t I_len = add_or_throw(S_len, P_len, "PKCS12-KDF: input too large");
112
113 // Create D (diversifier): v bytes of id
114 secure_vector<uint8_t> D(v, id);
115
116 // Create I = S || P
117 secure_vector<uint8_t> I(I_len);
118 if(S_len > 0) {
119 copy_repeat(std::span{I}.first(S_len), salt);
120 }
121 if(P_len > 0) {
122 copy_repeat(std::span{I}.last(P_len), pwd_bytes);
123 }
124
125 secure_vector<uint8_t> A(hash_len);
127
128 size_t out_offset = 0;
129 while(out_offset < out.size()) {
130 // Compute A = H^iterations(D || I)
131 hash.update(D);
132 hash.update(I);
133 hash.final(A);
134
135 for(size_t iter = 1; iter < iterations; ++iter) {
136 hash.update(A);
137 hash.final(A);
138 }
139
140 // Copy to output
141 const size_t to_copy = std::min(hash_len, out.size() - out_offset);
142 copy_mem(out.subspan(out_offset, to_copy), std::span{A}.first(to_copy));
143 out_offset += to_copy;
144
145 // Update I for next block: B = A repeated to fill v bytes,
146 // then I_j = (I_j + B + 1) mod 2^(v*8)
147 copy_repeat(B, A);
148
149 for(size_t j = 0; j < I_len; j += v) {
150 bigendian_add_one(std::span{I}.subspan(j, v), B);
151 }
152 }
153}
154
155} // namespace
156
157void pkcs12_kdf(std::span<uint8_t> out,
158 std::span<const uint8_t> pwd_bytes,
159 std::span<const uint8_t> salt,
160 size_t iterations,
161 uint8_t id,
162 HashFunction& hash) {
163 pkcs12_kdf_with_hash(out, pwd_bytes, salt, iterations, id, hash);
164}
165
166PKCS12_KDF::PKCS12_KDF(std::unique_ptr<HashFunction> hash, uint8_t id, size_t iterations) :
167 m_hash(std::move(hash)), m_id(id), m_iterations(iterations) {
168 BOTAN_ARG_CHECK(m_hash != nullptr, "PKCS12-KDF: hash must not be null");
169 BOTAN_ARG_CHECK(m_iterations > 0, "PKCS12-KDF: iterations must be greater than zero");
170 BOTAN_ARG_CHECK(m_id >= 1 && m_id <= 3, "PKCS12-KDF: id must be 1 (key), 2 (IV), or 3 (MAC)");
171}
172
173std::string PKCS12_KDF::to_string() const {
174 return fmt("PKCS12-KDF({},{},{})", m_hash->name(), static_cast<unsigned>(m_id), m_iterations);
175}
176
177void PKCS12_KDF::derive_key(uint8_t out[],
178 size_t out_len,
179 const char* password,
180 size_t password_len,
181 const uint8_t salt[],
182 size_t salt_len) const {
183 const std::string_view pwd =
184 (password != nullptr && password_len > 0) ? std::string_view(password, password_len) : std::string_view{};
185 pkcs12_kdf_with_hash({out, out_len}, pkcs12_encode_password(pwd), {salt, salt_len}, m_iterations, m_id, *m_hash);
186}
187
188PKCS12_KDF_Family::PKCS12_KDF_Family(std::unique_ptr<HashFunction> hash, size_t id) :
189 m_hash(std::move(hash)), m_id(static_cast<uint8_t>(id)) {
190 BOTAN_ARG_CHECK(m_hash != nullptr, "PKCS12-KDF: hash must not be null");
191 BOTAN_ARG_CHECK(id >= 1 && id <= 3, "PKCS12-KDF: id must be 1 (key), 2 (IV), or 3 (MAC)");
192}
193
194std::string PKCS12_KDF_Family::name() const {
195 return fmt("PKCS12-KDF({},{})", m_hash->name(), static_cast<unsigned>(m_id));
196}
197
198std::unique_ptr<PasswordHash> PKCS12_KDF_Family::tune_params(size_t output_length,
199 uint64_t desired_msec,
200 std::optional<size_t> /*max_memory*/,
201 uint64_t tuning_msec) const {
202 // Benchmark the real KDF at a fixed iteration count and target output length,
203 // so the measurement captures hash(D || I), the full I-update carry loop, the
204 // inner A-rehash loop, and the per-output-block cost.
205 const size_t tuning_iterations = 10000;
206 const size_t tuning_out_len = std::max<size_t>(output_length, 1);
207 const std::array<uint8_t, 16> tuning_salt{};
208 const std::array<uint8_t, 16> tuning_pwd{};
209 const auto pwd_bytes =
210 pkcs12_encode_password(std::string_view(reinterpret_cast<const char*>(tuning_pwd.data()), tuning_pwd.size()));
211 std::vector<uint8_t> tuning_out(tuning_out_len);
212
213 auto tuning_hash = m_hash->new_object();
214 const uint64_t measured_nsec = measure_cost(tuning_msec, [&]() {
215 pkcs12_kdf_with_hash(tuning_out, pwd_bytes, tuning_salt, tuning_iterations, m_id, *tuning_hash);
216 });
217
218 // Scale: one benchmark sample = tuning_iterations iterations; target matches desired_msec.
219 const double measured = std::max<double>(1.0, static_cast<double>(measured_nsec));
220 const double desired = static_cast<double>(desired_msec) * 1'000'000.0;
221 const double est = (desired * static_cast<double>(tuning_iterations)) / measured;
222 const double est_clamped = std::clamp(est, 1.0, static_cast<double>(std::numeric_limits<size_t>::max()));
223 const size_t iterations = static_cast<size_t>(est_clamped);
224
225 return std::make_unique<PKCS12_KDF>(m_hash->new_object(), m_id, iterations);
226}
227
228std::unique_ptr<PasswordHash> PKCS12_KDF_Family::default_params() const {
229 return std::make_unique<PKCS12_KDF>(m_hash->new_object(), m_id, 2048);
230}
231
232std::unique_ptr<PasswordHash> PKCS12_KDF_Family::from_iterations(size_t iterations) const {
233 return std::make_unique<PKCS12_KDF>(m_hash->new_object(), m_id, iterations);
234}
235
236std::unique_ptr<PasswordHash> PKCS12_KDF_Family::from_params(size_t i1, size_t /*i2*/, size_t /*i3*/) const {
237 return std::make_unique<PKCS12_KDF>(m_hash->new_object(), m_id, i1);
238}
239
240} // namespace Botan
#define BOTAN_DEBUG_ASSERT(expr)
Definition assert.h:129
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
std::string name() const override
std::unique_ptr< PasswordHash > from_params(size_t i1, size_t i2=0, size_t i3=0) const override
std::unique_ptr< PasswordHash > tune_params(size_t output_length, uint64_t desired_runtime_msec, std::optional< size_t > max_memory_usage_mb={}, uint64_t tuning_msec=10) const override
std::unique_ptr< PasswordHash > default_params() const override
std::unique_ptr< PasswordHash > from_iterations(size_t iterations) const override
PKCS12_KDF_Family(std::unique_ptr< HashFunction > hash, size_t id)
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
PKCS12_KDF(std::unique_ptr< HashFunction > hash, uint8_t id, size_t iterations)
size_t iterations() const override
Definition pkcs12_kdf.h:41
void hash(std::span< uint8_t > out, std::string_view password, std::span< const uint8_t > salt) const
Definition pwdhash.h:95
constexpr T add_or_throw(T a, T b, std::string_view msg)
Definition int_utils.h:66
constexpr T mul_or_throw(T a, T b, std::string_view msg)
Definition int_utils.h:81
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
void secure_scrub_memory(void *ptr, size_t n)
Definition mem_utils.cpp:25
void carry(int64_t &h0, int64_t &h1)
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128
uint64_t measure_cost(uint64_t trial_msec, F func)
Definition time_utils.h:19
void pkcs12_kdf(std::span< uint8_t > out, std::span< const uint8_t > pwd_bytes, std::span< const uint8_t > salt, size_t iterations, uint8_t id, HashFunction &hash)
constexpr void clear_mem(T *ptr, size_t n)
Definition mem_ops.h:118
std::vector< uint8_t > utf8_to_ucs2(std::string_view utf8)
Definition charset.cpp:137