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
23 changes: 23 additions & 0 deletions include/hmac_cpp/hmac_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,29 @@ namespace hmac_cpp {
iterations, dk_len, prf);
}

/// \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
/// \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_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;

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

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
55 changes: 55 additions & 0 deletions src/hmac_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdexcept>
#include <limits>
#include <algorithm>
#include <cstring>

namespace hmac_cpp {

Expand Down Expand Up @@ -107,6 +108,60 @@ 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 {
if ((password_len > 0 && password_ptr == nullptr) ||
(salt_len > 0 && salt_ptr == nullptr) ||
out_ptr == nullptr)
return false;
if (iterations < 1 || dk_len == 0 || salt_len < 16)
return false;

const size_t hlen = hmac_hash::SHA256::DIGEST_SIZE;
uint64_t max_dk = (static_cast<uint64_t>(1) << 32) - 1;
max_dk *= hlen;
if (dk_len > max_dk)
return false;

size_t l = (dk_len + hlen - 1) / hlen;
size_t r = dk_len - (l - 1) * hlen;

std::vector<uint8_t> salt_block;
salt_block.reserve(salt_len + 4);
salt_block.insert(salt_block.end(),
reinterpret_cast<const uint8_t*>(salt_ptr),
reinterpret_cast<const uint8_t*>(salt_ptr) + salt_len);
salt_block.resize(salt_len + 4);

size_t pos = 0;
for (size_t i = 1; i <= l; ++i) {
salt_block[salt_len ] = static_cast<uint8_t>((i >> 24) & 0xFF);
salt_block[salt_len + 1] = static_cast<uint8_t>((i >> 16) & 0xFF);
salt_block[salt_len + 2] = static_cast<uint8_t>((i >> 8) & 0xFF);
salt_block[salt_len + 3] = static_cast<uint8_t>(i & 0xFF);

std::vector<uint8_t> u = get_hmac(password_ptr, password_len,
salt_block.data(), salt_block.size(),
TypeHash::SHA256);
std::vector<uint8_t> t = u;
for (uint32_t j = 1; j < iterations; ++j) {
u = get_hmac(password_ptr, password_len,
u.data(), u.size(), TypeHash::SHA256);
for (size_t k = 0; k < t.size(); ++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;
}

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
23 changes: 23 additions & 0 deletions test_all.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <limits>
#include <cerrno>
#include <random>
#include <array>
#include <algorithm>
#include <openssl/evp.h>

#include "hmac_cpp/hmac.hpp"
Expand Down Expand Up @@ -224,6 +226,17 @@ TEST(PBKDF2Validation, ShortSaltThrows) {
EXPECT_THROW(hmac::pbkdf2("password", "salt", 2, 20, hmac::Pbkdf2Hash::Sha1), std::invalid_argument);
}

TEST(PBKDF2Validation, ZeroIterationsThrows) {
std::string salt(16, 'a');
EXPECT_THROW(hmac::pbkdf2("password", salt, 0, 32, hmac::Pbkdf2Hash::Sha256), std::invalid_argument);
}

TEST(PBKDF2Validation, TooLargeDkLenThrows) {
std::string salt(16, 'a');
size_t too_large = (static_cast<uint64_t>(1) << 32) * 20;
EXPECT_THROW(hmac::pbkdf2("password", salt, 1, too_large, hmac::Pbkdf2Hash::Sha1), std::invalid_argument);
}

TEST(PBKDF2Test, SHA256WithValidSalt) {
auto salt = from_hex("000102030405060708090a0b0c0d0e0f");
std::string salt_str(salt.begin(), salt.end());
Expand All @@ -233,6 +246,16 @@ TEST(PBKDF2Test, SHA256WithValidSalt) {
EXPECT_TRUE(hmac::constant_time_equals(dk, ref));
}

TEST(PBKDF2BufferApiTest, SHA256ArrayOutput) {
auto salt = from_hex("000102030405060708090a0b0c0d0e0f");
std::string salt_str(salt.begin(), salt.end());
std::array<uint8_t,32> out{};
ASSERT_TRUE(hmac::pbkdf2_hmac_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()));
}

// SHA512 vector from BoringSSL pbkdf_test.cc
TEST(PBKDF2Test, BoringSSL_SHA512) {
auto dk = hmac::pbkdf2("passwordPASSWORDpassword",
Expand Down
Loading