From fc2f100f0c5c7e0e4068c82d37a16910200ae9b6 Mon Sep 17 00:00:00 2001 From: hashlag Date: Tue, 6 Jan 2026 04:13:31 +0300 Subject: [PATCH 1/2] Add SHA1 draft implementation --- Chaos/Hash/Sha1.hpp | 297 ++++++++++++++++++++++++++++ ChaosTests/CMakeLists.txt | 1 + ChaosTests/Hash/Sha1HasherTests.cpp | 202 +++++++++++++++++++ 3 files changed, 500 insertions(+) create mode 100644 Chaos/Hash/Sha1.hpp create mode 100644 ChaosTests/Hash/Sha1HasherTests.cpp diff --git a/Chaos/Hash/Sha1.hpp b/Chaos/Hash/Sha1.hpp new file mode 100644 index 0000000..1dd74a7 --- /dev/null +++ b/Chaos/Hash/Sha1.hpp @@ -0,0 +1,297 @@ +#ifndef CHAOS_HASH_SHA1_HPP +#define CHAOS_HASH_SHA1_HPP + +#include +#include +#include + +#include "Hash.hpp" +#include "Hasher.hpp" + +namespace Chaos::Hash::Sha1::Inner_ +{ + +struct Buffer +{ + uint32_t Regs_[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 }; +}; + +using Block = std::array; + +struct Algorithm +{ +public: + static void UpdateBuffer(Buffer & buffer, const Block & block) + { + static_assert(std::tuple_size_v == 80); + + ScheduledBlock scheduled; + ScheduleBlock(scheduled, 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]; + uint32_t e = buffer.Regs_[4]; + + for (int_fast8_t i = 0; i < 20; ++i) + { + PerformRound(a, b, c, d, e, F0, scheduled[i], 0x5a827999); + } + + for (int_fast8_t i = 20; i < 40; ++i) + { + PerformRound(a, b, c, d, e, F20, scheduled[i], 0x6ed9eba1); + } + + for (int_fast8_t i = 40; i < 60; ++i) + { + PerformRound(a, b, c, d, e, F40, scheduled[i], 0x8f1bbcdc); + } + + for (int_fast8_t i = 60; i < 80; ++i) + { + PerformRound(a, b, c, d, e, F60, scheduled[i], 0xca62c1d6); + } + + buffer.Regs_[0] += a; + buffer.Regs_[1] += b; + buffer.Regs_[2] += c; + buffer.Regs_[3] += d; + buffer.Regs_[4] += e; + } + +private: + using ScheduledBlock = std::array; + using RoundFunction = uint32_t (*)(uint32_t b, uint32_t c, uint32_t d); + + static uint32_t Rotl(uint32_t v, int_fast8_t s) + { + return (v << s) | (v >> (32 - s)); + } + + static uint32_t F0(uint32_t b, uint32_t c, uint32_t d) + { + return (b & c) | ((~b) & d); + } + + static uint32_t F20(uint32_t b, uint32_t c, uint32_t d) + { + return b ^ c ^ d; + } + + static uint32_t F40(uint32_t b, uint32_t c, uint32_t d) + { + return (b & c) | (b & d) | (c & d); + } + + static uint32_t F60(uint32_t b, uint32_t c, uint32_t d) + { + return b ^ c ^ d; + } + + static void ScheduleBlock(ScheduledBlock & result, const Block & block) + { + static_assert(std::tuple_size_v == 16); + static_assert(std::tuple_size_v == 80); + + std::copy(block.begin(), block.end(), result.begin()); + + for (int_fast8_t t = 16; t < 80; ++t) + { + result[t] = Rotl(result[t - 3] ^ + result[t - 8] ^ + result[t - 14] ^ + result[t - 16], 1); + } + } + + static void PerformRound(uint32_t & a, uint32_t & b, uint32_t & c, + uint32_t & d, uint32_t & e, + RoundFunction func, uint32_t data, uint32_t k) + { + const uint32_t temp = Rotl(a, 5) + func(b, c, d) + e + data + k; + + e = d; + d = c; + c = Rotl(b, 30); + b = a; + a = temp; + } +}; + +} // namespace Chaos::Hash::Sha1::Inner_ + +namespace Chaos::Hash::Sha1 +{ + +struct Sha1Hash : public Hash +{ + std::array GetRawDigest() const + { + return RawDigest_; + } + + std::string ToHexString() const + { + char buf[41]; + + std::sprintf(buf, + "%02x%02x%02x%02x%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], + RawDigest_[16], RawDigest_[17], RawDigest_[18], RawDigest_[19]); + + return std::string(buf, buf + 40); + } + + std::array RawDigest_; +}; + +class Sha1Hasher : public Hasher +{ +public: + using HashType = Sha1Hash; + + static constexpr size_t BLOCK_SIZE_BYTES = 64; + + Sha1Hasher() + { + ResetImpl(); + } + + void Reset() + { + ResetImpl(); + } + + template + void Update(InputIt begin, InputIt end) + { + MessageSizeBytes_ += UpdateImpl(begin, end); + } + + HashType 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 >> 56) & 0xFF), + static_cast((messageSizeBits >> 48) & 0xFF), + static_cast((messageSizeBits >> 40) & 0xFF), + static_cast((messageSizeBits >> 32) & 0xFF), + static_cast((messageSizeBits >> 24) & 0xFF), + static_cast((messageSizeBits >> 16) & 0xFF), + static_cast((messageSizeBits >> 8) & 0xFF), + static_cast((messageSizeBits >> 0) & 0xFF), + }; + + static_assert(std::size(encodedMessageSizeBits) == 8); + + UpdateImpl(encodedMessageSizeBits, + encodedMessageSizeBits + std::size(encodedMessageSizeBits)); + + HashType result; + + int_fast8_t i = 0; + for (int_fast8_t reg = 0; reg < 5; ++reg) + { + for (int_fast8_t shift = 0; shift < 32; shift += 8) + { + result.RawDigest_[i++] = (Buffer_.Regs_[reg] >> (24 - 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); + + Inner_::Buffer Buffer_; + + Inner_::Block Block_; + int_fast8_t BlockSize_; + + uint32_t Word_; + int_fast8_t WordBytesPacked_; + + uint64_t MessageSizeBytes_; + + void ResetImpl() + { + Buffer_ = Inner_::Buffer(); + Block_.fill(0); + + BlockSize_ = 0; + Word_ = 0; + WordBytesPacked_ = 0; + MessageSizeBytes_ = 0; + } + + template + uint64_t UpdateImpl(InputIt begin, InputIt end) + { + uint64_t written = 0; + + for (InputIt it = begin; it != end; ++it, ++written) + { + Word_ |= (static_cast(*it) << (24 - (WordBytesPacked_ * 8))); + ++WordBytesPacked_; + + if (WordBytesPacked_ == 4) + { + Block_[BlockSize_++] = Word_; + WordBytesPacked_ = 0; + Word_ = 0; + + if (BlockSize_ == 16) + { + Inner_::Algorithm::UpdateBuffer(Buffer_, Block_); + BlockSize_ = 0; + } + } + } + + return written; + } +}; + +} // namespace Chaos::Hash::Sha1 + +#endif // CHAOS_HASH_SHA1_HPP diff --git a/ChaosTests/CMakeLists.txt b/ChaosTests/CMakeLists.txt index 6ee8ee2..ec4e058 100644 --- a/ChaosTests/CMakeLists.txt +++ b/ChaosTests/CMakeLists.txt @@ -13,6 +13,7 @@ FetchContent_MakeAvailable(googletest) set(ChaosTests_SOURCE Hash/Md4HasherTests.cpp Hash/Md5HasherTests.cpp + Hash/Sha1HasherTests.cpp Mac/HmacTests.cpp Cipher/Arc4GenTests.cpp Cipher/Arc4CryptTests.cpp diff --git a/ChaosTests/Hash/Sha1HasherTests.cpp b/ChaosTests/Hash/Sha1HasherTests.cpp new file mode 100644 index 0000000..145a181 --- /dev/null +++ b/ChaosTests/Hash/Sha1HasherTests.cpp @@ -0,0 +1,202 @@ +#include + +#include "Hash/Sha1.hpp" + +using namespace Chaos::Hash::Sha1; + +TEST(Sha1Tests, RfcTest) +{ + struct Helper + { + std::string operator()(const char * in) const + { + Sha1Hasher hasher; + hasher.Update(in, in + strlen(in)); + return hasher.Finish().ToHexString(); + } + }; + + Helper hash; + + ASSERT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709", hash("")); + ASSERT_EQ("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", hash("a")); + ASSERT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", hash("abc")); + ASSERT_EQ("84983e441c3bd26ebaae4aa1f95129e5e54670f1", hash("abcdbcdecdefdefgefghfghighijhi" + "jkijkljklmklmnlmnomnopnopq")); + ASSERT_EQ("e0c094e867ef46c350ef54a7f59dd60bed92ae83", hash("01234567012345670123456701234567" + "01234567012345670123456701234567")); +} + +TEST(Sha1Tests, PartialUpdateTest) +{ + { + // "a" + Sha1Hasher hasher; + + { + const char * in = "a"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = ""; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("86f7e437faa5a7fce15d1ddcb9eaeaea377667b8", hasher.Finish().ToHexString()); + } + + { + // "abc" + Sha1Hasher hasher; + + { + const char * in = "ab"; + hasher.Update(in, in + strlen(in)); + } + + { + const char * in = "c"; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", hasher.Finish().ToHexString()); + } + + { + // "message digest" + Sha1Hasher 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("c12252ceda8be8994d5fa0290a47231c1d16aae3", hasher.Finish().ToHexString()); + } + + { + // "12345678901234567890123456789012345678901234567890123456789012345678901234567890" + Sha1Hasher 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("50abf5706a150990a08b2c5ea40fa0e585554732", hasher.Finish().ToHexString()); + } +} + +TEST(Sha1Tests, LongInputTest) +{ + struct Helper + { + std::string operator()(const char * in) const + { + Sha1Hasher hasher; + hasher.Update(in, in + strlen(in)); + return hasher.Finish().ToHexString(); + } + }; + + Helper hash; + + // 2500 zeros ('0'). + ASSERT_EQ("79e7958997241a7ffe484e14cbe1a41a088aa70b", hash(std::string(2500, '0').c_str())); + // 1000 'a' followed by 1000 'b'. + ASSERT_EQ("246f7ca16d5edebf7a5df7ddeab7c044745942ec", hash((std::string(1000, 'a') + + std::string(1000, 'b')).c_str())); +} + +TEST(Sha1Tests, LongInputPartialUpdateTest) +{ + { + // 2500 zeros ('0'). + Sha1Hasher 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("79e7958997241a7ffe484e14cbe1a41a088aa70b", hasher.Finish().ToHexString()); + } + + { + // 1000 'a' followed by 1000 'b'. + Sha1Hasher 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("246f7ca16d5edebf7a5df7ddeab7c044745942ec", hasher.Finish().ToHexString()); + } +} + +TEST(Sha1Tests, ResetTest) +{ + Sha1Hasher hasher; + + { + const char * in = "abc"; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("a9993e364706816aba3e25717850c26c9cd0d89d", hasher.Finish().ToHexString()); + + hasher.Reset(); + + { + const char * in = "message digest"; + hasher.Update(in, in + strlen(in)); + } + + ASSERT_EQ("c12252ceda8be8994d5fa0290a47231c1d16aae3", hasher.Finish().ToHexString()); + + hasher.Reset(); + + ASSERT_EQ("da39a3ee5e6b4b0d3255bfef95601890afd80709", hasher.Finish().ToHexString()); +} -- 2.47.3 From 7f9430e0e6cf055dbf848ed3da76b355169d7e12 Mon Sep 17 00:00:00 2001 From: hashlag Date: Tue, 6 Jan 2026 04:30:07 +0300 Subject: [PATCH 2/2] Update HmacTests: add Sha1HmacTest --- ChaosTests/Mac/HmacTests.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/ChaosTests/Mac/HmacTests.cpp b/ChaosTests/Mac/HmacTests.cpp index 57c7239..c26317b 100644 --- a/ChaosTests/Mac/HmacTests.cpp +++ b/ChaosTests/Mac/HmacTests.cpp @@ -1,10 +1,12 @@ #include #include "Hash/Md5.hpp" +#include "Hash/Sha1.hpp" #include "Mac/Hmac.hpp" using namespace Chaos::Mac::Hmac; using namespace Chaos::Hash::Md5; +using namespace Chaos::Hash::Sha1; TEST(HmacTests, RfcTest) { @@ -51,6 +53,28 @@ TEST(HmacTests, RfcTest) } } +TEST(HmacTests, Sha1HmacTest) +{ + 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 hmacSha1; + + { + const char * key = "Two Generals'"; + const char * data = "Attack at dawn."; + + ASSERT_EQ("20ccda1c4de0e206f3a47056f2abd40f731ff3db", hmacSha1(key, data)); + } +} + TEST(HmacTests, LongKeyTest) { struct Helper -- 2.47.3