Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions include/hmac_cpp/hmac.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,36 @@ namespace hmac_cpp {
return get_hash(input.data(), input.size(), type);
}

/// \brief Streaming HMAC computation context.
class HmacContext {
public:
explicit HmacContext(TypeHash type) : type_(type), block_size_(0), digest_size_(0) {}

/// \brief Initializes the context with a secret key.
/// \param key_ptr Pointer to the key buffer; must be non-null if key_len > 0
/// \param key_len Length of the key in bytes
void init(const void* key_ptr, size_t key_len);

/// \brief Updates the HMAC with message data.
/// \param data_ptr Pointer to the message buffer; must be non-null if data_len > 0
/// \param data_len Length of the message in bytes
void update(const void* data_ptr, size_t data_len);

/// \brief Finalizes the HMAC and writes the result to the provided buffer.
/// \param out_ptr Output buffer for the HMAC result
/// \param out_len Length of the output buffer; must be at least the digest size
void final(uint8_t* out_ptr, size_t out_len);

private:
TypeHash type_;
size_t block_size_;
size_t digest_size_;
secure_buffer<uint8_t> okeypad_;
hmac_hash::SHA1 sha1_;
hmac_hash::SHA256 sha256_;
hmac_hash::SHA512 sha512_;
};

/// \brief Computes HMAC for raw binary data using the specified hash function.
/// \param key_ptr Pointer to the key buffer; must be non-null if key_len > 0
/// \param key_len Length of the key in bytes
Expand Down
49 changes: 49 additions & 0 deletions include/hmac_cpp/hmac_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,55 @@ namespace hmac_cpp {
iterations, dk_len, prf);
}

/// \brief Derives PBKDF2 into caller-provided buffer using selected hash.
/// \param prf Hash function to use (SHA1, SHA256, SHA512)
/// \param password_ptr Pointer to the password buffer
/// \param password_len Length of the password in bytes
/// \param salt_ptr Pointer to the salt buffer
/// \param salt_len Length of the salt in bytes
/// \param iterations Number of iterations, must be positive
/// \param out_ptr Output buffer for derived key
/// \param dk_len Length of output buffer in bytes, must be positive
/// \return true on success, false on invalid parameters
bool pbkdf2(Pbkdf2Hash prf,
const void* password_ptr, size_t password_len,
const void* salt_ptr, size_t salt_len,
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept;

/// \deprecated Use overloads that accept std::vector<uint8_t> or secure_buffer.
template<size_t N>
HMACCPP_DEPRECATED("use std::vector<uint8_t> or secure_buffer overload")
inline bool pbkdf2(Pbkdf2Hash prf,
const std::string& password,
const std::string& salt,
uint32_t iterations,
std::array<uint8_t, N>& out) noexcept {
return pbkdf2(prf, password.data(), password.size(),
salt.data(), salt.size(),
iterations, out.data(), out.size());
}

inline bool pbkdf2(Pbkdf2Hash prf,
const secure_buffer<uint8_t>& password,
const secure_buffer<uint8_t>& salt,
uint32_t iterations,
uint8_t* out_ptr, size_t dk_len) noexcept {
return pbkdf2(prf, password.data(), password.size(),
salt.data(), salt.size(),
iterations, out_ptr, dk_len);
}

template<size_t N>
inline bool pbkdf2(Pbkdf2Hash prf,
const secure_buffer<uint8_t>& password,
const secure_buffer<uint8_t>& salt,
uint32_t iterations,
std::array<uint8_t, N>& out) noexcept {
return pbkdf2(prf, password.data(), password.size(),
salt.data(), salt.size(),
iterations, out.data(), out.size());
}

/// \brief Derives PBKDF2-HMAC-SHA256 into caller-provided buffer
/// \param password_ptr Pointer to the password buffer
/// \param password_len Length of the password in bytes
Expand Down
120 changes: 120 additions & 0 deletions src/hmac.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,126 @@ namespace hmac_cpp {
}
}

void HmacContext::init(const void* key_ptr, size_t key_len) {
if (key_len > 0 && key_ptr == nullptr)
throw std::invalid_argument("Null key with non-zero length");

switch (type_) {
case TypeHash::SHA1:
block_size_ = hmac_hash::SHA1::BLOCK_SIZE;
digest_size_ = hmac_hash::SHA1::DIGEST_SIZE;
break;
case TypeHash::SHA256:
block_size_ = hmac_hash::SHA256::SHA224_256_BLOCK_SIZE;
digest_size_ = hmac_hash::SHA256::DIGEST_SIZE;
break;
case TypeHash::SHA512:
block_size_ = hmac_hash::SHA512::SHA384_512_BLOCK_SIZE;
digest_size_ = hmac_hash::SHA512::DIGEST_SIZE;
break;
default:
throw std::invalid_argument("Unsupported hash type");
}

secure_buffer<uint8_t> key(block_size_);
if (key_len > block_size_) {
auto hashed = get_hash(key_ptr, key_len, type_);
std::copy(hashed.begin(), hashed.end(), key.begin());
if (hashed.size() < block_size_)
std::fill(key.begin() + hashed.size(), key.end(), 0);
secure_zero(hashed.data(), hashed.size());
} else {
if (key_len > 0)
std::memcpy(key.data(), key_ptr, key_len);
if (key_len < block_size_)
std::fill(key.begin() + key_len, key.end(), 0);
}

okeypad_ = secure_buffer<uint8_t>(block_size_);
secure_buffer<uint8_t> ipad(block_size_);
for (size_t i = 0; i < block_size_; ++i) {
const uint8_t k = key[i];
ipad[i] = k ^ 0x36;
okeypad_[i] = k ^ 0x5c;
}

switch (type_) {
case TypeHash::SHA1:
sha1_.init();
sha1_.update(ipad.data(), block_size_);
break;
case TypeHash::SHA256:
sha256_.init();
sha256_.update(ipad.data(), block_size_);
break;
case TypeHash::SHA512:
sha512_.init();
sha512_.update(ipad.data(), block_size_);
break;
default:
throw std::invalid_argument("Unsupported hash type");
}

secure_zero(key.data(), key.size());
secure_zero(ipad.data(), ipad.size());
}

void HmacContext::update(const void* data_ptr, size_t data_len) {
if (data_len > 0 && data_ptr == nullptr)
throw std::invalid_argument("Null data pointer with non-zero length");
const uint8_t* p = static_cast<const uint8_t*>(data_ptr);
switch (type_) {
case TypeHash::SHA1:
sha1_.update(p, data_len);
break;
case TypeHash::SHA256:
sha256_.update(p, data_len);
break;
case TypeHash::SHA512:
sha512_.update(p, data_len);
break;
default:
throw std::invalid_argument("Unsupported hash type");
}
}

void HmacContext::final(uint8_t* out_ptr, size_t out_len) {
if (out_ptr == nullptr)
throw std::invalid_argument("Null output pointer");
if (out_len < digest_size_)
throw std::invalid_argument("Output buffer too small");

secure_buffer<uint8_t> inner(digest_size_);

switch (type_) {
case TypeHash::SHA1:
sha1_.finish(inner.data());
sha1_.init();
sha1_.update(okeypad_.data(), block_size_);
sha1_.update(inner.data(), digest_size_);
sha1_.finish(out_ptr);
break;
case TypeHash::SHA256:
sha256_.finish(inner.data());
sha256_.init();
sha256_.update(okeypad_.data(), block_size_);
sha256_.update(inner.data(), digest_size_);
sha256_.finish(out_ptr);
break;
case TypeHash::SHA512:
sha512_.finish(inner.data());
sha512_.init();
sha512_.update(okeypad_.data(), block_size_);
sha512_.update(inner.data(), digest_size_);
sha512_.finish(out_ptr);
break;
default:
throw std::invalid_argument("Unsupported hash type");
}

secure_zero(inner.data(), inner.size());
}

std::vector<uint8_t> get_hmac(const void* key_ptr, size_t key_len, const void* msg_ptr, size_t msg_len, TypeHash type) {
if ((key_len > 0 && key_ptr == nullptr) || (msg_len > 0 && msg_ptr == nullptr))
throw std::invalid_argument("Null pointer with non-zero length");
Expand Down
55 changes: 44 additions & 11 deletions src/hmac_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ namespace hmac_cpp {
return derived;
}

bool pbkdf2_hmac_sha256(const void* password_ptr, size_t password_len,
const void* salt_ptr, size_t salt_len,
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept {
bool pbkdf2(Pbkdf2Hash prf,
const void* password_ptr, size_t password_len,
const void* salt_ptr, size_t salt_len,
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept {
if ((password_len > 0 && password_ptr == nullptr) ||
(salt_len > 0 && salt_ptr == nullptr) ||
out_ptr == nullptr)
Expand All @@ -121,7 +122,22 @@ namespace hmac_cpp {
iterations > MAX_PBKDF2_ITERATIONS)
return false;

const size_t hlen = hmac_hash::SHA256::DIGEST_SIZE;
TypeHash hash_type = to_type_hash(prf);
size_t hlen = 0;
switch (hash_type) {
case TypeHash::SHA1:
hlen = hmac_hash::SHA1::DIGEST_SIZE;
break;
case TypeHash::SHA256:
hlen = hmac_hash::SHA256::DIGEST_SIZE;
break;
case TypeHash::SHA512:
hlen = hmac_hash::SHA512::DIGEST_SIZE;
break;
default:
return false;
}

uint64_t max_dk = (static_cast<uint64_t>(1) << 32) - 1;
max_dk *= hlen;
if (dk_len > max_dk)
Expand All @@ -144,27 +160,44 @@ namespace hmac_cpp {
salt_block[salt_len + 2] = static_cast<uint8_t>((i >> 8) & 0xFF);
salt_block[salt_len + 3] = static_cast<uint8_t>(i & 0xFF);

secure_buffer<uint8_t> u(std::move(get_hmac(password_ptr, password_len,
salt_block.data(), salt_block.size(),
TypeHash::SHA256)));
secure_buffer<uint8_t> t = u;
secure_buffer<uint8_t> u(hlen);
secure_buffer<uint8_t> t(hlen);
HmacContext ctx(hash_type);
ctx.init(password_ptr, password_len);
ctx.update(salt_block.data(), salt_block.size());
ctx.final(u.data(), hlen);
std::memcpy(t.data(), u.data(), hlen);

for (uint32_t j = 1; j < iterations; ++j) {
u = secure_buffer<uint8_t>(get_hmac(password_ptr, password_len,
u.data(), u.size(), TypeHash::SHA256));
for (size_t k = 0; k < t.size(); ++k) {
ctx.init(password_ptr, password_len);
ctx.update(u.data(), hlen);
ctx.final(u.data(), hlen);
for (size_t k = 0; k < hlen; ++k) {
t[k] ^= u[k];
}
}

size_t take = (i == l) ? r : hlen;
std::memcpy(out_ptr + pos, t.data(), take);
pos += take;

secure_zero(u.data(), u.size());
secure_zero(t.data(), t.size());
}

secure_zero(salt_block.data(), salt_block.size());
return true;
}

bool pbkdf2_hmac_sha256(const void* password_ptr, size_t password_len,
const void* salt_ptr, size_t salt_len,
uint32_t iterations, uint8_t* out_ptr, size_t dk_len) noexcept {
return pbkdf2(Pbkdf2Hash::Sha256,
password_ptr, password_len,
salt_ptr, salt_len,
iterations, out_ptr, dk_len);
}

std::vector<uint8_t> pbkdf2_with_pepper(
const void* password_ptr, size_t password_len,
const void* salt_ptr, size_t salt_len,
Expand Down
11 changes: 11 additions & 0 deletions test_all.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,17 @@ TEST(PBKDF2BufferApiTest, SHA256ArrayOutput) {
EXPECT_TRUE(std::equal(out.begin(), out.end(), ref.begin()));
}

TEST(PBKDF2BufferApiTest, GenericArrayOutput) {
auto salt = from_hex("000102030405060708090a0b0c0d0e0f");
std::string salt_str(salt.begin(), salt.end());
std::array<uint8_t,32> out{};
ASSERT_TRUE(hmac::pbkdf2(hmac::Pbkdf2Hash::Sha256,
std::string("password"), salt_str, 2, out));
std::vector<uint8_t> ref(32);
ASSERT_TRUE(PKCS5_PBKDF2_HMAC("password", 8, salt.data(), salt.size(), 2, EVP_sha256(), ref.size(), ref.data()));
EXPECT_TRUE(std::equal(out.begin(), out.end(), ref.begin()));
}

TEST(PBKDF2BufferApiTest, IterationsLimit) {
std::string salt(16, 'a');
std::array<uint8_t,32> out{};
Expand Down
Loading