MD4(Message Digest Algorithm 4)是由羅納德·李維斯特於 1990 年設計的密碼雜湊函數,雖然現已不建議用於安全關鍵場合,但在舊系統整合、相容性測試或教學示範中仍具參考價值。本文提供一個完整的 C# 實作版本,源自 Norbert Hranitzky 的 Java 移植版,經由 Blood 修改整理,命名空間為 Blood.COM.Security。
依 RFC 1320 規範,以 Little-Endian 方式設定初始向量:
context[0] = 0x67452301;
context[1] = 0xEFCDAB89;
context[2] = 0x98BADCFE;
context[3] = 0x10325476;
MD4 包含三輪運算,各對應一個非線性函式:
| 函式 | 公式 | 常數 |
|---|---|---|
| FF | (b & c) | (~b & d) | 無 |
| GG | (b & (c | d)) | (c & d) | 0x5A827999 |
| HH | b ^ c ^ d | 0x6ED9EBA1 |
每輪皆執行 16 步驟,結合左循環位移(t << s | t >> (32-s))達成擴散效果。
將 64 位元組解碼為 16 個 uint,依序執行三輪運算,最後累加至 context。
engineUpdate 處理尾部區塊context 四個 uint 拆解為 16 位元組陣列回傳engineReset 重置狀態類別提供多種便利方法:
GetByteHashFromString(string):字串 → UTF-8 位元組 → 雜湊位元組陣列GetHexHashFromString(string):字串 → 十六進位字串(小寫、無分隔符)GetByteHashFromBytes(byte[]) / GetHexHashFromBytes(byte[]):位元組陣列輸入GetByteHashFromByte(byte) / GetHexHashFromByte(byte):單一位元組輸入內部 bytesToHex 利用 BitConverter.ToString 移除連字號產生連續十六進位字串。
/*
Copyright 2002 Blood (eaststarbuy@sina.com)
Ported from Norbert Hranitzky's Java version.
*/
using System;
using System.Text;
namespace Blood.COM.Security
{
public class MD4
{
private const int BLOCK_LENGTH = 64;
private uint[] context = new uint[4];
private long count;
private byte[] buffer = new byte[BLOCK_LENGTH];
private uint[] X = new uint[16];
public MD4() { engineReset(); }
private MD4(MD4 md) : this()
{
context = (uint[])md.context.Clone();
buffer = (byte[])md.buffer.Clone();
count = md.count;
}
public object Clone() => new MD4(this);
private void engineReset()
{
context[0] = 0x67452301;
context[1] = 0xEFCDAB89;
context[2] = 0x98BADCFE;
context[3] = 0x10325476;
count = 0L;
Array.Clear(buffer, 0, BLOCK_LENGTH);
}
private void engineUpdate(byte b)
{
int i = (int)(count % BLOCK_LENGTH);
count++;
buffer[i] = b;
if (i == BLOCK_LENGTH - 1) transform(ref buffer, 0);
}
private void engineUpdate(byte[] input, int offset, int len)
{
if (offset < 0 || len < 0 || (long)offset + len > input.Length)
throw new ArgumentOutOfRangeException();
int bufferNdx = (int)(count % BLOCK_LENGTH);
count += len;
int partLen = BLOCK_LENGTH - bufferNdx;
int i = 0;
if (len >= partLen)
{
Array.Copy(input, offset, buffer, bufferNdx, partLen);
transform(ref buffer, 0);
for (i = partLen; i + BLOCK_LENGTH - 1 < len; i += BLOCK_LENGTH)
transform(ref input, offset + i);
bufferNdx = 0;
}
if (i < len)
Array.Copy(input, offset + i, buffer, bufferNdx, len - i);
}
private byte[] engineDigest()
{
int bufferNdx = (int)(count % BLOCK_LENGTH);
int padLen = (bufferNdx < 56) ? (56 - bufferNdx) : (120 - bufferNdx);
byte[] tail = new byte[padLen + 8];
tail[0] = 0x80;
for (int i = 0; i < 8; i++)
tail[padLen + i] = (byte)((count * 8) >> (8 * i));
engineUpdate(tail, 0, tail.Length);
byte[] result = new byte[16];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
result[i * 4 + j] = (byte)(context[i] >> (8 * j));
engineReset();
return result;
}
public byte[] GetByteHashFromString(string s)
{
byte[] b = Encoding.UTF8.GetBytes(s);
var md4 = new MD4();
md4.engineUpdate(b, 0, b.Length);
return md4.engineDigest();
}
public byte[] GetByteHashFromBytes(byte[] b)
{
var md4 = new MD4();
md4.engineUpdate(b, 0, b.Length);
return md4.engineDigest();
}
public string GetHexHashFromBytes(byte[] b)
=> bytesToHex(GetByteHashFromBytes(b), 16);
public byte[] GetByteHashFromByte(byte b)
{
var md4 = new MD4();
md4.engineUpdate(b);
return md4.engineDigest();
}
public string GetHexHashFromByte(byte b)
=> bytesToHex(GetByteHashFromByte(b), 16);
public string GetHexHashFromString(string s)
=> bytesToHex(GetByteHashFromString(s), 16);
private static string bytesToHex(byte[] a, int len)
{
string temp = BitConverter.ToString(a);
var sb = new StringBuilder(temp.Length - (len - 1));
foreach (char c in temp)
if (c != '-') sb.Append(c);
return sb.ToString();
}
private void transform(ref byte[] block, int offset)
{
for (int i = 0; i < 16; i++)
{
X[i] = ((uint)block[offset++] & 0xFF)
| (((uint)block[offset++] & 0xFF) << 8)
| (((uint)block[offset++] & 0xFF) << 16)
| (((uint)block[offset++] & 0xFF) << 24);
}
uint A = context[0], B = context[1], C = context[2], D = context[3];
// Round 1
A = FF(A, B, C, D, X[0], 3); D = FF(D, A, B, C, X[1], 7);
C = FF(C, D, A, B, X[2], 11); B = FF(B, C, D, A, X[3], 19);
A = FF(A, B, C, D, X[4], 3); D = FF(D, A, B, C, X[5], 7);
C = FF(C, D, A, B, X[6], 11); B = FF(B, C, D, A, X[7], 19);
A = FF(A, B, C, D, X[8], 3); D = FF(D, A, B, C, X[9], 7);
C = FF(C, D, A, B, X[10], 11); B = FF(B, C, D, A, X[11], 19);
A = FF(A, B, C, D, X[12], 3); D = FF(D, A, B, C, X[13], 7);
C = FF(C, D, A, B, X[14], 11); B = FF(B, C, D, A, X[15], 19);
// Round 2
A = GG(A, B, C, D, X[0], 3); D = GG(D, A, B, C, X[4], 5);
C = GG(C, D, A, B, X[8], 9); B = GG(B, C, D, A, X[12], 13);
A = GG(A, B, C, D, X[1], 3); D = GG(D, A, B, C, X[5], 5);
C = GG(C, D, A, B, X[9], 9); B = GG(B, C, D, A, X[13], 13);
A = GG(A, B, C, D, X[2], 3); D = GG(D, A, B, C, X[6], 5);
C = GG(C, D, A, B, X[10], 9); B = GG(B, C, D, A, X[14], 13);
A = GG(A, B, C, D, X[3], 3); D = GG(D, A, B, C, X[7], 5);
C = GG(C, D, A, B, X[11], 9); B = GG(B, C, D, A, X[15], 13);
// Round 3
A = HH(A, B, C, D, X[0], 3); D = HH(D, A, B, C, X[8], 9);
C = HH(C, D, A, B, X[4], 11); B = HH(B, C, D, A, X[12], 15);
A = HH(A, B, C, D, X[2], 3); D = HH(D, A, B, C, X[10], 9);
C = HH(C, D, A, B, X[6], 11); B = HH(B, C, D, A, X[14], 15);
A = HH(A, B, C, D, X[1], 3); D = HH(D, A, B, C, X[9], 9);
C = HH(C, D, A, B, X[5], 11); B = HH(B, C, D, A, X[13], 15);
A = HH(A, B, C, D, X[3], 3); D = HH(D, A, B, C, X[11], 9);
C = HH(C, D, A, B, X[7], 11); B = HH(B, C, D, A, X[15], 15);
context[0] += A; context[1] += B; context[2] += C; context[3] += D;
}
private uint FF(uint a, uint b, uint c, uint d, uint x, int s)
{
uint t = a + ((b & c) | (~b & d)) + x;
return t << s | t >> (32 - s);
}
private uint GG(uint a, uint b, uint c, uint d, uint x, int s)
{
uint t = a + ((b & (c | d)) | (c & d)) + x + 0x5A827999;
return t << s | t >> (32 - s);
}
private uint HH(uint a, uint b, uint c, uint d, uint x, int s)
{
uint t = a + (b ^ c ^ d) + x + 0x6ED9EBA1;
return t << s | t >> (32 - s);
}
}
}
var md4 = new Blood.COM.Security.MD4();
string hash = md4.GetHexHashFromString("Hello, MD4!");
Console.WriteLine(hash); // 輸出 32 字元十六進位字串
System.Security.Cryptography 內建演算法。HashAlgorithm 抽象類別。