Skip to content

Commit 8eade60

Browse files
committed
feat(hmac): add streaming context and generic pbkdf2
Introduce HmacContext with init, update and final methods. Implement the context in source, add a generic PBKDF2 buffer API, and extend tests.
1 parent baa5d59 commit 8eade60

5 files changed

Lines changed: 254 additions & 11 deletions

File tree

include/hmac_cpp/hmac.hpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,36 @@ namespace hmac_cpp {
5050
return get_hash(input.data(), input.size(), type);
5151
}
5252

53+
/// \brief Streaming HMAC computation context.
54+
class HmacContext {
55+
public:
56+
explicit HmacContext(TypeHash type) : type_(type), block_size_(0), digest_size_(0) {}
57+
58+
/// \brief Initializes the context with a secret key.
59+
/// \param key_ptr Pointer to the key buffer; must be non-null if key_len > 0
60+
/// \param key_len Length of the key in bytes
61+
void init(const void* key_ptr, size_t key_len);
62+
63+
/// \brief Updates the HMAC with message data.
64+
/// \param data_ptr Pointer to the message buffer; must be non-null if data_len > 0
65+
/// \param data_len Length of the message in bytes
66+
void update(const void* data_ptr, size_t data_len);
67+
68+
/// \brief Finalizes the HMAC and writes the result to the provided buffer.
69+
/// \param out_ptr Output buffer for the HMAC result
70+
/// \param out_len Length of the output buffer; must be at least the digest size
71+
void final(uint8_t* out_ptr, size_t out_len);
72+
73+
private:
74+
TypeHash type_;
75+
size_t block_size_;
76+
size_t digest_size_;
77+
secure_buffer<uint8_t> okeypad_;
78+
hmac_hash::SHA1 sha1_;
79+
hmac_hash::SHA256 sha256_;
80+
hmac_hash::SHA512 sha512_;
81+
};
82+
5383
/// \brief Computes HMAC for raw binary data using the specified hash function.
5484
/// \param key_ptr Pointer to the key buffer; must be non-null if key_len > 0
5585
/// \param key_len Length of the key in bytes

include/hmac_cpp/hmac_utils.hpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,55 @@ namespace hmac_cpp {
106106
iterations, dk_len, prf);
107107
}
108108

109+
/// \brief Derives PBKDF2 into caller-provided buffer using selected hash.
110+
/// \param prf Hash function to use (SHA1, SHA256, SHA512)
111+
/// \param password_ptr Pointer to the password buffer
112+
/// \param password_len Length of the password in bytes
113+
/// \param salt_ptr Pointer to the salt buffer
114+
/// \param salt_len Length of the salt in bytes
115+
/// \param iterations Number of iterations, must be positive
116+
/// \param out_ptr Output buffer for derived key
117+
/// \param dk_len Length of output buffer in bytes, must be positive
118+
/// \return true on success, false on invalid parameters
119+
bool pbkdf2(Pbkdf2Hash prf,
120+
const void* password_ptr, size_t password_len,
121+
const void* salt_ptr, size_t salt_len,
122+
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept;
123+
124+
/// \deprecated Use overloads that accept std::vector<uint8_t> or secure_buffer.
125+
template<size_t N>
126+
HMACCPP_DEPRECATED("use std::vector<uint8_t> or secure_buffer overload")
127+
inline bool pbkdf2(Pbkdf2Hash prf,
128+
const std::string& password,
129+
const std::string& salt,
130+
uint32_t iterations,
131+
std::array<uint8_t, N>& out) noexcept {
132+
return pbkdf2(prf, password.data(), password.size(),
133+
salt.data(), salt.size(),
134+
iterations, out.data(), out.size());
135+
}
136+
137+
inline bool pbkdf2(Pbkdf2Hash prf,
138+
const secure_buffer<uint8_t>& password,
139+
const secure_buffer<uint8_t>& salt,
140+
uint32_t iterations,
141+
uint8_t* out_ptr, size_t dk_len) noexcept {
142+
return pbkdf2(prf, password.data(), password.size(),
143+
salt.data(), salt.size(),
144+
iterations, out_ptr, dk_len);
145+
}
146+
147+
template<size_t N>
148+
inline bool pbkdf2(Pbkdf2Hash prf,
149+
const secure_buffer<uint8_t>& password,
150+
const secure_buffer<uint8_t>& salt,
151+
uint32_t iterations,
152+
std::array<uint8_t, N>& out) noexcept {
153+
return pbkdf2(prf, password.data(), password.size(),
154+
salt.data(), salt.size(),
155+
iterations, out.data(), out.size());
156+
}
157+
109158
/// \brief Derives PBKDF2-HMAC-SHA256 into caller-provided buffer
110159
/// \param password_ptr Pointer to the password buffer
111160
/// \param password_len Length of the password in bytes

src/hmac.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,126 @@ namespace hmac_cpp {
8686
}
8787
}
8888

