diff --git a/include/hmac_cpp/hmac.hpp b/include/hmac_cpp/hmac.hpp index 7938d6d..b72ab3c 100644 --- a/include/hmac_cpp/hmac.hpp +++ b/include/hmac_cpp/hmac.hpp @@ -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 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 diff --git a/include/hmac_cpp/hmac_utils.hpp b/include/hmac_cpp/hmac_utils.hpp index c2b09d6..28d2477 100644 --- a/include/hmac_cpp/hmac_utils.hpp +++ b/include/hmac_cpp/hmac_utils.hpp @@ -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 or secure_buffer. + template + HMACCPP_DEPRECATED("use std::vector or secure_buffer overload") + inline bool pbkdf2(Pbkdf2Hash prf, + const std::string& password, + const std::string& salt, + uint32_t iterations, + std::array& 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& password, + const secure_buffer& 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 + inline bool pbkdf2(Pbkdf2Hash prf, + const secure_buffer& password, + const secure_buffer& salt, + uint32_t iterations, + std::array& 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 diff --git a/src/hmac.cpp b/src/hmac.cpp index 72e4409..8fbe202 100644 --- a/src/hmac.cpp +++ b/src/hmac.cpp @@ -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 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(block_size_); + secure_buffer 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(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 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 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"); diff --git a/src/hmac_utils.cpp b/src/hmac_utils.cpp index 1244e29..2ba6fad 100644 --- a/src/hmac_utils.cpp +++ b/src/hmac_utils.cpp @@ -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) @@ -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(1) << 32) - 1; max_dk *= hlen; if (dk_len > max_dk) @@ -144,27 +160,44 @@ namespace hmac_cpp { salt_block[salt_len + 2] = static_cast((i >> 8) & 0xFF); salt_block[salt_len + 3] = static_cast(i & 0xFF); - secure_buffer u(std::move(get_hmac(password_ptr, password_len, - salt_block.data(), salt_block.size(), - TypeHash::SHA256))); - secure_buffer t = u; + secure_buffer u(hlen); + secure_buffer 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(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 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 5f40508..8d31ee3 100644 --- a/test_all.cpp +++ b/test_all.cpp @@ -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 out{}; + ASSERT_TRUE(hmac::pbkdf2(hmac::Pbkdf2Hash::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())); +} + TEST(PBKDF2BufferApiTest, IterationsLimit) { std::string salt(16, 'a'); std::array out{};