Skip to content

Instantly share code, notes, and snippets.

@AntonC9018
Created February 3, 2022 18:59
Show Gist options
  • Save AntonC9018/de93518679bd4fb90a1195d8b54f414d to your computer and use it in GitHub Desktop.
Save AntonC9018/de93518679bd4fb90a1195d8b54f414d to your computer and use it in GitHub Desktop.
module md4;
struct MD4Context
{
uint[4] hash;
uint[16] block;
ulong byteCount;
}
ubyte[MD4Context.hash.sizeof] md4Of(scope const(ubyte)[] input)
{
MD4Context context = md4CreateContext();
md4Update(&context, input);
return md4Final(&context);
}
unittest
{
static string md4(string input)
{
import std.digest : toHexString, LetterCase;
import std.string : representation;
return md4Of(input.representation).toHexString!(LetterCase.lower).idup;
}
assert(md4("") == "31d6cfe0d16ae931b73c59d7e0c089c0");
assert(md4("a") == "bde52cb31de33e46245e05fbdbd6fb24");
assert(md4("abc") == "a448017aaf21d8525fc10ae87aa6729d");
assert(md4("message digest") == "d9130a8164549fe818874806e1c7014b");
assert(md4("abcdefghijklmnopqrstuvwxyz") == "d79e1c308aa5bbcdeea8ed63df412da9");
assert(md4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") == "043f8582f241db351ce627e153e7f0e4");
assert(md4("12345678901234567890123456789012345678901234567890123456789012345678901234567890") == "e33b4ddc9c38f2199c3e7b164fcc0536");
}
MD4Context md4CreateContext()
{
MD4Context context;
md4ResetContext(&context);
return context;
}
void md4ResetContext(MD4Context* context)
{
with (context)
{
hash[0] = 0x67452301;
hash[1] = 0xefcdab89;
hash[2] = 0x98badcfe;
hash[3] = 0x10325476;
byteCount = 0;
}
}
void md4Update(MD4Context* context, scope const(ubyte)[] data)
{
const currentByteIndex = cast(size_t)(context.byteCount & (context.block.sizeof - 1));
const availableByteCount = context.block.sizeof - currentByteIndex;
ubyte[] blockBytes = cast(ubyte[]) context.block[];
context.byteCount += data.length;
if (availableByteCount > data.length)
{
blockBytes[currentByteIndex .. currentByteIndex + data.length] = data[];
return;
}
blockBytes[currentByteIndex .. $] = data[0 .. availableByteCount];
md4TransformWithLittleEndianConversion(context);
data = data[availableByteCount .. $];
while (data.length >= context.block.sizeof)
{
blockBytes[] = data[0 .. context.block.sizeof];
md4TransformWithLittleEndianConversion(context);
data = data[context.block.sizeof .. $];
}
blockBytes[0 .. data.length] = data[];
}
ubyte[MD4Context.hash.sizeof] md4Final(MD4Context* context)
{
// Two cases:
// if (stuff.length + 1 < until_the_last_two_bytes)
// MD4([ ...stuff, 0x80, ...0, byteCount[0], byteCount[1] ])
// else //
// MD4([ ...stuff, 0x80, ...0])
/// MD4([ ...0, byteCount[0], byteCount[1]])
scope(exit)
context.hash[] = 0;
const lastTwoWordsIndex = context.block.sizeof - (uint[2]).sizeof;
// The padding is a 0x80 followed by zeros.
const paddingStartIndex = context.byteCount & (context.block.sizeof - 1);
const zerosStartIndex = paddingStartIndex + 1;
ubyte[] blockBytes = cast(ubyte[]) context.block[];
blockBytes[paddingStartIndex] = 0x80;
// the byte count (the last 8 bytes) does not fit
if (paddingStartIndex >= lastTwoWordsIndex)
{
blockBytes[zerosStartIndex .. $] = 0;
// do md4
md4TransformWithLittleEndianConversion(context);
blockBytes[0 .. lastTwoWordsIndex] = 0;
}
// the byte count fits
else // if (zerosStartIndex <= lastTwoBytesIndex)
{
blockBytes[zerosStartIndex .. lastTwoWordsIndex] = 0;
}
// do md4
context.block[14] = cast(uint) (context.byteCount << 3);
context.block[15] = cast(uint) (context.byteCount >> 29);
littleEndianToNativeAll(context.block[0 .. $ - 2]);
md4Transform(context.hash, context.block);
return cast(ubyte[context.hash.sizeof]) context.hash;
}
private void md4Transform(ref uint[4] hash, in uint[16] state)
{
uint[4] temp = hash;
enum size_t[4][4] indexPermutationForInnerIteration =
[
[ 0, 1, 2, 3 ],
[ 3, 0, 1, 2 ],
[ 2, 3, 0, 1 ],
[ 1, 2, 3, 0 ],
];
enum size_t[4][3] constantStateOffsets = [
[ 0, 4, 8, 12 ],
[ 0, 1, 2, 3 ],
[ 0, 2, 1, 3 ],
];
enum size_t[4][3] variableStateOffsets = [
[ 0, 1, 2, 3 ],
[ 0, 4, 8, 12 ],
[ 0, 8, 4, 12 ],
];
enum uint[4][3] shifts = [
[ 3, 7, 11, 19 ],
[ 3, 5, 9, 13 ],
[ 3, 9, 11, 15 ],
];
static uint F(uint x, uint y, uint z)
{
return (x & y) | ((~x) & z);
}
static uint G(uint x, uint y, uint z)
{
return (x & y) | (x & z) | (y & z);
}
static uint H(uint x, uint y, uint z)
{
return x ^ y ^ z;
}
import std.meta : AliasSeq;
alias functions = AliasSeq!(F, G, H);
enum uint[3] M = [0, 0x5A827999, 0x6ED9EBA1];
static uint leftShiftRotate(uint x, uint s)
{
return (x << s) | (x >> (32 - s));
}
// Why do this? because then logging debug messages is way easier.
static foreach (roundIndex; 0 .. 3)
{
static foreach (outerIterationIndex, constantStateOffset; constantStateOffsets[roundIndex])
{
static foreach (innerIterationIndex, variableStateOffset; variableStateOffsets[roundIndex])
{{
enum perm = indexPermutationForInnerIteration[innerIterationIndex];
enum shift = shifts[roundIndex][innerIterationIndex];
uint k = state[constantStateOffset + variableStateOffset];
uint funcResult = functions[roundIndex](temp[perm[1]], temp[perm[2]], temp[perm[3]]);
uint sumResult = temp[perm[0]] + funcResult + k + M[roundIndex];
uint rotated = leftShiftRotate(sumResult, shift);
temp[perm[0]] = rotated;
}}
}
}
hash[] += temp[];
}
private void littleEndianToNativeAll(uint[] arr)
{
import std.bitmanip : littleEndianToNative;
foreach (ref ubyte[4] b; cast(ubyte[4][]) arr)
*(cast(uint*)&b) = littleEndianToNative!uint(b);
}
private void md4TransformWithLittleEndianConversion(MD4Context* context)
{
littleEndianToNativeAll(context.block[]);
md4Transform(context.hash, context.block);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment