// Copyright (C) 2007 EQ2EMulator Development Team, GPL v3 #pragma once #include #include #include #include class MD5 { public: // MD5 computation context structure struct MD5Context { std::uint32_t hash[4]; // Current hash state std::uint32_t bytes[2]; // Number of bytes processed (64-bit counter) std::uint32_t input[16]; // Input buffer for 64-byte blocks }; // Static method to generate MD5 hash from buffer static void Generate(const std::int8_t* buf, std::uint32_t len, std::int8_t digest[16]); // Initialize MD5 context for incremental hashing static void Init(struct MD5Context *context); // Update MD5 context with additional data static void Update(struct MD5Context *context, const std::int8_t *buf, std::uint32_t len); // Finalize MD5 computation and produce digest static void Final(std::int8_t digest[16], struct MD5Context *context); // Default constructor - initializes to zero hash MD5(); // Constructor from unsigned char buffer MD5(const unsigned char* buf, std::uint32_t len); // Constructor from char buffer MD5(const char* buf, std::uint32_t len); // Constructor from 16-byte binary hash MD5(const std::int8_t buf[16]); // Constructor from hex string representation MD5(const char* iMD5String); // Generate MD5 from null-terminated string void Generate(const char* iString); // Generate MD5 from buffer void Generate(const std::int8_t* buf, std::uint32_t len); // Set MD5 from 16-byte binary data bool Set(const std::int8_t buf[16]); // Set MD5 from 32-character hex string bool Set(const char* iMD5String); // Equality comparison operators bool operator== (const MD5& iMD5); bool operator== (const std::int8_t iMD5[16]); bool operator== (const char* iMD5String); // Assignment operators MD5& operator= (const MD5& iMD5); MD5* operator= (const MD5* iMD5); // Conversion to hex string operator const char* (); private: // Helper function to convert bytes to little-endian format static void byteSwap(std::uint32_t *buf, std::uint32_t words); // Core MD5 transformation function static void Transform(std::uint32_t hash[4], const std::uint32_t input[16]); // Helper function to check if character is valid hex digit static bool IsHexDigit(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } // Helper function to convert hex string to integer static std::uint32_t HexToInt(const char* hex) { std::uint32_t result = 0; for (int i = 0; hex[i] != '\0'; ++i) { result *= 16; if (hex[i] >= '0' && hex[i] <= '9') { result += hex[i] - '0'; } else if (hex[i] >= 'a' && hex[i] <= 'f') { result += hex[i] - 'a' + 10; } else if (hex[i] >= 'A' && hex[i] <= 'F') { result += hex[i] - 'A' + 10; } } return result; } std::int8_t pMD5[16]; // 16-byte MD5 hash storage char pMD5String[33]; // 32-character hex string representation + null terminator }; // Default constructor - zero out hash MD5::MD5() { std::memset(pMD5, 0, 16); } // Constructor from unsigned char buffer MD5::MD5(const unsigned char* buf, std::uint32_t len) { Generate(reinterpret_cast(buf), len, pMD5); } // Constructor from char buffer MD5::MD5(const char* buf, std::uint32_t len) { Generate(reinterpret_cast(buf), len, pMD5); } // Constructor from 16-byte binary hash MD5::MD5(const std::int8_t buf[16]) { Set(buf); } // Constructor from hex string MD5::MD5(const char* iMD5String) { Set(iMD5String); } // Generate MD5 from null-terminated string void MD5::Generate(const char* iString) { Generate(reinterpret_cast(iString), std::strlen(iString)); } // Generate MD5 from buffer using instance method void MD5::Generate(const std::int8_t* buf, std::uint32_t len) { Generate(buf, len, pMD5); } // Set MD5 from 16-byte binary data bool MD5::Set(const std::int8_t buf[16]) { std::memcpy(pMD5, buf, 16); return true; } // Set MD5 from 32-character hex string bool MD5::Set(const char* iMD5String) { char tmp[5] = { '0', 'x', 0, 0, 0 }; for (int i = 0; i < 16; i++) { tmp[2] = iMD5String[i * 2]; tmp[3] = iMD5String[(i * 2) + 1]; // Validate hex characters if (!IsHexDigit(tmp[2]) || !IsHexDigit(tmp[3])) return false; pMD5[i] = static_cast(HexToInt(tmp)); } return true; } // Convert MD5 to hex string representation MD5::operator const char* () { std::snprintf(pMD5String, sizeof(pMD5String), "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", static_cast(pMD5[0]), static_cast(pMD5[1]), static_cast(pMD5[2]), static_cast(pMD5[3]), static_cast(pMD5[4]), static_cast(pMD5[5]), static_cast(pMD5[6]), static_cast(pMD5[7]), static_cast(pMD5[8]), static_cast(pMD5[9]), static_cast(pMD5[10]), static_cast(pMD5[11]), static_cast(pMD5[12]), static_cast(pMD5[13]), static_cast(pMD5[14]), static_cast(pMD5[15])); return pMD5String; } // Equality comparison with another MD5 object bool MD5::operator== (const MD5& iMD5) { return std::memcmp(pMD5, iMD5.pMD5, 16) == 0; } // Equality comparison with 16-byte array bool MD5::operator== (const std::int8_t* iMD5) { return std::memcmp(pMD5, iMD5, 16) == 0; } // Equality comparison with hex string bool MD5::operator== (const char* iMD5String) { char tmp[5] = { '0', 'x', 0, 0, 0 }; for (int i = 0; i < 16; i++) { tmp[2] = iMD5String[i * 2]; tmp[3] = iMD5String[(i * 2) + 1]; if (pMD5[i] != static_cast(HexToInt(tmp))) return false; } return true; } // Assignment from another MD5 object MD5& MD5::operator= (const MD5& iMD5) { std::memcpy(pMD5, iMD5.pMD5, 16); return *this; } // Assignment from MD5 pointer MD5* MD5::operator= (const MD5* iMD5) { std::memcpy(pMD5, iMD5->pMD5, 16); return this; } // Byte-swap array of 32-bit words to little-endian format void MD5::byteSwap(std::uint32_t *buf, std::uint32_t words) { std::int8_t *p = reinterpret_cast(buf); do { *buf++ = (static_cast(static_cast(p[3]) << 8 | p[2]) << 16) | (static_cast(p[1]) << 8 | p[0]); p += 4; } while (--words); } // Generate MD5 digest from buffer using static method void MD5::Generate(const std::int8_t* buf, std::uint32_t len, std::int8_t digest[16]) { MD5Context ctx; Init(&ctx); Update(&ctx, buf, len); Final(digest, &ctx); } // Initialize MD5 context with standard initial values void MD5::Init(struct MD5Context *ctx) { ctx->hash[0] = 0x67452301; ctx->hash[1] = 0xefcdab89; ctx->hash[2] = 0x98badcfe; ctx->hash[3] = 0x10325476; ctx->bytes[1] = ctx->bytes[0] = 0; } // Update MD5 context with new data void MD5::Update(struct MD5Context *ctx, std::int8_t const *buf, std::uint32_t len) { std::uint32_t t = ctx->bytes[0]; if ((ctx->bytes[0] = t + len) < t) // Update 64-bit byte count ctx->bytes[1]++; // Carry from low to high t = 64 - (t & 0x3f); // Bytes available in ctx->input (>= 1) if (t > len) { std::memcpy(reinterpret_cast(ctx->input) + 64 - t, buf, len); return; } // First chunk is an odd size std::memcpy(reinterpret_cast(ctx->input) + 64 - t, buf, t); byteSwap(ctx->input, 16); Transform(ctx->hash, ctx->input); buf += t; len -= t; // Process data in 64-byte chunks while (len >= 64) { std::memcpy(ctx->input, buf, 64); byteSwap(ctx->input, 16); Transform(ctx->hash, ctx->input); buf += 64; len -= 64; } // Buffer any remaining bytes of data std::memcpy(ctx->input, buf, len); } // Finalize MD5 computation with padding and length void MD5::Final(std::int8_t digest[16], MD5Context *ctx) { int count = ctx->bytes[0] & 0x3F; // Bytes mod 64 std::int8_t *p = reinterpret_cast(ctx->input) + count; // Set the first byte of padding to 0x80. There is always room. *p++ = 0x80; // Bytes of zero padding needed to make 56 bytes (-8..55) count = 56 - 1 - count; if (count < 0) { // Padding forces an extra block std::memset(p, 0, count + 8); byteSwap(ctx->input, 16); Transform(ctx->hash, ctx->input); p = reinterpret_cast(ctx->input); count = 56; } std::memset(p, 0, count); byteSwap(ctx->input, 14); // Append 8 bytes of length in *bits* and transform ctx->input[14] = ctx->bytes[0] << 3; ctx->input[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; Transform(ctx->hash, ctx->input); byteSwap(ctx->hash, 4); std::memcpy(digest, ctx->hash, 16); std::memset(ctx, 0, sizeof(*ctx)); // Clear sensitive data } // MD5 transformation constants and helper functions #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) // Central step in MD5 algorithm #define MD5STEP(f,w,x,y,z,in,s) (w += f(x,y,z)+in, w = (w<>(32-s)) + x) // Core MD5 transformation function - processes one 64-byte block void MD5::Transform(std::uint32_t hash[4], const std::uint32_t input[16]) { std::uint32_t a = hash[0], b = hash[1], c = hash[2], d = hash[3]; // Round 1 - F1 function MD5STEP(F1, a, b, c, d, input[ 0]+0xd76aa478, 7); MD5STEP(F1, d, a, b, c, input[ 1]+0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, input[ 2]+0x242070db, 17); MD5STEP(F1, b, c, d, a, input[ 3]+0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, input[ 4]+0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, input[ 5]+0x4787c62a, 12); MD5STEP(F1, c, d, a, b, input[ 6]+0xa8304613, 17); MD5STEP(F1, b, c, d, a, input[ 7]+0xfd469501, 22); MD5STEP(F1, a, b, c, d, input[ 8]+0x698098d8, 7); MD5STEP(F1, d, a, b, c, input[ 9]+0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, input[10]+0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, input[11]+0x895cd7be, 22); MD5STEP(F1, a, b, c, d, input[12]+0x6b901122, 7); MD5STEP(F1, d, a, b, c, input[13]+0xfd987193, 12); MD5STEP(F1, c, d, a, b, input[14]+0xa679438e, 17); MD5STEP(F1, b, c, d, a, input[15]+0x49b40821, 22); // Round 2 - F2 function MD5STEP(F2, a, b, c, d, input[ 1]+0xf61e2562, 5); MD5STEP(F2, d, a, b, c, input[ 6]+0xc040b340, 9); MD5STEP(F2, c, d, a, b, input[11]+0x265e5a51, 14); MD5STEP(F2, b, c, d, a, input[ 0]+0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, input[ 5]+0xd62f105d, 5); MD5STEP(F2, d, a, b, c, input[10]+0x02441453, 9); MD5STEP(F2, c, d, a, b, input[15]+0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, input[ 4]+0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, input[ 9]+0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, input[14]+0xc33707d6, 9); MD5STEP(F2, c, d, a, b, input[ 3]+0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, input[ 8]+0x455a14ed, 20); MD5STEP(F2, a, b, c, d, input[13]+0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, input[ 2]+0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, input[ 7]+0x676f02d9, 14); MD5STEP(F2, b, c, d, a, input[12]+0x8d2a4c8a, 20); // Round 3 - F3 function MD5STEP(F3, a, b, c, d, input[ 5]+0xfffa3942, 4); MD5STEP(F3, d, a, b, c, input[ 8]+0x8771f681, 11); MD5STEP(F3, c, d, a, b, input[11]+0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, input[14]+0xfde5380c, 23); MD5STEP(F3, a, b, c, d, input[ 1]+0xa4beea44, 4); MD5STEP(F3, d, a, b, c, input[ 4]+0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, input[ 7]+0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, input[10]+0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, input[13]+0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, input[ 0]+0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, input[ 3]+0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, input[ 6]+0x04881d05, 23); MD5STEP(F3, a, b, c, d, input[ 9]+0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, input[12]+0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, input[15]+0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, input[ 2]+0xc4ac5665, 23); // Round 4 - F4 function MD5STEP(F4, a, b, c, d, input[ 0]+0xf4292244, 6); MD5STEP(F4, d, a, b, c, input[ 7]+0x432aff97, 10); MD5STEP(F4, c, d, a, b, input[14]+0xab9423a7, 15); MD5STEP(F4, b, c, d, a, input[ 5]+0xfc93a039, 21); MD5STEP(F4, a, b, c, d, input[12]+0x655b59c3, 6); MD5STEP(F4, d, a, b, c, input[ 3]+0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, input[10]+0xffeff47d, 15); MD5STEP(F4, b, c, d, a, input[ 1]+0x85845dd1, 21); MD5STEP(F4, a, b, c, d, input[ 8]+0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, input[15]+0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, input[ 6]+0xa3014314, 15); MD5STEP(F4, b, c, d, a, input[13]+0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, input[ 4]+0xf7537e82, 6); MD5STEP(F4, d, a, b, c, input[11]+0xbd3af235, 10); MD5STEP(F4, c, d, a, b, input[ 2]+0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, input[ 9]+0xeb86d391, 21); // Add the working variables back into the hash state hash[0] += a; hash[1] += b; hash[2] += c; hash[3] += d; }