複製鏈接
請複製以下鏈接發送給好友

MD5

鎖定
MD5信息摘要算法(英語:MD5 Message-Digest Algorithm),一種被廣泛使用的密碼散列函數,可以產生出一個128位(16字節)的散列值(hash value),用於確保信息傳輸完整一致。MD5由美國密碼學家羅納德·李維斯特(Ronald Linn Rivest)設計,於1992年公開,用以取代MD4算法。這套算法的程序在 RFC 1321 標準中被加以規範。1996年後該算法被證實存在弱點,可以被加以破解,對於需要高度安全性的數據,專家一般建議改用其他算法,如SHA-2。2004年,證實MD5算法無法防止碰撞(collision),因此不適用於安全性認證,如SSL公開密鑰認證或是數字簽名等用途。
中文名
消息摘要算法
外文名
Message Digest Algorithm MD5
別    名
摘要算法
提出者
羅納德·李維斯特
提出時間
1992年
應用學科
信息技術,計算機科學

MD5發展歷史

1992年8月,羅納德·李維斯特互聯網工程任務組(IETF)提交了一份重要文件,描述了這種算法的原理。由於這種算法的公開性和安全性,在90年代被廣泛使用在各種程序語言中,用以確保資料傳遞無誤等 [1] 
MD5由MD4、MD3、MD2改進而來,主要增強算法複雜度不可逆性。MD5算法因其普遍、穩定、快速的特點,仍廣泛應用於普通數據的加密保護領域 [2] 

MD5MD2

Rivest在1989年開發出MD2算法 [3]  。在這個算法中,首先對信息進行數據補位,使信息的字節長度是16的倍數。然後,以一個16位的校驗和追加到信息末尾,並且根據這個新產生的信息計算出散列值。後來,Rogier和Chauvaud發現如果忽略了校驗和MD2將產生衝突。MD2算法加密後結果是唯一的(即不同信息加密後的結果不同) [4] 

MD5MD4

為了加強算法的安全性,Rivest在1990年又開發出MD4算法 [3]  。MD4算法同樣需要填補信息以確保信息的比特位長度減去448後能被512整除(信息比特位長度mod 512 = 448)。然後,一個以64位二進制表示的信息的最初長度被添加進來。信息被處理成512位damgard/merkle迭代結構的區塊,而且每個區塊要通過三個不同步驟的處理。Den boer和Bosselaers以及其他人很快的發現了攻擊MD4版本中第一步和第三步的漏洞。Dobbertin向大家演示瞭如何利用一部普通的個人電腦在幾分鐘內找到MD4完整版本中的衝突(這個衝突實際上是一種漏洞,它將導致對不同的內容進行加密卻可能得到相同的加密後結果) [5] 

MD5MD5

1991年,Rivest開發出技術上更為趨近成熟的MD5算法。它在MD4的基礎上增加了"安全帶"(safety-belts)的概念。雖然MD5比MD4複雜度大一些,但卻更為安全。這個算法很明顯的由四個和MD4設計有少許不同的步驟組成。在MD5算法中,信息-摘要的大小和填充的必要條件與MD4完全相同。Den boer和Bosselaers曾發現MD5算法中的假衝突(pseudo-collisions),但除此之外就沒有其他被發現的加密後結果了 [3] 

MD5原理

MD5算法的原理可簡要的敍述為:MD5碼以512位分組來處理輸入的信息,且每一分組又被劃分為16個32位子分組,經過了一系列的處理後,算法的輸出由四個32位分組組成,將這四個32位分組級聯後將生成一個128位散列值 [6] 
總體流程如下圖所示,每次的運算都由前一輪的128位結果值和當前的512bit值進行運算 [7] 
圖1.MD5算法的整體流程圖 圖1.MD5算法的整體流程圖

MD5算法步驟

MD5按位補充數據

在MD5算法中,首先需要對信息進行填充,這個數據按位(bit)補充,要求最終的位數對512求模的結果為448。也就是説數據補位後,其位數長度只差64位(bit)就是512的整數倍。即便是這個數據的位數對512求模的結果正好是448也必須進行補位。補位的實現過程:首先在數據後補一個1 bit; 接着在後面補上一堆0 bit, 直到整個數據的位數對512求模的結果正好為448。總之,至少補1位,而最多可能補512位 [8] 

MD5擴展長度

在完成補位工作後,又將一個表示數據原始長度的64 bit數(這是對原始數據沒有補位前長度的描述,用二進制來表示)補在最後。當完成補位及補充數據的描述後,得到的結果數據長度正好是512的整數倍。也就是説長度正好是16個(32bit) 字的整數倍 [8] 

MD5初始化MD緩存器

MD5運算要用到一個128位的MD5緩存器,用來保存中間變量和最終結果。該緩存器又可看成是4個32位的寄存器A、B、C、D,初始化為 [8]  :
A : 01 23 45 67
B: 89 ab cd ef
C: fe dc ba 98
D: 76 54 32 10

MD5處理數據段

首先定義4個非線性函數F、G、H、I,對輸入的報文運算以512位數據段為單位進行處理。對每個數據段都要進行4輪的邏輯處理,在4輪中分別使用4個不同的函數F、G、H、I。每一輪以ABCD和當前的512位的塊為輸入,處理後送入ABCD(128位) [8] 

MD5輸出

信息摘要最終處理成以A, B, C, D 的形式輸出。也就是開始於A的低位在前的順序字節,結束於D的高位在前的順序字節 [8] 

MD5應用

MD5用於密碼管理

當我們需要保存某些密碼信息以用於身份確認時,如果直接將密碼信息以明碼方式保存在數據庫中,不使用任何保密措施系統管理員就很容易能得到原來的密碼信息,這些信息一旦泄露, 密碼也很容易被破譯。為了增加安全性,有必要對數據庫中需要保密的信息進行加密,這樣,即使有人得到了整個數據庫,如果沒有解密算法,也不能得到原來的密碼信息。MD5算法可以很好地解決這個問題,因為它可以將任意長度的輸入串經過計算得到固定長度的輸出,而且只有在明文相同的情況下,才能等到相同的密文,並且這個算法是不可逆的,即便得到了加密以後的密文,也不可能通過解密算法反算出明文。這樣就可以把用户的密碼以MD5值(或類似的其它算法)的方式保存起來,用户註冊的時候,系統是把用户輸入的密碼計算成 MD5 值,然後再去和系統中保存的 MD5 值進行比較,如果密文相同,就可以認定密碼是正確的,否則密碼錯誤。通過這樣的步驟,系統在並不知道用户密碼明碼的情況下就可以確定用户登錄系統的合法性。這樣不但可以避免用户的密碼被具有系統管理員權限的用户知道,而且還在一定程度上增加了密碼被破解的難度 [8] 

MD5電子簽名

MD5 算法還可以作為一種電子簽名的方法來使用,使用 MD5算法就可以為任何文件(不管其大小、格式、數量)產生一個獨一無二的“數字指紋”,藉助這個“數字指紋”,通過檢查文件前後 MD5 值是否發生了改變,就可以知道源文件是否被改動。我們在下載軟件的時候經常會發現,軟件的下載頁面上除了會提供軟件的下載地址以外,還會給出一串長長的字符串。這串字符串其實就是該軟件的MD5 值,它的作用就在於下載該軟件後,對下載得到的文件用專門的軟件(如 Windows MD5 check 等)做一次 MD5 校驗,以確保我們獲得的文件與該站點提供的文件為同一文件。利用 MD5 算法來進行文件校驗的方案被大量應用到軟件下載站、論壇數據庫、系統文件安全等方面 [8] 

MD5垃圾郵件篩選

電子郵件使用越來越普遍的情況下,可以利用 MD5 算法在郵件接收服務器上進行垃圾郵件的篩選,以減少此類郵件的干擾,具體思路如下:
  1. 建立一個郵件 MD5 值資料庫,分別儲存郵件的 MD5 值、允許出現的次數(假定為 3)和出現次數(初值為零)。
  2. 對每一封收到的郵件,將它的正文部分進行MD5 計算,得到 MD5 值,將這個值在資料庫中進行搜索。
  3. 如未發現相同的 MD5 值,説明此郵件是第一次收到,將此 MD5 值存入資料庫,並將出現次數置為1,轉到第五步。
  4. 如發現相同的 MD5 值,説明收到過同樣內容的郵件,將出現次數加 1,並與允許出現次數相比較,如小於允許出現次數,就轉到第五步。否則中止接收該郵件。結束。
  5. 接收該郵件 [8] 

