From be03a5314c7ed468b8d0dab49c2544da2e53bc39 Mon Sep 17 00:00:00 2001 From: hashlag <90853356+hashlag@users.noreply.github.com> Date: Thu, 21 Aug 2025 02:09:30 +0300 Subject: [PATCH] Add HMAC as per RFC 2104 draft implementation --- Chaos/Hash/Md4.hpp | 8 ++- Chaos/Hash/Md5.hpp | 8 ++- Chaos/Mac/Hmac.hpp | 115 ++++++++++++++++++++++++++++++++++++++ ChaosTests/CMakeLists.txt | 3 +- ChaosTests/HmacTests.cpp | 52 +++++++++++++++++ 5 files changed, 181 insertions(+), 5 deletions(-) create mode 100644 Chaos/Mac/Hmac.hpp create mode 100644 ChaosTests/HmacTests.cpp diff --git a/Chaos/Hash/Md4.hpp b/Chaos/Hash/Md4.hpp index b752429..6e29b82 100644 --- a/Chaos/Hash/Md4.hpp +++ b/Chaos/Hash/Md4.hpp @@ -162,6 +162,10 @@ struct Md4Hash : public Hash class Md4Hasher : public Hasher { public: + using HashType = Md4Hash; + + static constexpr size_t BLOCK_SIZE_BYTES = 64; + Md4Hasher() { ResetImpl(); @@ -178,7 +182,7 @@ public: MessageSizeBytes_ += UpdateImpl(begin, end); } - Md4Hash Finish() + HashType Finish() { uint64_t messageSizeBytesMod64 = MessageSizeBytes_ % 64; @@ -218,7 +222,7 @@ public: UpdateImpl(encodedMessageSizeBits, encodedMessageSizeBits + std::size(encodedMessageSizeBits)); - Md4Hash result; + HashType result; int_fast8_t i = 0; for (int_fast8_t reg = 0; reg < 4; ++reg) diff --git a/Chaos/Hash/Md5.hpp b/Chaos/Hash/Md5.hpp index e1e3ade..3f74c66 100644 --- a/Chaos/Hash/Md5.hpp +++ b/Chaos/Hash/Md5.hpp @@ -186,6 +186,10 @@ struct Md5Hash : public Hash class Md5Hasher : public Hasher { public: + using HashType = Md5Hash; + + static constexpr size_t BLOCK_SIZE_BYTES = 64; + Md5Hasher() { ResetImpl(); @@ -202,7 +206,7 @@ public: MessageSizeBytes_ += UpdateImpl(begin, end); } - Md5Hash Finish() + HashType Finish() { uint64_t messageSizeBytesMod64 = MessageSizeBytes_ % 64; @@ -242,7 +246,7 @@ public: UpdateImpl(encodedMessageSizeBits, encodedMessageSizeBits + std::size(encodedMessageSizeBits)); - Md5Hash result; + HashType result; int_fast8_t i = 0; for (int_fast8_t reg = 0; reg < 4; ++reg) diff --git a/Chaos/Mac/Hmac.hpp b/Chaos/Mac/Hmac.hpp new file mode 100644 index 0000000..2b983c3 --- /dev/null +++ b/Chaos/Mac/Hmac.hpp @@ -0,0 +1,115 @@ +#ifndef CHAOS_MAC_HMAC_HPP +#define CHAOS_MAC_HMAC_HPP + +#include +#include +#include + +#include "Hash/Hasher.hpp" + +namespace Chaos::Mac::Hmac +{ + +template, HasherImpl>>> +class Hmac +{ +public: + template + Hmac(InputIt keyBegin, InputIt keyEnd) + { + Key_ = GenerateKey(keyBegin, keyEnd); + + KeyType ipaddedKey = PadKey(Key_); + Hasher_.Update(ipaddedKey.begin(), ipaddedKey.end()); + } + + template + void Update(InputIt begin, InputIt end) + { + Hasher_.Update(begin, end); + } + + typename HasherImpl::HashType Finish() + { + auto innerDigest = Hasher_.Finish().GetRawDigest(); + + Hasher_.Reset(); + + KeyType opaddedKey = PadKey(Key_); + Hasher_.Update(opaddedKey.begin(), opaddedKey.end()); + Hasher_.Update(innerDigest.begin(), innerDigest.end()); + + return Hasher_.Finish(); + } + +private: + using KeyType = std::array; + + static constexpr uint8_t OPAD_BYTE = 0x5c; + static constexpr uint8_t IPAD_BYTE = 0x36; + + KeyType Key_; + HasherImpl Hasher_; + + template + static KeyType GenerateKey(InputIt keyBegin, InputIt keyEnd) + { + KeyType key; + key.fill(0); + + InputIt keyIt = keyBegin; + uint64_t idx = 0; + + for (; keyIt != keyEnd && idx < key.size(); + ++keyIt, ++idx) + { + key[idx] = *keyIt; + } + + if (keyIt != keyEnd) + { + HasherImpl keyHasher; + + keyHasher.Update(key.begin(), key.end()); + keyHasher.Update(keyIt, keyEnd); + + auto digest = keyHasher.Finish().GetRawDigest(); + static_assert(digest.size() <= HasherImpl::BLOCK_SIZE_BYTES); + + key.fill(0); + idx = 0; + + for (auto it = digest.begin(); + it != digest.end() && idx < key.size(); + ++it, ++idx) + { + key[idx] = *it; + } + } + + return key; + } + + template + static KeyType PadKey(const KeyType & key) + { + static_assert(PAD_BYTE == IPAD_BYTE || PAD_BYTE == OPAD_BYTE); + + KeyType paddedKey; + uint64_t idx = 0; + + for (auto it = key.cbegin(); + it != key.cend() && idx < paddedKey.size(); + ++it, ++idx) + { + paddedKey[idx] = (*it) ^ PAD_BYTE; + } + + return paddedKey; + } +}; + +} // namespace Chaos::Mac::Hmac + +#endif diff --git a/ChaosTests/CMakeLists.txt b/ChaosTests/CMakeLists.txt index c52357b..dad209c 100644 --- a/ChaosTests/CMakeLists.txt +++ b/ChaosTests/CMakeLists.txt @@ -12,7 +12,8 @@ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) set(ChaosTests_SOURCE Md4HasherTests.cpp - Md5HasherTests.cpp) + Md5HasherTests.cpp + HmacTests.cpp) add_executable(ChaosTests ${ChaosTests_SOURCE}) target_link_libraries(ChaosTests gtest gtest_main) diff --git a/ChaosTests/HmacTests.cpp b/ChaosTests/HmacTests.cpp new file mode 100644 index 0000000..1965310 --- /dev/null +++ b/ChaosTests/HmacTests.cpp @@ -0,0 +1,52 @@ +#include + +#include "Hash/Md5.hpp" +#include "Mac/Hmac.hpp" + +using namespace Chaos::Mac::Hmac; +using namespace Chaos::Hash::Md5; + +TEST(HmacTests, RFCTest) +{ + struct Helper + { + std::string operator()(const char * key, const char * data) const + { + Hmac hmac(key, key + strlen(key)); + hmac.Update(data, data + strlen(data)); + return hmac.Finish().ToHexString(); + } + }; + + Helper hmacMd5; + + { + const char * key = "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b" + "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"; + const char * data = "Hi There"; + + ASSERT_EQ("9294727a3638bb1c13f48ef8158bfc9d", hmacMd5(key, data)); + } + + { + const char * key = "Jefe"; + const char * data = "what do ya want for nothing?"; + + ASSERT_EQ("750c783e6ab0b503eaa86e310a5db738", hmacMd5(key, data)); + } + + { + uint8_t key[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa }; + uint8_t data[] = { 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd }; + + Hmac hmacMd5(key, key + std::size(key)); + hmacMd5.Update(data, data + std::size(data)); + + ASSERT_EQ("56be34521d144c88dbb8c733f0e8b3f6", hmacMd5.Finish().ToHexString()); + } +}