diff --git a/Chaos/Md5.hpp b/Chaos/Md5.hpp new file mode 100644 index 0000000..e913828 --- /dev/null +++ b/Chaos/Md5.hpp @@ -0,0 +1,300 @@ +#ifndef CHAOS_MD5HASHER_HPP +#define CHAOS_MD5HASHER_HPP + +#include +#include +#include + +namespace Chaos::Md5 +{ + +struct Buffer +{ + uint32_t Regs_[4] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 }; +}; + +using Block = std::array; + +struct Algorithm +{ +public: + static void UpdateBuffer(Buffer & buffer, const Block & block) + { + uint32_t a = buffer.Regs_[0]; + uint32_t b = buffer.Regs_[1]; + uint32_t c = buffer.Regs_[2]; + uint32_t d = buffer.Regs_[3]; + + FF(a, b, c, d, block[ 0], 0xd76aa478, 7); + FF(d, a, b, c, block[ 1], 0xe8c7b756, 12); + FF(c, d, a, b, block[ 2], 0x242070db, 17); + FF(b, c, d, a, block[ 3], 0xc1bdceee, 22); + FF(a, b, c, d, block[ 4], 0xf57c0faf, 7); + FF(d, a, b, c, block[ 5], 0x4787c62a, 12); + FF(c, d, a, b, block[ 6], 0xa8304613, 17); + FF(b, c, d, a, block[ 7], 0xfd469501, 22); + FF(a, b, c, d, block[ 8], 0x698098d8, 7); + FF(d, a, b, c, block[ 9], 0x8b44f7af, 12); + FF(c, d, a, b, block[10], 0xffff5bb1, 17); + FF(b, c, d, a, block[11], 0x895cd7be, 22); + FF(a, b, c, d, block[12], 0x6b901122, 7); + FF(d, a, b, c, block[13], 0xfd987193, 12); + FF(c, d, a, b, block[14], 0xa679438e, 17); + FF(b, c, d, a, block[15], 0x49b40821, 22); + + GG(a, b, c, d, block[ 1], 0xf61e2562, 5); + GG(d, a, b, c, block[ 6], 0xc040b340, 9); + GG(c, d, a, b, block[11], 0x265e5a51, 14); + GG(b, c, d, a, block[ 0], 0xe9b6c7aa, 20); + GG(a, b, c, d, block[ 5], 0xd62f105d, 5); + GG(d, a, b, c, block[10], 0x02441453, 9); + GG(c, d, a, b, block[15], 0xd8a1e681, 14); + GG(b, c, d, a, block[ 4], 0xe7d3fbc8, 20); + GG(a, b, c, d, block[ 9], 0x21e1cde6, 5); + GG(d, a, b, c, block[14], 0xc33707d6, 9); + GG(c, d, a, b, block[ 3], 0xf4d50d87, 14); + GG(b, c, d, a, block[ 8], 0x455a14ed, 20); + GG(a, b, c, d, block[13], 0xa9e3e905, 5); + GG(d, a, b, c, block[ 2], 0xfcefa3f8, 9); + GG(c, d, a, b, block[ 7], 0x676f02d9, 14); + GG(b, c, d, a, block[12], 0x8d2a4c8a, 20); + + HH(a, b, c, d, block[ 5], 0xfffa3942, 4); + HH(d, a, b, c, block[ 8], 0x8771f681, 11); + HH(c, d, a, b, block[11], 0x6d9d6122, 16); + HH(b, c, d, a, block[14], 0xfde5380c, 23); + HH(a, b, c, d, block[ 1], 0xa4beea44, 4); + HH(d, a, b, c, block[ 4], 0x4bdecfa9, 11); + HH(c, d, a, b, block[ 7], 0xf6bb4b60, 16); + HH(b, c, d, a, block[10], 0xbebfbc70, 23); + HH(a, b, c, d, block[13], 0x289b7ec6, 4); + HH(d, a, b, c, block[ 0], 0xeaa127fa, 11); + HH(c, d, a, b, block[ 3], 0xd4ef3085, 16); + HH(b, c, d, a, block[ 6], 0x04881d05, 23); + HH(a, b, c, d, block[ 9], 0xd9d4d039, 4); + HH(d, a, b, c, block[12], 0xe6db99e5, 11); + HH(c, d, a, b, block[15], 0x1fa27cf8, 16); + HH(b, c, d, a, block[ 2], 0xc4ac5665, 23); + + II(a, b, c, d, block[ 0], 0xf4292244, 6); + II(d, a, b, c, block[ 7], 0x432aff97, 10); + II(c, d, a, b, block[14], 0xab9423a7, 15); + II(b, c, d, a, block[ 5], 0xfc93a039, 21); + II(a, b, c, d, block[12], 0x655b59c3, 6); + II(d, a, b, c, block[ 3], 0x8f0ccc92, 10); + II(c, d, a, b, block[10], 0xffeff47d, 15); + II(b, c, d, a, block[ 1], 0x85845dd1, 21); + II(a, b, c, d, block[ 8], 0x6fa87e4f, 6); + II(d, a, b, c, block[15], 0xfe2ce6e0, 10); + II(c, d, a, b, block[ 6], 0xa3014314, 15); + II(b, c, d, a, block[13], 0x4e0811a1, 21); + II(a, b, c, d, block[ 4], 0xf7537e82, 6); + II(d, a, b, c, block[11], 0xbd3af235, 10); + II(c, d, a, b, block[ 2], 0x2ad7d2bb, 15); + II(b, c, d, a, block[ 9], 0xeb86d391, 21); + + buffer.Regs_[0] += a; + buffer.Regs_[1] += b; + buffer.Regs_[2] += c; + buffer.Regs_[3] += d; + } + +private: + static uint32_t Rotl(uint32_t v, int_fast8_t s) + { + return (v << s) | (v >> (32 - s)); + } + + static uint32_t F(uint32_t x, uint32_t y, uint32_t z) + { + return (x & y) | ((~x) & z); + } + + static uint32_t G(uint32_t x, uint32_t y, uint32_t z) + { + return (x & z) | (y & (~z)); + } + + static uint32_t H(uint32_t x, uint32_t y, uint32_t z) + { + return x ^ y ^ z; + } + + static uint32_t I(uint32_t x, uint32_t y, uint32_t z) + { + return y ^ (x | (~z)); + } + + static void FF(uint32_t & a, uint32_t b, uint32_t c, uint32_t d, + uint32_t x, uint32_t t, int_fast8_t s) + { + a = b + Rotl(a + F(b, c, d) + x + t, s); + } + + static void GG(uint32_t & a, uint32_t b, uint32_t c, uint32_t d, + uint32_t x, uint32_t t, int_fast8_t s) + { + a = b + Rotl(a + G(b, c, d) + x + t, s); + } + + static void HH(uint32_t & a, uint32_t b, uint32_t c, uint32_t d, + uint32_t x, uint32_t t, int_fast8_t s) + { + a = b + Rotl(a + H(b, c, d) + x + t, s); + } + + static void II(uint32_t & a, uint32_t b, uint32_t c, uint32_t d, + uint32_t x, uint32_t t, int_fast8_t s) + { + a = b + Rotl(a + I(b, c, d) + x + t, s); + } +}; + +struct Hash +{ + std::string ToHexString() const + { + char buf[33]; + + std::sprintf(buf, + "%02x%02x%02x%02x%02x%02x%02x%02x" + "%02x%02x%02x%02x%02x%02x%02x%02x", + RawDigest[ 0], RawDigest[ 1], RawDigest[ 2], RawDigest[ 3], + RawDigest[ 4], RawDigest[ 5], RawDigest[ 6], RawDigest[ 7], + RawDigest[ 8], RawDigest[ 9], RawDigest[10], RawDigest[11], + RawDigest[12], RawDigest[13], RawDigest[14], RawDigest[15]); + + return std::string(buf, buf + 32); + } + + std::array RawDigest; +}; + +class Hasher +{ +public: + Hasher() + : BlockSize_(0) + , Word_(0) + , WordBytesPacked_(0) + , MessageSizeBytes_(0) + { + Block_.fill(0); + } + + template + void Update(InputIt begin, InputIt end) + { + MessageSizeBytes_ += UpdateImpl(begin, end); + } + + Hash Finish() + { + uint64_t messageSizeBytesMod64 = MessageSizeBytes_ % 64; + + int_fast8_t paddingNeededBytes; + + if (messageSizeBytesMod64 < 56) + { + paddingNeededBytes = 56 - messageSizeBytesMod64; + } + else if (messageSizeBytesMod64 > 56) + { + paddingNeededBytes = 120 - messageSizeBytesMod64; + } + else + { + paddingNeededBytes = 64; + } + + UpdateImpl(PAD_, PAD_ + paddingNeededBytes); + + const uint64_t messageSizeBits = MessageSizeBytes_ * 8; + + uint8_t encodedMessageSizeBits[] = + { + static_cast((messageSizeBits >> 0) & 0xFF), + static_cast((messageSizeBits >> 8) & 0xFF), + static_cast((messageSizeBits >> 16) & 0xFF), + static_cast((messageSizeBits >> 24) & 0xFF), + static_cast((messageSizeBits >> 32) & 0xFF), + static_cast((messageSizeBits >> 40) & 0xFF), + static_cast((messageSizeBits >> 48) & 0xFF), + static_cast((messageSizeBits >> 56) & 0xFF), + }; + + static_assert(std::size(encodedMessageSizeBits) == 8); + + UpdateImpl(encodedMessageSizeBits, + encodedMessageSizeBits + std::size(encodedMessageSizeBits)); + + Hash result; + + int_fast8_t i = 0; + for (int_fast8_t reg = 0; reg < 4; ++reg) + { + for (int_fast8_t shift = 0; shift < 32; shift += 8) + { + result.RawDigest[i++] = (Buffer_.Regs_[reg] >> shift) & 0xFF; + } + } + + return result; + } + +private: + static constexpr uint8_t PAD_[] = + { + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + static_assert(std::size(PAD_) == 64); + + Buffer Buffer_; + + Block Block_; + int_fast8_t BlockSize_; + + uint32_t Word_; + int_fast8_t WordBytesPacked_; + + uint64_t MessageSizeBytes_; + + template + uint64_t UpdateImpl(InputIt begin, InputIt end) + { + uint64_t written = 0; + + for (InputIt it = begin; it != end; ++it, ++written) + { + Word_ |= (static_cast(*it) << (WordBytesPacked_ * 8)); + ++WordBytesPacked_; + + if (WordBytesPacked_ == 4) + { + Block_[BlockSize_++] = Word_; + WordBytesPacked_ = 0; + Word_ = 0; + + if (BlockSize_ == 16) + { + Algorithm::UpdateBuffer(Buffer_, Block_); + BlockSize_ = 0; + } + } + } + + return written; + } +}; + +} // namespace Chaos::Md5 + +#endif diff --git a/ChaosTests/CMakeLists.txt b/ChaosTests/CMakeLists.txt index 0891ef0..f784ea2 100644 --- a/ChaosTests/CMakeLists.txt +++ b/ChaosTests/CMakeLists.txt @@ -9,7 +9,7 @@ FetchContent_Declare( set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) FetchContent_MakeAvailable(googletest) -add_executable(ChaosTests Md4HasherTests.cpp) +add_executable(ChaosTests Md4HasherTests.cpp Md5HasherTests.cpp) target_link_libraries(ChaosTests gtest gtest_main) target_include_directories(ChaosTests PRIVATE $ diff --git a/ChaosTests/MD5HasherTests.cpp b/ChaosTests/MD5HasherTests.cpp new file mode 100644 index 0000000..b5e3053 --- /dev/null +++ b/ChaosTests/MD5HasherTests.cpp @@ -0,0 +1,177 @@ +#include + +#include "Md5.hpp" + +using namespace Chaos; + +TEST(Md5Tests, RFCTest) +{ + struct Helper + { + std::string operator()(const char * in) const + { + Md5::Hasher hasher; + hasher.Update(in, in + strlen(in)); + return hasher.Finish().ToHexString(); + } + }; + + Helper hash; + + ASSERT_EQ("d41d8cd98f00b204e9800998ecf8427e", hash("")); + ASSERT_EQ("0cc175b9c0f1b6a831c399e269772661", hash("a")); + ASSERT_EQ("900150983cd24fb0d6963f7d28e17f72", hash("abc")); + ASSERT_EQ("f96b697d7cb7938d525a2f31aaf161d0", hash("message digest")); + ASSERT_EQ("c3fcd3d76192e4007dfb496cca67e13b", hash("abcdefghijklmnopqrstuvwxyz")); + ASSERT_EQ("d174ab98d277d9f5a5611c2c9f419d9f", hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")); + ASSERT_EQ("57edf4a22be3c955ac49da2e2107b67a", hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890")); +} + +TEST(Md5Tests, PartialUpdateTest) +{ + { + // "a" + Md5::Hasher hasher; + + { + const char * in = "a"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = ""; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("0cc175b9c0f1b6a831c399e269772661", hasher.Finish().ToHexString()); + } + + { + // "abc" + Md5::Hasher hasher; + + { + const char * in = "ab"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "c"; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("900150983cd24fb0d6963f7d28e17f72", hasher.Finish().ToHexString()); + } + + { + // "message digest" + Md5::Hasher hasher; + + { + const char * in = "me"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "ssage "; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "diges"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "t"; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("f96b697d7cb7938d525a2f31aaf161d0", hasher.Finish().ToHexString()); + } + + { + // "12345678901234567890123456789012345678901234567890123456789012345678901234567890" + Md5::Hasher hasher; + + { + const char * in = "12345678901234567890"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "12345678901234567890"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "12345678901234567890"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "12345678901234567890"; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("57edf4a22be3c955ac49da2e2107b67a", hasher.Finish().ToHexString()); + } +} + +TEST(Md5Tests, LongInputTest) +{ + struct Helper + { + std::string operator()(const char * in) const + { + Md5::Hasher hasher; + hasher.Update(in, in + strlen(in)); + return hasher.Finish().ToHexString(); + } + }; + + Helper hash; + + // 2500 zeros ('0'). + ASSERT_EQ("17aa376e13f65b7a4cb1a4913b5e748c", hash(std::string(2500, '0').c_str())); + // 1000 'a' followed by 1000 'b'. + ASSERT_EQ("5ede0802e614ef9cccc73dc02f04c032", hash((std::string(1000, 'a') + + std::string(1000, 'b')).c_str())); +} + +TEST(Md5Tests, LongInputPartialUpdateTest) +{ + { + // 2500 zeros ('0'). + Md5::Hasher hasher; + + std::string in(750, '0'); + + hasher.Update(in.begin(), in.begin() + 250); + hasher.Update(in.begin(), in.begin() + 500); + hasher.Update(in.begin(), in.begin() + 500); + hasher.Update(in.begin(), in.begin() + 750); + hasher.Update(in.begin(), in.begin() + 333); + hasher.Update(in.begin(), in.begin() + 167); + + ASSERT_EQ("17aa376e13f65b7a4cb1a4913b5e748c", hasher.Finish().ToHexString()); + } + + { + // 1000 'a' followed by 1000 'b'. + Md5::Hasher hasher; + + std::string inA(1000, 'a'); + std::string inB(1000, 'b'); + + hasher.Update(inA.begin(), inA.begin() + 100); + hasher.Update(inA.begin(), inA.begin() + 255); + hasher.Update(inA.begin(), inA.begin() + 645); + + hasher.Update(inB.begin(), inB.begin() + 33); + hasher.Update(inB.begin(), inB.begin() + 701); + hasher.Update(inB.begin(), inB.begin() + 266); + + ASSERT_EQ("5ede0802e614ef9cccc73dc02f04c032", hasher.Finish().ToHexString()); + } +}