MD5文件完整性校驗

常用Web服務器本身缺乏頁面完整性驗證機制,無法防止站點文件被篡改。為確保文件的完整性,防止用户訪問頁面被篡改,可採用MD5算法校驗文件完整性的Web防篡改機制,計算目標文件的數字指紋,運用快照技術恢復被篡改文件,以解決多數防篡改系統對動態站點保護失效及小文件恢復難的問題 。 [10] 

MD5安全性分析

MD5相對MD4所作的改進:
  1. 增加了第四輪。
  2. 每一步均有唯一的加法常數。
  3. 減弱第二輪中函數的對稱性。
  4. 第一步加上了上一步的結果,這將引起更快的雪崩效應(就是對明文或者密鑰改變 1bit 都會引起密文的巨大不同)。
  5. 改變了第二輪和第三輪中訪問消息子分組的次序,使其更不相似。
  6. 近似優化了每一輪中的循環左移位移量以實現更快的雪崩效應,各輪的位移量互不相同。
MD5 算法自誕生之日起,就有很多人試圖證明和發現它的不安全之處,即存在碰撞(在對兩個不同的內容使用 MD5算法運算的時候,有可能得到一對相同的結果值) [8]  。2009年,中國科學院的謝濤和馮登國僅用了
的碰撞算法複雜度,破解了MD5的碰撞抵抗,該攻擊在普通計算機上運行只需要數秒鐘 [9] 

MD5代碼

MD5C++實現

#include<iostream>
#include<string>
using namespace std;
#define shift(x, n) (((x) << (n)) | ((x) >> (32-(n))))//右移的時候,高位一定要補零,而不是補充符號位
#define F(x, y, z) (((x) & (y)) | ((~x) & (z)))    
#define G(x, y, z) (((x) & (z)) | ((y) & (~z)))
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define I(x, y, z) ((y) ^ ((x) | (~z)))
#define A 0x67452301
#define B 0xefcdab89
#define C 0x98badcfe
#define D 0x10325476
//strBaye的長度
unsigned int strlength;
//A,B,C,D的臨時變量
unsigned int atemp;
unsigned int btemp;
unsigned int ctemp;
unsigned int dtemp;
//常量ti unsigned int(abs(sin(i+1))*(2pow32))
const unsigned int k[]={
        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};
//向左位移數
const unsigned int s[]={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};
const char str16[]="0123456789abcdef";
void mainLoop(unsigned int M[])
{
    unsigned int f,g;
    unsigned int a=atemp;
    unsigned int b=btemp;
    unsigned int c=ctemp;
    unsigned int d=dtemp;
    for (unsigned int i = 0; i < 64; i++)
    {
        if(i<16){
            f=F(b,c,d);
            g=i;
        }else if (i<32)
        {
            f=G(b,c,d);
            g=(5*i+1)%16;
        }else if(i<48){
            f=H(b,c,d);
            g=(3*i+5)%16;
        }else{
            f=I(b,c,d);
            g=(7*i)%16;
        }
        unsigned int tmp=d;
        d=c;
        c=b;
        b=b+shift((a+f+k[i]+M[g]),s[i]);
        a=tmp;
    }
    atemp=a+atemp;
    btemp=b+btemp;
    ctemp=c+ctemp;
    dtemp=d+dtemp;
}
/*
*填充函數
*處理後應滿足bits≡448(mod512),字節就是bytes≡56(mode64)
*填充方式為先加一個1,其它位補零
*最後加上64位的原來長度
*/
unsigned int* add(string str)
{
    unsigned int num=((str.length()+8)/64)+1;//以512位,64個字節為一組
    unsigned int *strByte=new unsigned int[num*16];    //64/4=16,所以有16個整數
    strlength=num*16;
    for (unsigned int i = 0; i < num*16; i++)
        strByte[i]=0;
    for (unsigned int i=0; i <str.length(); i++)
    {
        strByte[i>>2]|=(str[i])<<((i%4)*8);//一個整數存儲四個字節,i>>2表示i/4 一個unsigned int對應4個字節,保存4個字符信息
    }
    strByte[str.length()>>2]|=0x80<<(((str.length()%4))*8);//尾部添加1 一個unsigned int保存4個字符信息,所以用128左移
    /*
    *添加原長度,長度指位的長度,所以要乘8,然後是小端序,所以放在倒數第二個,這裏長度只用了32位
    */
    strByte[num*16-2]=str.length()*8;
    return strByte;
}
string changeHex(int a)
{
    int b;
    string str1;
    string str="";
    for(int i=0;i<4;i++)
    {
        str1="";
        b=((a>>i*8)%(1<<8))&0xff;   //逆序處理每個字節
        for (int j = 0; j < 2; j++)
        {
            str1.insert(0,1,str16[b%16]);
            b=b/16;
        }
        str+=str1;
    }
    return str;
}
string getMD5(string source)
{
    atemp=A;    //初始化
    btemp=B;
    ctemp=C;
    dtemp=D;
    unsigned int *strByte=add(source);
    for(unsigned int i=0;i<strlength/16;i++)
    {
        unsigned int num[16];
        for(unsigned int j=0;j<16;j++)
            num[j]=strByte[i*16+j];
        mainLoop(num);
    }
    return changeHex(atemp).append(changeHex(btemp)).append(changeHex(ctemp)).append(changeHex(dtemp));
}
unsigned int main()
{
    string ss;
//    cin>>ss;
    string s=getMD5("abc");
    cout<<s;
    return 0;
}