89+
void HmacContext::init(const void* key_ptr, size_t key_len) {
90+
if (key_len > 0 && key_ptr == nullptr)
91+
throw std::invalid_argument("Null key with non-zero length");
92+
93+
switch (type_) {
94+
case TypeHash::SHA1:
95+
block_size_ = hmac_hash::SHA1::BLOCK_SIZE;
96+
digest_size_ = hmac_hash::SHA1::DIGEST_SIZE;
97+
break;
98+
case TypeHash::SHA256:
99+
block_size_ = hmac_hash::SHA256::SHA224_256_BLOCK_SIZE;
100+
digest_size_ = hmac_hash::SHA256::DIGEST_SIZE;
101+
break;
102+
case TypeHash::SHA512:
103+
block_size_ = hmac_hash::SHA512::SHA384_512_BLOCK_SIZE;
104+
digest_size_ = hmac_hash::SHA512::DIGEST_SIZE;
105+
break;
106+
default:
107+
throw std::invalid_argument("Unsupported hash type");
108+
}
109+
110+
secure_buffer<uint8_t> key(block_size_);
111+
if (key_len > block_size_) {
112+
auto hashed = get_hash(key_ptr, key_len, type_);
113+
std::copy(hashed.begin(), hashed.end(), key.begin());
114+
if (hashed.size() < block_size_)
115+
std::fill(key.begin() + hashed.size(), key.end(), 0);
116+
secure_zero(hashed.data(), hashed.size());
117+
} else {
118+
if (key_len > 0)
119+
std::memcpy(key.data(), key_ptr, key_len);
120+
if (key_len < block_size_)
121+
std::fill(key.begin() + key_len, key.end(), 0);
122+
}
123+
124+
okeypad_ = secure_buffer<uint8_t>(block_size_);
125+
secure_buffer<uint8_t> ipad(block_size_);
126+
for (size_t i = 0; i < block_size_; ++i) {
127+
const uint8_t k = key[i];
128+
ipad[i] = k ^ 0x36;
129+
okeypad_[i] = k ^ 0x5c;
130+
}
131+
132+
switch (type_) {
133+
case TypeHash::SHA1:
134+
sha1_.init();
135+
sha1_.update(ipad.data(), block_size_);
136+
break;
137+
case TypeHash::SHA256:
138+
sha256_.init();
139+
sha256_.update(ipad.data(), block_size_);
140+
break;
141+
case TypeHash::SHA512:
142+
sha512_.init();
143+
sha512_.update(ipad.data(), block_size_);
144+
break;
145+
default:
146+
throw std::invalid_argument("Unsupported hash type");
147+
}
148+
149+
secure_zero(key.data(), key.size());
150+
secure_zero(ipad.data(), ipad.size());
151+
}
152+
153+
void HmacContext::update(const void* data_ptr, size_t data_len) {
154+
if (data_len > 0 && data_ptr == nullptr)
155+
throw std::invalid_argument("Null data pointer with non-zero length");
156+
const uint8_t* p = static_cast<const uint8_t*>(data_ptr);
157+
switch (type_) {
158+
case TypeHash::SHA1:
159+
sha1_.update(p, data_len);
160+
break;
161+
case TypeHash::SHA256:
162+
sha256_.update(p, data_len);
163+
break;
164+
case TypeHash::SHA512:
165+
sha512_.update(p, data_len);
166+
break;
167+
default:
168+
throw std::invalid_argument("Unsupported hash type");
169+
}
170+
}
171+
172+
void HmacContext::final(uint8_t* out_ptr, size_t out_len) {
173+
if (out_ptr == nullptr)
174+
throw std::invalid_argument("Null output pointer");
175+
if (out_len < digest_size_)
176+
throw std::invalid_argument("Output buffer too small");
177+
178+
secure_buffer<uint8_t> inner(digest_size_);
179+
180+
switch (type_) {
181+
case TypeHash::SHA1:
182+
sha1_.finish(inner.data());
183+
sha1_.init();
184+
sha1_.update(okeypad_.data(), block_size_);
185+
sha1_.update(inner.data(), digest_size_);
186+
sha1_.finish(out_ptr);
187+
break;
188+
case TypeHash::SHA256:
189+
sha256_.finish(inner.data());
190+
sha256_.init();
191+
sha256_.update(okeypad_.data(), block_size_);
192+
sha256_.update(inner.data(), digest_size_);
193+
sha256_.finish(out_ptr);
194+
break;
195+
case TypeHash::SHA512:
196+
sha512_.finish(inner.data());
197+
sha512_.init();
198+
sha512_.update(okeypad_.data(), block_size_);
199+
sha512_.update(inner.data(), digest_size_);
200+
sha512_.finish(out_ptr);
201+
break;
202+
default:
203+
throw std::invalid_argument("Unsupported hash type");
204+
}
205+
206+
secure_zero(inner.data(), inner.size());
207+
}
208+
89209
std::vector<uint8_t> get_hmac(const void* key_ptr, size_t key_len, const void* msg_ptr, size_t msg_len, TypeHash type) {
90210
if ((key_len > 0 && key_ptr == nullptr) || (msg_len > 0 && msg_ptr == nullptr))
91211
throw std::invalid_argument("Null pointer with non-zero length");

src/hmac_utils.cpp

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,10 @@ namespace hmac_cpp {
110110
return derived;
111111
}
112112

113-
bool pbkdf2_hmac_sha256(const void* password_ptr, size_t password_len,
114-
const void* salt_ptr, size_t salt_len,
115-
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept {
113+
bool pbkdf2(Pbkdf2Hash prf,
114+
const void* password_ptr, size_t password_len,
115+
const void* salt_ptr, size_t salt_len,
116+
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept {
116117
if ((password_len > 0 && password_ptr == nullptr) ||
117118
(salt_len > 0 && salt_ptr == nullptr) ||
118119
out_ptr == nullptr)
@@ -121,7 +122,22 @@ namespace hmac_cpp {
121122
iterations > MAX_PBKDF2_ITERATIONS)
122123
return false;
123124

124-
const size_t hlen = hmac_hash::SHA256::DIGEST_SIZE;
125+
TypeHash hash_type = to_type_hash(prf);
126+
size_t hlen = 0;
127+
switch (hash_type) {
128+
case TypeHash::SHA1:
129+
hlen = hmac_hash::SHA1::DIGEST_SIZE;
130+
break;
131+
case TypeHash::SHA256:
132+
hlen = hmac_hash::SHA256::DIGEST_SIZE;
133+
break;
134+
case TypeHash::SHA512:
135+
hlen = hmac_hash::SHA512::DIGEST_SIZE;
136+
break;
137+
default:
138+
return false;
139+
}
140+
125141
uint64_t max_dk = (static_cast<uint64_t>(1) << 32) - 1;
126142
max_dk *= hlen;
127143
if (dk_len > max_dk)
@@ -144,27 +160,44 @@ namespace hmac_cpp {
144160
salt_block[salt_len + 2] = static_cast<uint8_t>((i >> 8) & 0xFF);
145161
salt_block[salt_len + 3] = static_cast<uint8_t>(i & 0xFF);
146162

147-
secure_buffer<uint8_t> u(std::move(get_hmac(password_ptr, password_len,
148-
salt_block.data(), salt_block.size(),
149-
TypeHash::SHA256)));
150-
secure_buffer<uint8_t> t = u;
163+
secure_buffer<uint8_t> u(hlen);
164+
secure_buffer<uint8_t> t(hlen);
165+
HmacContext ctx(hash_type);
166+
ctx.init(password_ptr, password_len);
167+
ctx.update(salt_block.data(), salt_block.size());
168+
ctx.final(u.data(), hlen);
169+
std::memcpy(t.data(), u.data(), hlen);
170+
151171
for (uint32_t j = 1; j < iterations; ++j) {
152-
u = secure_buffer<uint8_t>(get_hmac(password_ptr, password_len,
153-
u.data(), u.size(), TypeHash::SHA256));
154-
for (size_t k = 0; k < t.size(); ++k) {
172+
ctx.init(password_ptr, password_len);
173+
ctx.update(u.data(), hlen);
174+
ctx.final(u.data(), hlen);
175+
for (size_t k = 0; k < hlen; ++k) {
155176
t[k] ^= u[k];
156177
}
157178
}
179+
158180
size_t take = (i == l) ? r : hlen;
159181
std::memcpy(out_ptr + pos, t.data(), take);
160182
pos += take;
183+
161184
secure_zero(u.data(), u.size());
162185
secure_zero(t.data(), t.size());
163186
}
187+
164188
secure_zero(salt_block.data(), salt_block.size());
165189
return true;
166190
}
167191

192+
bool pbkdf2_hmac_sha256(const void* password_ptr, size_t password_len,
193+
const void* salt_ptr, size_t salt_len,
194+
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept {
195+
return pbkdf2(Pbkdf2Hash::Sha256,
196+
password_ptr, password_len,
197+
salt_ptr, salt_len,
198+
iterations, out_ptr, dk_len);
199+
}
200+
168201
std::vector<uint8_t> pbkdf2_with_pepper(
169202
const void* password_ptr, size_t password_len,
170203
const void* salt_ptr, size_t salt_len,

test_all.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,17 @@ TEST(PBKDF2BufferApiTest, SHA256ArrayOutput) {
306306
EXPECT_TRUE(std::equal(out.begin(), out.end(), ref.begin()));
307307
}
308308

309+
TEST(PBKDF2BufferApiTest, GenericArrayOutput) {
310+
auto salt = from_hex("000102030405060708090a0b0c0d0e0f");
311+
std::string salt_str(salt.begin(), salt.end());
312+
std::array<uint8_t,32> out{};
313+
ASSERT_TRUE(hmac::pbkdf2(hmac::Pbkdf2Hash::Sha256,
314+
std::string("password"), salt_str, 2, out));
315+
std::vector<uint8_t> ref(32);
316+
ASSERT_TRUE(PKCS5_PBKDF2_HMAC("password", 8, salt.data(), salt.size(), 2, EVP_sha256(), ref.size(), ref.data()));
317+
EXPECT_TRUE(std::equal(out.begin(), out.end(), ref.begin()));
318+
}
319+
309320
TEST(PBKDF2BufferApiTest, IterationsLimit) {
310321
std::string salt(16, 'a');
311322
std::array<uint8_t,32> out{};

0 commit comments

Comments
 (0)