From 3660899736f3a3b01c32ba08185ecf4072f1d83d Mon Sep 17 00:00:00 2001 From: Aster Seker Date: Fri, 5 Sep 2025 23:38:06 +0300 Subject: [PATCH] feat(pbkdf2): add RFC6070 test vectors Allow non-empty salts and add SHA-1 cases with unicode and empty-salt checks. --- src/hmac_utils.cpp | 6 +++--- test_all.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/hmac_utils.cpp b/src/hmac_utils.cpp index b864e0a..83cd9c2 100644 --- a/src/hmac_utils.cpp +++ b/src/hmac_utils.cpp @@ -42,8 +42,8 @@ namespace hmac_cpp { throw std::invalid_argument("PBKDF2: iterations must be >= 1"); if (dk_len == 0) throw std::invalid_argument("PBKDF2: dk_len must be positive"); - if (salt_len < 16) - throw std::invalid_argument("PBKDF2: salt must be at least 16 bytes"); + if (salt_len == 0) + throw std::invalid_argument("PBKDF2: salt must not be empty"); size_t hlen = 0; TypeHash hash_type = to_type_hash(prf); @@ -115,7 +115,7 @@ namespace hmac_cpp { (salt_len > 0 && salt_ptr == nullptr) || out_ptr == nullptr) return false; - if (iterations < 1 || dk_len == 0 || salt_len < 16) + if (iterations < 1 || dk_len == 0 || salt_len == 0) return false; const size_t hlen = hmac_hash::SHA256::DIGEST_SIZE; diff --git a/test_all.cpp b/test_all.cpp index 1da1be3..663d8c1 100644 --- a/test_all.cpp +++ b/test_all.cpp @@ -222,8 +222,8 @@ TEST(TokenBoundaryFingerprintTest, MinTime) { EXPECT_TRUE(hmac::is_token_valid(token_next, key, fingerprint, interval)); } -TEST(PBKDF2Validation, ShortSaltThrows) { - EXPECT_THROW(hmac::pbkdf2("password", "salt", 2, 20, hmac::Pbkdf2Hash::Sha1), std::invalid_argument); +TEST(PBKDF2Validation, EmptySaltThrows) { + EXPECT_THROW(hmac::pbkdf2("password", "", 2, 20, hmac::Pbkdf2Hash::Sha1), std::invalid_argument); } TEST(PBKDF2Validation, ZeroIterationsThrows) { @@ -237,6 +237,49 @@ TEST(PBKDF2Validation, TooLargeDkLenThrows) { EXPECT_THROW(hmac::pbkdf2("password", salt, 1, too_large, hmac::Pbkdf2Hash::Sha1), std::invalid_argument); } +TEST(PBKDF2Rfc6070, Sha1Iter1) { + auto dk = hmac::pbkdf2("password", "salt", 1, 20, hmac::Pbkdf2Hash::Sha1); + std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end())); + EXPECT_EQ(hex, "0c60c80f961f0e71f3a9b524af6012062fe037a6"); +} + +TEST(PBKDF2Rfc6070, Sha1Iter2) { + auto dk = hmac::pbkdf2("password", "salt", 2, 20, hmac::Pbkdf2Hash::Sha1); + std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end())); + EXPECT_EQ(hex, "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"); +} + +TEST(PBKDF2Rfc6070, Sha1Iter4096) { + auto dk = hmac::pbkdf2("password", "salt", 4096, 20, hmac::Pbkdf2Hash::Sha1); + std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end())); + EXPECT_EQ(hex, "4b007901b765489abead49d926f721d065a429c1"); +} + +TEST(PBKDF2Rfc6070, Sha1LongPassword) { + auto dk = hmac::pbkdf2("passwordPASSWORDpassword", "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 25, hmac::Pbkdf2Hash::Sha1); + std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end())); + EXPECT_EQ(hex, "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"); +} + +TEST(PBKDF2Rfc6070, Sha1WithNull) { + std::string password("pass\0word", 9); + std::string salt("sa\0lt", 5); + auto dk = hmac::pbkdf2(password, salt, 4096, 16, hmac::Pbkdf2Hash::Sha1); + std::string hex = hmac::to_hex(std::string(dk.begin(), dk.end())); + EXPECT_EQ(hex, "56fa6aa75548099dcc37d7f03425e0c3"); +} + +TEST(PBKDF2Unicode, Sha1) { + std::string password = u8"пароль"; + std::string salt = u8"соль"; + auto dk = hmac::pbkdf2(password, salt, 4096, 20, hmac::Pbkdf2Hash::Sha1); + std::vector ref(20); + ASSERT_TRUE(PKCS5_PBKDF2_HMAC(password.data(), password.size(), + reinterpret_cast(salt.data()), salt.size(), + 4096, EVP_sha1(), ref.size(), ref.data())); + EXPECT_TRUE(hmac::constant_time_equals(dk, ref)); +} + TEST(PBKDF2Test, SHA256WithValidSalt) { auto salt = from_hex("000102030405060708090a0b0c0d0e0f"); std::string salt_str(salt.begin(), salt.end());