MD5JAVA實現

public class MD5{
    /*
    *四個鏈接變量
    */
    private final int A=0x67452301;
    private final int B=0xefcdab89;
    private final int C=0x98badcfe;
    private final int D=0x10325476;
    /*
    *ABCD的臨時變量
    */
    private int Atemp,Btemp,Ctemp,Dtemp;
    
    /*
    *常量ti
    *公式:floor(abs(sin(i+1))×(2pow32)
    */
    private final int K[]={
        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};
    /*
    *向左位移數,計算方法未知
    */
    private final int s[]={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};
    
    
    /*
    *初始化函數
    */
    private void init(){
        Atemp=A;
        Btemp=B;
        Ctemp=C;
        Dtemp=D;
    }
    /*
    *移動一定位數
    */
    private    int    shift(int a,int s){
        return(a<<s)|(a>>>(32-s));//右移的時候,高位一定要補零,而不是補充符號位
    }
    /*
    *主循環
    */
    private void MainLoop(int M[]){
        int F,g;
        int a=Atemp;
        int b=Btemp;
        int c=Ctemp;
        int d=Dtemp;
        for(int i = 0; i < 64; i ++){
            if(i<16){
                F=(b&c)|((~b)&d);
                g=i;
            }else if(i<32){
                F=(d&b)|((~d)&c);
                g=(5*i+1)%16;
            }else if(i<48){
                F=b^c^d;
                g=(3*i+5)%16;
            }else{
                F=c^(b|(~d));
                g=(7*i)%16;
            }
            int tmp=d;
            d=c;
            c=b;
            b=b+shift(a+F+K[i]+M[g],s[i]);
            a=tmp;
        }
        Atemp=a+Atemp;
        Btemp=b+Btemp;
        Ctemp=c+Ctemp;
        Dtemp=d+Dtemp;
    
    }
    /*
    *填充函數
    *處理後應滿足bits≡448(mod512),字節就是bytes≡56(mode64)
    *填充方式為先加一個0,其它位補零
    *最後加上64位的原來長度
    */
    private int[] add(String str){
        int num=((str.length()+8)/64)+1;//以512位,64個字節為一組
        int strByte[]=new int[num*16];//64/4=16,所以有16個整數
        for(int i=0;i<num*16;i++){//全部初始化0
            strByte[i]=0;
        }
        int    i;
        for(i=0;i<str.length();i++){
            strByte[i>>2]|=str.charAt(i)<<((i%4)*8);//一個整數存儲四個字節,小端序
        }
        strByte[i>>2]|=0x80<<((i%4)*8);//尾部添加1
        /*
        *添加原長度,長度指位的長度,所以要乘8,然後是小端序,所以放在倒數第二個,這裏長度只用了32位
        */
        strByte[num*16-2]=str.length()*8;
            return strByte;
    }
    /*
    *調用函數
    */
    public String getMD5(String source){
        init();
        int strByte[]=add(source);
        for(int i=0;i<strByte.length/16;i++){
        int num[]=new int[16];
        for(int j=0;j<16;j++){
            num[j]=strByte[i*16+j];
        }
        MainLoop(num);
        }
        return changeHex(Atemp)+changeHex(Btemp)+changeHex(Ctemp)+changeHex(Dtemp);
    
    }
    /*
    *整數變成16進制字符串
    */
    private String changeHex(int a){
        String str="";
        for(int i=0;i<4;i++){
            str+=String.format("%2s", Integer.toHexString(((a>>i*8)%(1<<8))&0xff)).replace(' ', '0');

        }
        return str;
    }
    /*
    *單例
    */
    private static MD5 instance;
    public static MD5 getInstance(){
        if(instance==null){
            instance=new MD5();
        }
        return instance;
    }
    
    private MD5(){};
    
    public static void main(String[] args){
        String str=MD5.getInstance().getMD5("");
        System.out.println(str);
    }
}


結果錯誤

MD5VB2010實現

Imports System

Imports System.Security.Cryptography

Imports System.Text


Module Example
    '哈希輸入字符串並返回一個 32 字符的十六進制字符串哈希。

    Function GetMd5Hash(ByVal input As String) As String

        '創建新的一個 MD5CryptoServiceProvider 對象的實例。

        Dim md5Hasher As New MD5CryptoServiceProvider()

        '輸入的字符串轉換為字節數組,並計算哈希。

        Dim data As Byte() = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input))

        '創建一個新的 StringBuilder 收集的字節,並創建一個字符串。

        Dim sBuilder As New StringBuilder()

        '通過每個字節的哈希數據和格式為十六進制字符串的每一個循環。

        For i As Integer = 0 To data.Length - 1

            sBuilder.Append(data(i).ToString("x2"))

        Next

        '返回十六進制字符串。

        Return sBuilder.ToString()

    End Function


    '驗證對一個字符串的哈希值。

    Function VerifyMd5Hash(ByVal input As String, ByVal hash As String) As Boolean

        '哈希的輸入。

        Dim hashOfInput As String = GetMd5Hash(input)

        '創建 StringComparer 的哈希進行比較。

        Dim comparer As StringComparer = StringComparer.OrdinalIgnoreCase

        Return comparer.Compare(hashOfInput, hash) = 0

    End Function


    Sub Main()

        Dim source As String = "Hello World!"

        Dim hash As String = GetMd5Hash(source)

        Console.WriteLine($"進行MD5加密的字符串為:{source},加密的結果是:{hash}。")

        Console.WriteLine("正在驗證哈希……")

        If VerifyMd5Hash(source, hash) Then

            Console.WriteLine("哈希值是
相同的。")

Else

            Console.WriteLine("哈希值是不相同的。")

EndIf

    EndSub

EndModule


'此代碼示例產生下面的輸出:


'進行MD5加密的字符串為:Hello World!,加密的結果是:ed076287532e86365e841e92bfc50d8c。

'正在驗證哈希……

'哈希值是相同的。

MD5JavaScript實現

JavaScript 版本的實現代碼,可以用於瀏覽器中運行和計算文本字符串的 MD5。
function md5(string) {
    function md5_RotateLeft(lValue, iShiftBits) {
        return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits));
    }
    function md5_AddUnsigned(lX, lY) {
        var lX4, lY4, lX8, lY8, lResult;
        lX8 = (lX & 0x80000000);
        lY8 = (lY & 0x80000000);
        lX4 = (lX & 0x40000000);
        lY4 = (lY & 0x40000000);
        lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF);
        if (lX4 & lY4) {
            return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
        }
        if (lX4 | lY4) {
            if (lResult & 0x40000000) {
                return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
            } else {
                return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
            }
        } else {
            return (lResult ^ lX8 ^ lY8);
        }
    }
    function md5_F(x, y, z) {
        return (x & y) | ((~x) & z);
    }
    function md5_G(x, y, z) {
        return (x & z) | (y & (~z));
    }
    function md5_H(x, y, z) {
        return (x ^ y ^ z);
    }
    function md5_I(x, y, z) {
        return (y ^ (x | (~z)));
    }
    function md5_FF(a, b, c, d, x, s, ac) {
        a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_F(b, c, d), x), ac));
        return md5_AddUnsigned(md5_RotateLeft(a, s), b);
    };
    function md5_GG(a, b, c, d, x, s, ac) {
        a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_G(b, c, d), x), ac));
        return md5_AddUnsigned(md5_RotateLeft(a, s), b);
    };
    function md5_HH(a, b, c, d, x, s, ac) {
        a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_H(b, c, d), x), ac));
        return md5_AddUnsigned(md5_RotateLeft(a, s), b);
    };
    function md5_II(a, b, c, d, x, s, ac) {
        a = md5_AddUnsigned(a, md5_AddUnsigned(md5_AddUnsigned(md5_I(b, c, d), x), ac));
        return md5_AddUnsigned(md5_RotateLeft(a, s), b);
    };
    function md5_ConvertToWordArray(string) {
        var lWordCount;
        var lMessageLength = string.length;
        var lNumberOfWords_temp1 = lMessageLength + 8;
        var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64;
        var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16;
        var lWordArray = Array(lNumberOfWords - 1);
        var lBytePosition = 0;
        var lByteCount = 0;
        while (lByteCount < lMessageLength) {
            lWordCount = (lByteCount - (lByteCount % 4)) / 4;
            lBytePosition = (lByteCount % 4) * 8;
            lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount) << lBytePosition));
            lByteCount++;
        }
        lWordCount = (lByteCount - (lByteCount % 4)) / 4;
        lBytePosition = (lByteCount % 4) * 8;
        lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition);
        lWordArray[lNumberOfWords - 2] = lMessageLength << 3;
        lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29;
        return lWordArray;
    };
    function md5_WordToHex(lValue) {
        var WordToHexValue = "",
        WordToHexValue_temp = "",
        lByte, lCount;
        for (lCount = 0; lCount <= 3; lCount++) {
            lByte = (lValue >>> (lCount * 8)) & 255;
            WordToHexValue_temp = "0" + lByte.toString(16);
            WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length - 2, 2);
        }
        return WordToHexValue;
    };
    function md5_Utf8Encode(string) {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }
        return utftext;
    };
    var x = Array();
    var k, AA, BB, CC, DD, a, b, c, d;
    var S11 = 7,
    S12 = 12,
    S13 = 17,
    S14 = 22;
    var S21 = 5,
    S22 = 9,
    S23 = 14,
    S24 = 20;
    var S31 = 4,
    S32 = 11,
    S33 = 16,
    S34 = 23;
    var S41 = 6,
    S42 = 10,
    S43 = 15,
    S44 = 21;
    string = md5_Utf8Encode(string);
    x = md5_ConvertToWordArray(string);
    a = 0x67452301;
    b = 0xEFCDAB89;
    c = 0x98BADCFE;
    d = 0x10325476;
    for (k = 0; k < x.length; k += 16) {
        AA = a;
        BB = b;
        CC = c;
        DD = d;
        a = md5_FF(a, b, c, d, x[k + 0], S11, 0xD76AA478);
        d = md5_FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756);
        c = md5_FF(c, d, a, b, x[k + 2], S13, 0x242070DB);
        b = md5_FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE);
        a = md5_FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF);
        d = md5_FF(d, a, b, c, x[k + 5], S12, 0x4787C62A);
        c = md5_FF(c, d, a, b, x[k + 6], S13, 0xA8304613);
        b = md5_FF(b, c, d, a, x[k + 7], S14, 0xFD469501);
        a = md5_FF(a, b, c, d, x[k + 8], S11, 0x698098D8);
        d = md5_FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF);
        c = md5_FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1);
        b = md5_FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE);
        a = md5_FF(a, b, c, d, x[k + 12], S11, 0x6B901122);
        d = md5_FF(d, a, b, c, x[k + 13], S12, 0xFD987193);
        c = md5_FF(c, d, a, b, x[k + 14], S13, 0xA679438E);
        b = md5_FF(b, c, d, a, x[k + 15], S14, 0x49B40821);
        a = md5_GG(a, b, c, d, x[k + 1], S21, 0xF61E2562);
        d = md5_GG(d, a, b, c, x[k + 6], S22, 0xC040B340);
        c = md5_GG(c, d, a, b, x[k + 11], S23, 0x265E5A51);
        b = md5_GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA);
        a = md5_GG(a, b, c, d, x[k + 5], S21, 0xD62F105D);
        d = md5_GG(d, a, b, c, x[k + 10], S22, 0x2441453);
        c = md5_GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681);
        b = md5_GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8);
        a = md5_GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6);
        d = md5_GG(d, a, b, c, x[k + 14], S22, 0xC33707D6);
        c = md5_GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87);
        b = md5_GG(b, c, d, a, x[k + 8], S24, 0x455A14ED);
        a = md5_GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905);
        d = md5_GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8);
        c = md5_GG(c, d, a, b, x[k + 7], S23, 0x676F02D9);
        b = md5_GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A);
        a = md5_HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942);
        d = md5_HH(d, a, b, c, x[k + 8], S32, 0x8771F681);
        c = md5_HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122);
        b = md5_HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C);
        a = md5_HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44);
        d = md5_HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9);
        c = md5_HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60);
        b = md5_HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70);
        a = md5_HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6);
        d = md5_HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA);
        c = md5_HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085);
        b = md5_HH(b, c, d, a, x[k + 6], S34, 0x4881D05);
        a = md5_HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039);
        d = md5_HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5);
        c = md5_HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8);
        b = md5_HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665);
        a = md5_II(a, b, c, d, x[k + 0], S41, 0xF4292244);
        d = md5_II(d, a, b, c, x[k + 7], S42, 0x432AFF97);
        c = md5_II(c, d, a, b, x[k + 14], S43, 0xAB9423A7);
        b = md5_II(b, c, d, a, x[k + 5], S44, 0xFC93A039);
        a = md5_II(a, b, c, d, x[k + 12], S41, 0x655B59C3);
        d = md5_II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92);
        c = md5_II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D);
        b = md5_II(b, c, d, a, x[k + 1], S44, 0x85845DD1);
        a = md5_II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F);
        d = md5_II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0);
        c = md5_II(c, d, a, b, x[k + 6], S43, 0xA3014314);
        b = md5_II(b, c, d, a, x[k + 13], S44, 0x4E0811A1);
        a = md5_II(a, b, c, d, x[k + 4], S41, 0xF7537E82);
        d = md5_II(d, a, b, c, x[k + 11], S42, 0xBD3AF235);
        c = md5_II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB);
        b = md5_II(b, c, d, a, x[k + 9], S44, 0xEB86D391);
        a = md5_AddUnsigned(a, AA);
        b = md5_AddUnsigned(b, BB);
        c = md5_AddUnsigned(c, CC);
        d = md5_AddUnsigned(d, DD);
    }
    return (md5_WordToHex(a) + md5_WordToHex(b) + md5_WordToHex(c) + md5_WordToHex(d)).toLowerCase();
}

