首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 實作 MD4 雜湊演算法:完整原始碼解析與應用指南

     編輯:關於C語言

前言

MD4(Message Digest Algorithm 4)是由羅納德·李維斯特於 1990 年設計的密碼雜湊函數,雖然現已不建議用於安全關鍵場合,但在舊系統整合、相容性測試或教學示範中仍具參考價值。本文提供一個完整的 C# 實作版本,源自 Norbert Hranitzky 的 Java 移植版,經由 Blood 修改整理,命名空間為 Blood.COM.Security。

核心架構設計

關鍵常數與欄位

  • BLOCK_LENGTH = 64:512 位元區塊大小(512/8)
  • context[4]:四個 32 位元暫存器(A、B、C、D),儲存中間運算結果
  • count:已處理位元組數(模 2^64)
  • buffer[64]:512 位元輸入緩衝區
  • X[16]:工作緩衝區,存放 16 個 32 位元字

初始化邏輯(engineReset)

依 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
HHb ^ c ^ d0x6ED9EBA1

每輪皆執行 16 步驟,結合左循環位移(t << s | t >> (32-s))達成擴散效果。

資料處理流程

1. 區塊轉換(transform)

將 64 位元組解碼為 16 個 uint,依序執行三輪運算,最後累加至 context。

2. 串流更新(engineUpdate)

  • 單位元組版本:填入緩衝區,滿 64 位元組即觸發轉換
  • 陣列版本:分塊處理,先補滿緩衝區,再直接轉換完整區塊,剩餘資料留待下次

3. 終結輸出(engineDigest)

  1. 補位:加 0x80 後填 0,使長度 ≡ 56 (mod 64)
  2. 附加 64 位元長度(位元計數,Little-Endian)
  3. 呼叫 engineUpdate 處理尾部區塊
  4. 將 context 四個 uint 拆解為 16 位元組陣列回傳
  5. 自動呼叫 engineReset 重置狀態

公開 API 介面

類別提供多種便利方法:

  • 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 字元十六進位字串

注意事項

  • MD4 已知存在碰撞攻擊風險,請勿用於數位簽章、密碼儲存或任何安全關鍵場景。
  • 本實作為單執行緒設計,若需多執行緒環境請自行加鎖或改用 System.Security.Cryptography 內建演算法。
  • 若需相容 .NET 內建介面,可實作 HashAlgorithm 抽象類別。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved