diff --git a/include/hmac_cpp/hmac_utils.hpp b/include/hmac_cpp/hmac_utils.hpp index 216667f..1539904 100644 --- a/include/hmac_cpp/hmac_utils.hpp +++ b/include/hmac_cpp/hmac_utils.hpp @@ -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 + inline bool pbkdf2_hmac_sha256(const std::string& password, + const std::string& salt, + uint32_t iterations, + std::array& out) noexcept { + return pbkdf2_hmac_sha256(password.data(), password.size(), + salt.data(), salt.size(), + iterations, out.data(), out.size()); + } + std::vector pbkdf2_with_pepper( const void* password_ptr, size_t password_len, const void* salt_ptr, size_t salt_len, diff --git a/src/hmac_utils.cpp b/src/hmac_utils.cpp index 05568c9..b864e0a 100644 --- a/src/hmac_utils.cpp +++ b/src/hmac_utils.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace hmac_cpp { @@ -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(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 salt_block; + salt_block.reserve(salt_len + 4); + salt_block.insert(salt_block.end(), + reinterpret_cast(salt_ptr), + reinterpret_cast(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((i >> 24) & 0xFF); + salt_block[salt_len + 1] = static_cast((i >> 16) & 0xFF); + salt_block[salt_len + 2] = static_cast((i >> 8) & 0xFF); + salt_block[salt_len + 3] = static_cast(i & 0xFF); + + std::vector u = get_hmac(password_ptr, password_len, + salt_block.data(), salt_block.size(), + TypeHash::SHA256); + std::vector 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 pbkdf2_with_pepper( const void* password_ptr, size_t password_len, const void* salt_ptr, size_t salt_len, diff --git a/test_all.cpp b/test_all.cpp index c61af8a..1da1be3 100644 --- a/test_all.cpp +++ b/test_all.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include "hmac_cpp/hmac.hpp" @@ -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(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()); @@ -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 out{}; + ASSERT_TRUE(hmac::pbkdf2_hmac_sha256(std::string("password"), salt_str, 2, out)); + std::vector 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",