參考資料
  • 1.    黃聲國, 吳蕃. 淺談RSA公司事件“後門”[J]. 中國金融電腦, 2014(7):62-64.
  • 2.    麼麗穎. MD5算法的分析和改進[J]. 哈爾濱師範大學自然科學學報, 2011, 27(5):34-37.
  • 3.    王可. MD5算法研究[J]. 中文信息, 2002(2):78-81.
  • 4.    Kaliski B. The MD2 Message-Digest Algorithm[M]. 1992.
  • 5.    Rivest R. The MD4 Message-Digest Algorithm[M]// Advances in Cryptology-CRYPT0’ 90. 1990.
  • 6.    劉俊輝. MD5 消息摘要算法實現及改進[J]. 福建電腦, 2007 (4): 92-93.
  • 7.    舒暢. MD5 算法原理及其碰撞攻擊[J]. 軟件導刊, 2007, 6(6): 103-104.
  • 8.    桑海, 李建寶. 加密算法MD5的研究與應用[J]. 金融科技時代, 2006, 14(4):74-77.
  • 9.    How To Find Weak Input Differences For MD5 Collision Attacks  .eprint[引用日期2019-08-20]
  • 10.    [1]段國雲,陳浩,黃文,唐亞純.一種Web程序防篡改系統的設計與實現[J].計算機工程,2014,40(5):149-153