diff --git a/hash_lib.cpp b/hash_lib.cpp index 089f762..c19ec30 100644 --- a/hash_lib.cpp +++ b/hash_lib.cpp @@ -11,6 +11,8 @@ #define SHA224_DIGEST_LENGTH 28 #define SHA1_DIGEST_LENGTH 20 #define SHA1_BLOCK_SIZE 64 +#define MD5_DIGEST_LENGTH 16 +#define MD5_BLOCK_SIZE 64 using namespace hash_lib; @@ -721,3 +723,137 @@ size_t SHA1::hashBlocks(const uint8_t* m, size_t pos, size_t len) { } return pos; } + +MD5::MD5() { + this->reset(); +} + +int MD5::digestLength() { + return MD5_DIGEST_LENGTH; // MD5 produces a 128-bit hash value (16 bytes) +} + +int MD5::blockSize() { + return MD5_BLOCK_SIZE; // MD5 processes data in 512-bit blocks (64 bytes) +} + +void MD5::_initState() { + state[0] = 0x67452301; + state[1] = 0xEFCDAB89; + state[2] = 0x98BADCFE; + state[3] = 0x10325476; +} + +Hash* MD5::reset() { + _initState(); + _bufferLength = 0; + _bytesHashed = 0; + _finished = false; + return this; +} + +void MD5::clean() { + cleanBuffer(_buffer); + cleanBuffer(_temp); + reset(); +} + +Hash* MD5::update(const uint8_t* data, size_t len) { + if (_finished) return this; + size_t dataPos = 0; + _bytesHashed += len; + if (_bufferLength > 0) { + while (_bufferLength < MD5_BLOCK_SIZE && len > 0) { + _buffer[_bufferLength++] = data[dataPos++]; + len--; + } + if (_bufferLength == MD5_BLOCK_SIZE) { + hashBlocks(_buffer, 0, MD5_BLOCK_SIZE); + _bufferLength = 0; + } + } + if (len >= MD5_BLOCK_SIZE) { + dataPos = hashBlocks(data, dataPos, len); + len %= MD5_BLOCK_SIZE; + } + while (len > 0) { + _buffer[_bufferLength++] = data[dataPos++]; + len--; + } + return this; +} + +Hash* MD5::finish(uint8_t* data, size_t len) { + if (!_finished) { + size_t bytesHashed = _bytesHashed; + size_t left = _bufferLength; + uint64_t bitLen = bytesHashed << 3; + size_t padLength = (bytesHashed % MD5_BLOCK_SIZE) < 56 ? 64 : 128; + _buffer[left] = 0x80; + memset(_buffer + left + 1, 0, padLength - left - 9); + cstr_write_uint64(_buffer + padLength - 8, bitLen, 0); + hashBlocks(_buffer, 0, padLength); + _finished = true; + } + for (int i = 0; i < this->digestLength() / 4 && i < len / 4; i++) { + cstr_write_uint32(data + i * 4, state[i], 0); + } + return this; +} + +const uint8_t MD5_s[] = { + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, +}; +const uint32_t MD5_K[] = { + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, + 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, + 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, + 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, + 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, + 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 +}; + +size_t MD5::hashBlocks(const uint8_t* m, size_t pos, size_t len) { + while (len >= 64) { + uint32_t a = state[0], b = state[1], c = state[2], d = state[3]; + for (int i = 0; i < 16; i++) { + size_t j = i * 4 + pos; + _temp[i] = cstr_read_uint32(m + j, 0); + } + for (int i = 0; i < 64; i++) { + uint32_t f, g; + if (i < 16) { + f = d ^ (b & (c ^ d)); + g = i; + } else if (i < 32) { + f = c ^ (d & (b ^ c)); + g = (5 * i + 1) % 16; + } else if (i < 48) { + f = b ^ c ^ d; + g = (3 * i + 5) % 16; + } else { + f = c ^ (b | ~d); + g = (7 * i) % 16; + } + f += a + MD5_K[i] + _temp[g]; + a = d; + d = c; + c = b; + b = b + ((f << MD5_s[i]) | (f >> (32 - MD5_s[i]))); + } + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + pos += 64; + len -= 64; + } + return pos; +} diff --git a/hash_lib.h b/hash_lib.h index f42d93d..27d9aeb 100644 --- a/hash_lib.h +++ b/hash_lib.h @@ -17,24 +17,67 @@ namespace hash_lib { * The byte length of the block */ virtual int blockSize() = 0; + /** + * Update the hash with data + * @param data Data to be hashed + * @param len Length of the data + * @return this + */ virtual Hash* update(const uint8_t* data, size_t len) = 0; + /** + * Update the hash with data + * @param data Data to be hashed + * @return this + */ template Hash* update(const uint8_t(&data)[T]) { return this->update(data, T); } Hash* update(const std::string& data); Hash* update(const std::vector& data); + /** + * Update the hash with data from a file + * @param f File pointer + * @return this + */ Hash* update(FILE* f); + /** + * Reset the hash + * @return this + */ virtual Hash* reset() = 0; + /** + * Finish the hash and return the result + * @param data Buffer to store the result + * @param len Length of the buffer + * @return this + */ virtual Hash* finish(uint8_t* data, size_t len) = 0; + /** + * Finish the hash and return the result + * @param data Buffer to store the result + * @return this + */ template Hash* finish(uint8_t(&data)[T]) { return this->finish(data, T); } Hash* finish(std::string& data); Hash* finish(std::vector& data); + /** + * Finish the hash and return the result + * @return The hash result + */ std::vector digest(); + /** + * Reset the hash and clean the buffer + * @return this + */ virtual void clean() = 0; + /** + * Finish the hash and return the result in hex format + * @return The hash result in hex format + */ std::string hexDigest(); }; class SHA512: public Hash { @@ -126,6 +169,28 @@ namespace hash_lib { bool _finished = false; size_t hashBlocks(const uint8_t* m, size_t pos, size_t len); }; + class MD5: public Hash { + public: + MD5(); + virtual int digestLength() override; + int blockSize() override; + Hash* update(const uint8_t* data, size_t len) override; + using Hash::update; + Hash* reset() override; + Hash* finish(uint8_t* data, size_t len) override; + using Hash::finish; + void clean() override; + protected: + uint32_t state[4]; + virtual void _initState(); + private: + uint32_t _temp[64]; + uint8_t _buffer[128]; + size_t _bufferLength = 0; + size_t _bytesHashed = 0; + bool _finished = false; + size_t hashBlocks(const uint8_t* m, size_t pos, size_t len); + }; template class HMAC: public Hash { public: @@ -190,61 +255,61 @@ namespace hash_lib { std::vector _pad; bool _finished = false; }; - template - std::vector hash(const uint8_t* data, size_t len) { - H h; + template + std::vector hash(const uint8_t* data, size_t len, Args... args) { + H h(args...); h.update(data, len); return h.digest(); } - template - std::vector hash(const std::string& data) { - H h; + template + std::vector hash(const std::string& data, Args... args) { + H h(args...); h.update(data); return h.digest(); } - template - std::vector hash(const std::vector& data) { - H h; + template + std::vector hash(const std::vector& data, Args... args) { + H h(args...); h.update(data); return h.digest(); } - template - std::vector hash(const uint8_t(&data)[T]) { - H h; + template + std::vector hash(const uint8_t(&data)[T], Args... args) { + H h(args...); h.update(data); return h.digest(); } - template - std::string hashHex(const uint8_t* data, size_t len) { - H h; + template + std::string hashHex(const uint8_t* data, size_t len, Args... args) { + H h(args...); h.update(data, len); return h.hexDigest(); } - template - std::string hashHex(const std::string& data) { - H h; + template + std::string hashHex(const std::string& data, Args... args) { + H h(args...); h.update(data); return h.hexDigest(); } - template - std::string hashHex(const std::vector& data) { - H h; + template + std::string hashHex(const std::vector& data, Args... args) { + H h(args...); h.update(data); return h.hexDigest(); } - template - std::string hashHex(const uint8_t(&data)[T]) { - H h; + template + std::string hashHex(const uint8_t(&data)[T], Args... args) { + H h(args...); h.update(data); return h.hexDigest(); } - template - std::vector hashFile(const std::string& filePath) { + template + std::vector hashFile(const std::string& filePath, Args... args) { FILE* f = fileop::fopen(filePath.c_str(), "rb"); if (!f) { return {}; } - H h; + H h(args...); h.update(f); if (ferror(f)) { fileop::fclose(f); @@ -253,13 +318,13 @@ namespace hash_lib { fileop::fclose(f); return h.digest(); } - template - std::string hashHexFile(const std::string& filePath) { + template + std::string hashHexFile(const std::string& filePath, Args... args) { FILE* f = fileop::fopen(filePath.c_str(), "rb"); if (!f) { return ""; } - H h; + H h(args...); h.update(f); if (ferror(f)) { fileop::fclose(f); diff --git a/test/hash_lib_test.cpp b/test/hash_lib_test.cpp index f8748cb..d4b2261 100644 --- a/test/hash_lib_test.cpp +++ b/test/hash_lib_test.cpp @@ -6,11 +6,16 @@ using namespace hash_lib; TEST(HashLibTest, SHA512ClassTest) { SHA512 sha512; sha512.update("Hello, World!"); + SHA512 another(sha512); GTEST_ASSERT_EQ(sha512.hexDigest(), "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387"); auto re = hash("Hello, World!"); GTEST_ASSERT_EQ(re, sha512.digest()); auto hexRe = hashHex("Hello, World!"); GTEST_ASSERT_EQ(hexRe, sha512.hexDigest()); + another.update("Hello, World!"); + GTEST_ASSERT_EQ(hashHex("Hello, World!Hello, World!"), another.hexDigest()); + sha512.update("Hello, World!"); + GTEST_ASSERT_EQ(hashHex("Hello, World!"), sha512.hexDigest()); } TEST(HashLibTest, SHA512Test) { @@ -49,6 +54,12 @@ TEST(HashLibTest, SHA1Test) { GTEST_ASSERT_EQ(hashHex("随便来一些中文。测试超过一百二十八字节时的状况。用于测试是否存在问题。还是不够长呢。啊啊啊。"), "21c05e3532d593ec382b8e361d43a17e8fb8774a"); } +TEST(HashLibTest, MD5Test) { + GTEST_ASSERT_EQ(hashHex(""), "d41d8cd98f00b204e9800998ecf8427e"); + GTEST_ASSERT_EQ(hashHex("Hello, World!"), "65a8e27d8879283831b664bd8b7f0ad4"); + GTEST_ASSERT_EQ(hashHex("随便来一些中文。测试超过一百二十八字节时的状况。用于测试是否存在问题。还是不够长呢。啊啊啊。"), "bbf4521fa0a37519d277660cb10805d1"); +} + TEST(HashLibTest, HMACClassTest) { HMAC hmac("key"); hmac.update("Hello, World!"); @@ -57,3 +68,9 @@ TEST(HashLibTest, HMACClassTest) { hmac.update("Hello, World!"); GTEST_ASSERT_EQ(hmac.hexDigest(), "7b735ac190ebd1432d56f95ae2aea5a04a23128f4c228e299b7a49fb7561de8cc8f4fdf4486dc743dfd07827d617273aab42b3bf819d243ded322fac167419f1"); } + +TEST(HashLibTest, HMACTest) { + GTEST_ASSERT_EQ(hashHex>("123", "abc"), "1bb47a2e086bfab3a86e3843ffd665fead90f0ef46cf2894c56a194fb18158685e9fd364bde008d5f2cb04e649c7396adda38dc5617a9dd56ab981920ae13188"); + GTEST_ASSERT_EQ(hashHex>("1dakljda", "abc"), "f1d5dadc84af9f826601f1d6682c1a5cf1a60751"); + GTEST_ASSERT_EQ(hashHex>("随便来一些中文。测试超过一百二十八字节时的状况。用于测试是否存在问题。还是不够长呢。啊啊啊。", "abc"), "7baac227c3f24787e906d9b8e11283945f5d38b3"); +}