Unroll the MD5 block function (#1465)

This commit is contained in:
Chris Tate
2026-10-19 23:51:35 -05:00
committed by GitHub
4 changed files with 151 additions and 43 deletions
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5775-md5-lengths.ts"
],
"diags": []
}
+110 -43
View File
@@ -7263,45 +7263,112 @@ static ScrStr *scr_digest_encode(const unsigned char *d, size_t n, int enc) {
/* ── MD5 (RFC 1321) ───────────────────────────────────────────────── */
static const uint32_t scr_md5_k[64] = {
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};
static const unsigned char scr_md5_r[64] = {
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};
/* The 64 steps are unrolled with their message word, constant and
* rotation inlined. a + m + K does not depend on the previous step, so
* each step's critical path is the round function of b, an add, the
* rotate and the add of b. G is written as (b & d) + (c & ~d) (the two
* terms share no bits) so that its c & ~d half joins that early sum. The
* empty asm statement keeps the compiler from moving the constant's add
* after the round function, which puts it back on the critical path. */
#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__)
#define SCR_MD5_PIN(v) __asm__("" : "+r"(v))
#else
#define SCR_MD5_PIN(v) ((void)0)
#endif
#define SCR_MD5_F(b, c, d) ((d) ^ ((b) & ((c) ^ (d))))
#define SCR_MD5_H(b, c, d) ((b) ^ (c) ^ (d))
#define SCR_MD5_I(b, c, d) ((c) ^ ((b) | ~(d)))
#define SCR_MD5_STEP(f, a, b, c, d, xk, s) \
do { \
(a) += (xk); \
SCR_MD5_PIN(a); \
(a) += f((b), (c), (d)); \
(a) = ((a) << (s) | (a) >> (32 - (s))) + (b); \
} while (0)
#define SCR_MD5_GSTEP(a, b, c, d, xk, s) \
do { \
(a) += ((c) & ~(d)) + (xk); \
SCR_MD5_PIN(a); \
(a) += (b) & (d); \
(a) = ((a) << (s) | (a) >> (32 - (s))) + (b); \
} while (0)
SCR_DIGEST_ALIGNED static void scr_md5_block(uint32_t h[4], const unsigned char *p) {
uint32_t m[16];
for (int i = 0; i < 16; i++) {
m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) |
((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24);
}
SCR_DIGEST_ALIGNED static void scr_md5_blocks(uint32_t h[4], const unsigned char *p, size_t blocks) {
uint32_t a = h[0], b = h[1], c = h[2], d = h[3];
for (int i = 0; i < 64; i++) {
uint32_t f, g;
if (i < 16) { f = (b & c) | (~b & d); g = (uint32_t)i; }
else if (i < 32) { f = (d & b) | (~d & c); g = (5u * i + 1) & 15; }
else if (i < 48) { f = b ^ c ^ d; g = (3u * i + 5) & 15; }
else { f = c ^ (b | ~d); g = (7u * i) & 15; }
uint32_t t = d;
d = c;
c = b;
uint32_t x = a + f + scr_md5_k[i] + m[g];
b = b + ((x << scr_md5_r[i]) | (x >> (32 - scr_md5_r[i])));
a = t;
for (; blocks != 0; blocks--, p += 64) {
uint32_t m[16];
for (int i = 0; i < 16; i++) {
m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) |
((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24);
}
uint32_t a0 = a, b0 = b, c0 = c, d0 = d;
SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[0] + 0xd76aa478u, 7);
SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[1] + 0xe8c7b756u, 12);
SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[2] + 0x242070dbu, 17);
SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[3] + 0xc1bdceeeu, 22);
SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[4] + 0xf57c0fafu, 7);
SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[5] + 0x4787c62au, 12);
SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[6] + 0xa8304613u, 17);
SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[7] + 0xfd469501u, 22);
SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[8] + 0x698098d8u, 7);
SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[9] + 0x8b44f7afu, 12);
SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[10] + 0xffff5bb1u, 17);
SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[11] + 0x895cd7beu, 22);
SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[12] + 0x6b901122u, 7);
SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[13] + 0xfd987193u, 12);
SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[14] + 0xa679438eu, 17);
SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[15] + 0x49b40821u, 22);
SCR_MD5_GSTEP(a, b, c, d, m[1] + 0xf61e2562u, 5);
SCR_MD5_GSTEP(d, a, b, c, m[6] + 0xc040b340u, 9);
SCR_MD5_GSTEP(c, d, a, b, m[11] + 0x265e5a51u, 14);
SCR_MD5_GSTEP(b, c, d, a, m[0] + 0xe9b6c7aau, 20);
SCR_MD5_GSTEP(a, b, c, d, m[5] + 0xd62f105du, 5);
SCR_MD5_GSTEP(d, a, b, c, m[10] + 0x02441453u, 9);
SCR_MD5_GSTEP(c, d, a, b, m[15] + 0xd8a1e681u, 14);
SCR_MD5_GSTEP(b, c, d, a, m[4] + 0xe7d3fbc8u, 20);
SCR_MD5_GSTEP(a, b, c, d, m[9] + 0x21e1cde6u, 5);
SCR_MD5_GSTEP(d, a, b, c, m[14] + 0xc33707d6u, 9);
SCR_MD5_GSTEP(c, d, a, b, m[3] + 0xf4d50d87u, 14);
SCR_MD5_GSTEP(b, c, d, a, m[8] + 0x455a14edu, 20);
SCR_MD5_GSTEP(a, b, c, d, m[13] + 0xa9e3e905u, 5);
SCR_MD5_GSTEP(d, a, b, c, m[2] + 0xfcefa3f8u, 9);
SCR_MD5_GSTEP(c, d, a, b, m[7] + 0x676f02d9u, 14);
SCR_MD5_GSTEP(b, c, d, a, m[12] + 0x8d2a4c8au, 20);
SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[5] + 0xfffa3942u, 4);
SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[8] + 0x8771f681u, 11);
SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[11] + 0x6d9d6122u, 16);
SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[14] + 0xfde5380cu, 23);
SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[1] + 0xa4beea44u, 4);
SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[4] + 0x4bdecfa9u, 11);
SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[7] + 0xf6bb4b60u, 16);
SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[10] + 0xbebfbc70u, 23);
SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[13] + 0x289b7ec6u, 4);
SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[0] + 0xeaa127fau, 11);
SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[3] + 0xd4ef3085u, 16);
SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[6] + 0x04881d05u, 23);
SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[9] + 0xd9d4d039u, 4);
SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[12] + 0xe6db99e5u, 11);
SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[15] + 0x1fa27cf8u, 16);
SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[2] + 0xc4ac5665u, 23);
SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[0] + 0xf4292244u, 6);
SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[7] + 0x432aff97u, 10);
SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[14] + 0xab9423a7u, 15);
SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[5] + 0xfc93a039u, 21);
SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[12] + 0x655b59c3u, 6);
SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[3] + 0x8f0ccc92u, 10);
SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[10] + 0xffeff47du, 15);
SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[1] + 0x85845dd1u, 21);
SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[8] + 0x6fa87e4fu, 6);
SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[15] + 0xfe2ce6e0u, 10);
SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[6] + 0xa3014314u, 15);
SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[13] + 0x4e0811a1u, 21);
SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[4] + 0xf7537e82u, 6);
SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[11] + 0xbd3af235u, 10);
SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[2] + 0x2ad7d2bbu, 15);
SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[9] + 0xeb86d391u, 21);
a += a0; b += b0; c += c0; d += d0;
}
h[0] += a; h[1] += b; h[2] += c; h[3] += d;
h[0] = a; h[1] = b; h[2] = c; h[3] = d;
}
typedef struct ScrMd5Ctx {
@@ -7329,14 +7396,15 @@ static void scr_md5_update(ScrMd5Ctx *ctx, const unsigned char *data, size_t len
data += take;
len -= take;
if (ctx->tail_len == 64) {
scr_md5_block(ctx->h, ctx->tail);
scr_md5_blocks(ctx->h, ctx->tail, 1);
ctx->tail_len = 0;
}
}
while (len >= 64) {
scr_md5_block(ctx->h, data);
data += 64;
len -= 64;
if (len >= 64) {
size_t blocks = len / 64;
scr_md5_blocks(ctx->h, data, blocks);
data += blocks * 64;
len -= blocks * 64;
}
if (len != 0) {
memcpy(ctx->tail, data, len);
@@ -7354,8 +7422,7 @@ static size_t scr_md5_final(const ScrMd5Ctx *source, unsigned char out[32]) {
memset(tail + rem + 1, 0, pad - rem - 1 - 8);
uint64_t bits = ctx.bytes * 8;
for (int b = 0; b < 8; b++) tail[pad - 8 + b] = (unsigned char)(bits >> (8 * b));
scr_md5_block(ctx.h, tail);
if (pad == 128) scr_md5_block(ctx.h, tail + 64);
scr_md5_blocks(ctx.h, tail, pad / 64);
for (int j = 0; j < 4; j++) {
for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(ctx.h[j] >> (8 * b));
}
+34
View File
@@ -0,0 +1,34 @@
// MD5 and md5-sha1 over every length from 0 to 520 bytes, fed whole and
// in 1-, 7-, 64- and 100-byte updates, then over 1 MiB, and HMAC-MD5
// over a few lengths. Each length's digests are folded into one running
// SHA-512 so the output stays short.
import { createHash, createHmac } from "node:crypto";
const data = Buffer.alloc(1 << 20);
let x = 0x9e3779b9;
for (let i = 0; i < data.length; i++) {
x ^= x << 13;
x ^= x >>> 17;
x ^= x << 5;
data[i] = x & 0xff;
}
function digest(algorithm: string, input: Buffer, chunk: number): Buffer {
const hash = createHash(algorithm);
if (chunk === 0) hash.update(input);
else for (let at = 0; at < input.length; at += chunk) hash.update(input.subarray(at, at + chunk));
return hash.digest();
}
for (const algorithm of ["md5", "md5-sha1"]) {
for (const chunk of [0, 1, 7, 64, 100]) {
const fold = createHash("sha512");
for (let length = 0; length <= 520; length++) fold.update(digest(algorithm, data.subarray(0, length), chunk));
console.log(algorithm, "chunk", chunk, fold.digest("hex").slice(0, 32));
}
console.log(algorithm, "1 MiB", digest(algorithm, data, 0).toString("hex").slice(0, 32), digest(algorithm, data, 4096).toString("hex").slice(0, 32));
}
console.log("abc", createHash("md5").update("abc").digest("hex"));
for (const length of [0, 1, 55, 56, 63, 64, 65, 119, 120, 1000]) {
console.log("hmac", length, createHmac("md5", "key").update(data.subarray(0, length)).digest("hex").slice(0, 32));
}
@@ -0,0 +1 @@
{"plain":2850,"san":3660}