diff --git a/src/impl/algorithm.inc b/src/impl/algorithm.inc deleted file mode 100644 index c320099..0000000 --- a/src/impl/algorithm.inc +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#define MD5_R1 A, B, C, D -#define MD5_R2 D, A, B, C -#define MD5_R3 C, D, A, B -#define MD5_R4 B, C, D, A - -#define MD5_F(x, y, z) (z ^ (x & (y ^ z))) -#define MD5_G(x, y, z) (y ^ (z & (x ^ y))) -#define MD5_H(x, y, z) (x ^ y ^ z) -#define MD5_I(x, y, z) (y ^ (x | ~z)) - -#define MD5_CYC(w, s) (w = (w << s) | (w >> (32 - s))) - -#define MD5_STEP(i, f, a, b, c, d) \ - do { \ - a += f(b, c, d) + block[K(i)] + T(i); \ - MD5_CYC(a, S(i)); \ - a += b; \ - } while (0) - -#define MD5_FF(i, ...) MD5_STEP((0x00 | i), MD5_F, __VA_ARGS__) -#define MD5_GG(i, ...) MD5_STEP((0x10 | i), MD5_G, __VA_ARGS__) -#define MD5_HH(i, ...) MD5_STEP((0x20 | i), MD5_H, __VA_ARGS__) -#define MD5_II(i, ...) MD5_STEP((0x30 | i), MD5_I, __VA_ARGS__) - -#define MD5_ROUND(OP) \ - OP(0x0, MD5_R1); OP(0x1, MD5_R2); OP(0x2, MD5_R3); OP(0x3, MD5_R4); \ - OP(0x4, MD5_R1); OP(0x5, MD5_R2); OP(0x6, MD5_R3); OP(0x7, MD5_R4); \ - OP(0x8, MD5_R1); OP(0x9, MD5_R2); OP(0xa, MD5_R3); OP(0xb, MD5_R4); \ - OP(0xc, MD5_R1); OP(0xd, MD5_R2); OP(0xe, MD5_R3); OP(0xf, MD5_R4); - -#define MD5_UPDATE \ - MD5_ROUND(MD5_FF) MD5_ROUND(MD5_GG) MD5_ROUND(MD5_HH) MD5_ROUND(MD5_II) - -#include "value.inc" - -namespace md5::impl { - -struct md5_ctx { - uint32_t A = value::kA; - uint32_t B = value::kB; - uint32_t C = value::kC; - uint32_t D = value::kD; - uint64_t size = 0; // processed size in byte -}; - -/// MD5 data block index, input between 0 and 63. -constexpr int K(const int i) { - constexpr int step[4] = {1, 5, 3, 7}; - constexpr int begin[4] = {0, 1, 5, 0}; - return (begin[i >> 4] + step[i >> 4] * i) & 0b1111; -} - -/// MD5 circular shift times, input between 0 and 63. -constexpr int S(const int i) { - constexpr int shift[4][4] = { - {7, 12, 17, 22}, - {5, 9, 14, 20}, - {4, 11, 16, 23}, - {6, 10, 15, 21}, - }; - return shift[i >> 4][i & 0b11]; -} - -/// MD5 T-table constant, input between 0 and 63. -constexpr uint32_t T(const int i) { - return value::kT[i]; -} - -static_assert(K(0) != K(63), "invalid constexpr"); -static_assert(S(0) != S(63), "invalid constexpr"); -static_assert(T(0) != T(63), "invalid constexpr"); - -} // namespace md5::impl diff --git a/src/impl/constexpr.inc b/src/impl/constexpr.inc index d2a720f..1e7965e 100644 --- a/src/impl/constexpr.inc +++ b/src/impl/constexpr.inc @@ -1,49 +1,71 @@ #pragma once -namespace md5::impl { +namespace md5::ce { -struct md5_ce_ctx { +struct md5_ctx { uint32_t A = value::kA; uint32_t B = value::kB; uint32_t C = value::kC; uint32_t D = value::kD; }; -struct md5_ce { - const char *data; - uint64_t data_len, padded_len; +struct md5_data { + const char *ptr; + uint64_t len, padded_len; - constexpr md5_ce(const char *data, const uint64_t len) - : data(data), data_len(len), padded_len((len + 64 + 8) & ~0b111111ULL) {} + constexpr md5_data(const char *data, const uint64_t len) + : ptr(data), len(len), padded_len((len + 64 + 8) & ~0b111111ULL) {} }; using Block = std::array; // single md5 block with 64 bytes /// Get the data and padding byte of the specified index. -constexpr uint8_t GetByte(const md5_ce *ctx, const uint64_t index) { - if (index < ctx->data_len) // message data - return ctx->data[index]; - if (index == ctx->data_len) // padding flag +constexpr uint8_t GetByte(const md5_data &data, const uint64_t index) { + if (index < data.len) // message data + return data.ptr[index]; + if (index == data.len) // padding flag return 0x80; - if (index < ctx->padded_len - 8) // padding content + if (index < data.padded_len - 8) // padding content return 0x00; - const auto offset = (index + 8 - ctx->padded_len) * 8; - return static_cast(0xff & (ctx->data_len * 8) >> offset); + const auto offset = (index + 8 - data.padded_len) * 8; + return static_cast(0xff & (data.len * 8) >> offset); } /// Get the MD5 block content at the specified index. -constexpr Block GetBlock(const md5_ce *ctx, const uint64_t index) { +constexpr Block GetBlock(const md5_data &data, const uint64_t index) { Block block {}; for (int i = 0; i < 16; ++i) { const auto offset = index + i * 4; - (block[i] <<= 8) |= GetByte(ctx, offset + 3); - (block[i] <<= 8) |= GetByte(ctx, offset + 2); - (block[i] <<= 8) |= GetByte(ctx, offset + 1); - (block[i] <<= 8) |= GetByte(ctx, offset + 0); + (block[i] <<= 8) |= GetByte(data, offset + 3); + (block[i] <<= 8) |= GetByte(data, offset + 2); + (block[i] <<= 8) |= GetByte(data, offset + 1); + (block[i] <<= 8) |= GetByte(data, offset + 0); } return block; } +/// Apply MD5 round process with 64 times calculate. +constexpr md5_ctx Round(const Block &block, md5_ctx ctx) { + constexpr auto calc = [](const md5_ctx &c, const int i) { + if (i < 0x10) + return c.D ^ (c.B & (c.C ^ c.D)); + if (i < 0x20) + return c.C ^ (c.D & (c.B ^ c.C)); + if (i < 0x30) + return c.B ^ c.C ^ c.D; + return c.C ^ (c.B | ~c.D); + }; + + for (int i = 0; i < 64; ++i) { + const auto a = ctx.A + calc(ctx, i) + block[value::K(i)] + value::T(i); + ctx.A = ctx.D; + ctx.D = ctx.C; + ctx.C = ctx.B; + ctx.B += a << value::S(i) | a >> (32 - value::S(i)); + } + return ctx; +} + /// Convert origin MD5 integers to hexadecimal character array. constexpr std::array DigestCE(const std::array &ctx) { std::array result {}; @@ -56,32 +78,12 @@ constexpr std::array DigestCE(const std::array &ctx) { return result; } -constexpr uint32_t Calc(const md5_ce_ctx &ctx, const int i) { - if (i < 0x10) - return ctx.D ^ (ctx.B & (ctx.C ^ ctx.D)); - if (i < 0x20) - return ctx.C ^ (ctx.D & (ctx.B ^ ctx.C)); - if (i < 0x30) - return ctx.B ^ ctx.C ^ ctx.D; - return ctx.C ^ (ctx.B | ~ctx.D); -} - -constexpr md5_ce_ctx Round(const Block &block, md5_ce_ctx ctx) { - for (int i = 0; i < 64; ++i) { - const auto a = ctx.A + Calc(ctx, i) + block[K(i)] + T(i); - ctx.A = ctx.D; - ctx.D = ctx.C; - ctx.C = ctx.B; - ctx.B += a << S(i) | a >> (32 - S(i)); - } - return ctx; -} - -constexpr std::array MD5::HashCE(const char *data, const uint64_t len) { - md5_ce_ctx ctx; - const md5_ce md5(data, len); +/// MD5 hash implement based on constexpr. +constexpr std::array Hash(const char *data, const uint64_t len) { + md5_ctx ctx; + const md5_data md5(data, len); for (uint32_t index = 0; index < md5.padded_len; index += 64) { - const auto [A, B, C, D] = Round(GetBlock(&md5, index), ctx); + const auto [A, B, C, D] = Round(GetBlock(md5, index), ctx); ctx.A += A; ctx.B += B; ctx.C += C; @@ -90,6 +92,6 @@ constexpr std::array MD5::HashCE(const char *data, const uint64_t len) return DigestCE({ctx.A, ctx.B, ctx.C, ctx.D}); } -static_assert(MD5::HashCE("")[0] == 'd'); +static_assert(Hash("", 0)[0] == 'd'); -} // namespace md5::impl +} // namespace md5::ce diff --git a/src/impl/core.cc b/src/impl/core.cc index 53db601..3e48130 100644 --- a/src/impl/core.cc +++ b/src/impl/core.cc @@ -1,8 +1,38 @@ +#include "md5.h" #include -#include "md5.h" +using md5::MD5; +using md5::value::K; +using md5::value::S; +using md5::value::T; + +#define R1 A, B, C, D +#define R2 D, A, B, C +#define R3 C, D, A, B +#define R4 B, C, D, A + +#define F(x, y, z) (z ^ (x & (y ^ z))) +#define G(x, y, z) (y ^ (z & (x ^ y))) +#define H(x, y, z) (x ^ y ^ z) +#define I(x, y, z) (y ^ (x | ~z)) + +#define STEP(i, f, a, b, c, d) \ + do { \ + a += f(b, c, d) + block[K(i)] + T(i); \ + a = a << S(i) | a >> (32 - S(i)); \ + a += b; \ + } while (0) + +#define FF(i, ...) STEP((0x00 | i), F, __VA_ARGS__) +#define GG(i, ...) STEP((0x10 | i), G, __VA_ARGS__) +#define HH(i, ...) STEP((0x20 | i), H, __VA_ARGS__) +#define II(i, ...) STEP((0x30 | i), I, __VA_ARGS__) -using ::md5::impl::MD5; +#define MD5_UPDATE(OP) \ + OP(0x0, R1); OP(0x1, R2); OP(0x2, R3); OP(0x3, R4); \ + OP(0x4, R1); OP(0x5, R2); OP(0x6, R3); OP(0x7, R4); \ + OP(0x8, R1); OP(0x9, R2); OP(0xa, R3); OP(0xb, R4); \ + OP(0xc, R1); OP(0xd, R2); OP(0xe, R3); OP(0xf, R4); static constexpr unsigned char Padding[64] { 0x80, /* 0x00, ... */ }; @@ -16,11 +46,14 @@ const void* MD5::UpdateImpl(const void *data, uint64_t len) { auto D = ctx_.D; while (block < limit) { - auto A_ = A; - auto B_ = B; - auto C_ = C; - auto D_ = D; - MD5_UPDATE + const auto A_ = A; + const auto B_ = B; + const auto C_ = C; + const auto D_ = D; + MD5_UPDATE(FF) + MD5_UPDATE(GG) + MD5_UPDATE(HH) + MD5_UPDATE(II) A += A_; B += B_; C += C_; @@ -43,16 +76,16 @@ void MD5::FinalImpl(const void *data, uint64_t len) { } unsigned char buffer[128]; // 2 blocks - ::std::memcpy(buffer, data, len); + std::memcpy(buffer, data, len); const uint64_t total = (ctx_.size + len) << 3; // total number in bit if (len < 56) { // len -> [0, 56) - ::std::memcpy(buffer + len, Padding, 56 - len); - ::std::memcpy(buffer + 56, &total, 8); + std::memcpy(buffer + len, Padding, 56 - len); + std::memcpy(buffer + 56, &total, 8); UpdateImpl(buffer, 64); // update 1 block } else { // len -> [56, 64 + 56) - ::std::memcpy(buffer + len, Padding, 120 - len); - ::std::memcpy(buffer + 120, &total, 8); + std::memcpy(buffer + len, Padding, 120 - len); + std::memcpy(buffer + 120, &total, 8); UpdateImpl(buffer, 128); // update 2 blocks } } diff --git a/src/impl/inline.inc b/src/impl/inline.inc index 1329747..73234b0 100644 --- a/src/impl/inline.inc +++ b/src/impl/inline.inc @@ -1,6 +1,6 @@ #pragma once -namespace md5::impl { +namespace md5 { inline MD5& MD5::Reset() { ctx_.A = value::kA; @@ -35,4 +35,8 @@ constexpr std::array MD5::HashCE(const std::string_view &data) { return HashCE(data.data(), data.size()); } -} // namespace md5::impl +constexpr std::array MD5::HashCE(const char *data, const uint64_t len) { + return ce::Hash(data, len); +} + +} // namespace md5 diff --git a/src/impl/value.inc b/src/impl/value.inc index 467030d..f3a63a8 100644 --- a/src/impl/value.inc +++ b/src/impl/value.inc @@ -16,15 +16,29 @@ constexpr uint32_t kB = 0xefcdab89; constexpr uint32_t kC = 0x98badcfe; constexpr uint32_t kD = 0x10325476; -// In order to be compatible with C++17, the `consteval` keyword cannot be used -// here. The MD5 T-table constants will be macro-expanded and calculated. +/// MD5 data block index, input between 0 and 63. +constexpr int K(const int i) { + constexpr int step[4] = {1, 5, 3, 7}; + constexpr int begin[4] = {0, 1, 5, 0}; + return (begin[i >> 4] + step[i >> 4] * i) & 0b1111; +} +static_assert(K(0) != K(63)); -constexpr uint32_t TCal(const int i) { - const auto val = math::sin(i + 1); - return static_cast(math::abs(val) * 0x100000000); +/// MD5 circular shift times, input between 0 and 63. +constexpr int S(const int i) { + constexpr int shift[4][4] = { + {7, 12, 17, 22}, + {5, 9, 14, 20}, + {4, 11, 16, 23}, + {6, 10, 15, 21}, + }; + return shift[i >> 4][i & 0b11]; } +static_assert(S(0) != S(63)); -#define MD5_TT \ +/// In order to be compatible with C++17, the `consteval` keyword cannot be used +/// here. The MD5 T-table constants will be macro-expanded and calculated. +#define MD5_TT \ MD5_T(00) MD5_T(01) MD5_T(02) MD5_T(03) MD5_T(04) MD5_T(05) MD5_T(06) MD5_T(07) \ MD5_T(08) MD5_T(09) MD5_T(0a) MD5_T(0b) MD5_T(0c) MD5_T(0d) MD5_T(0e) MD5_T(0f) \ MD5_T(10) MD5_T(11) MD5_T(12) MD5_T(13) MD5_T(14) MD5_T(15) MD5_T(16) MD5_T(17) \ @@ -34,15 +48,20 @@ constexpr uint32_t TCal(const int i) { MD5_T(30) MD5_T(31) MD5_T(32) MD5_T(33) MD5_T(34) MD5_T(35) MD5_T(36) MD5_T(37) \ MD5_T(38) MD5_T(39) MD5_T(3a) MD5_T(3b) MD5_T(3c) MD5_T(3d) MD5_T(3e) MD5_T(3f) -#define MD5_T(x) constexpr auto kT_##x = TCal(0x##x); +#define MD5_T(x) constexpr auto kT_##x = static_cast(math::abs(math::sin(0x##x + 1)) * 0x100000000); MD5_TT #undef MD5_T #define MD5_T(x) kT_##x, -/// MD5 T-table constant array. constexpr std::array kT = {MD5_TT}; #undef MD5_T #undef MD5_TT +/// MD5 T-table constant, input between 0 and 63. +constexpr uint32_t T(const int i) { + return kT[i]; +} +static_assert(T(0) != T(63)); + } // namespace md5::value diff --git a/src/impl/wrapper.cc b/src/impl/wrapper.cc index 33cd814..dd7b657 100644 --- a/src/impl/wrapper.cc +++ b/src/impl/wrapper.cc @@ -1,8 +1,7 @@ -#include - #include "md5.h" +#include -using ::md5::impl::MD5; +using md5::MD5; std::string MD5::Digest() const { std::string result(32, 0x00); @@ -17,13 +16,13 @@ std::string MD5::Digest() const { MD5& MD5::Update(const void *data, uint64_t len) { if (buffer_size_ != 0) { if (buffer_size_ + len < 64) { // buffer not filled - ::std::memcpy(buffer_ + buffer_size_, data, len); + std::memcpy(buffer_ + buffer_size_, data, len); buffer_size_ += len; return *this; // save into buffer and return } const auto size = 64 - buffer_size_; - ::std::memcpy(buffer_ + buffer_size_, data, size); + std::memcpy(buffer_ + buffer_size_, data, size); UpdateImpl(buffer_, 64); // fill and update with buffer data = static_cast(data) + size; buffer_size_ = 0; @@ -33,7 +32,7 @@ MD5& MD5::Update(const void *data, uint64_t len) { data = UpdateImpl(data, len); len &= 0b111111; // len -> [0, 64) if (len != 0) { - ::std::memcpy(buffer_, data, len); // save remain data into buffer + std::memcpy(buffer_, data, len); // save remain data into buffer buffer_size_ = len; } return *this; diff --git a/src/md5.h b/src/md5.h index 4524bda..5a40be1 100644 --- a/src/md5.h +++ b/src/md5.h @@ -10,9 +10,10 @@ static_assert(sizeof(uintptr_t) == 8, static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, "Project only works on little-endian architecture."); -#include "impl/algorithm.inc" +#include "impl/value.inc" +#include "impl/constexpr.inc" -namespace md5::impl { +namespace md5 { class MD5 { public: @@ -33,7 +34,6 @@ public: /// Get the string result of md5. [[nodiscard]] std::string Digest() const; -public: /// Calculate the md5 hash value of the specified data. static std::string Hash(const std::string_view &data); @@ -47,6 +47,14 @@ public: static constexpr std::array HashCE(const char *data, uint64_t len); private: + struct md5_ctx { + uint32_t A = value::kA; + uint32_t B = value::kB; + uint32_t C = value::kC; + uint32_t D = value::kD; + uint64_t size = 0; // processed size in byte + }; + md5_ctx ctx_; char buffer_[64] {}; uint64_t buffer_size_ = 0; // size < 64 @@ -58,13 +66,6 @@ private: void FinalImpl(const void *data, uint64_t len); }; -} // namespace md5::impl +} // namespace md5 #include "impl/inline.inc" -#include "impl/constexpr.inc" - -namespace md5 { - -using MD5 = ::md5::impl::MD5; - -} // namespace md5 diff --git a/test/assert.cc b/test/assert.cc index 086186d..8c5ccb6 100644 --- a/test/assert.cc +++ b/test/assert.cc @@ -1,8 +1,8 @@ #include "md5.h" -using md5::impl::K; -using md5::impl::S; -using md5::impl::T; +using md5::value::K; +using md5::value::S; +using md5::value::T; static_assert(K(0x00) == 0); static_assert(K(0x01) == 1);