MD5讯息摘要算法(英语MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函式,可以产生出一个128位(16位元组)的散列值(hash value),用于确保信息传输完整一致。MD5由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于1992年公开,用以取代MD4算法。
基本介绍
- 中文名讯息摘要算法
- 外文名Message Digest Algorithm MD5
- 别称摘要算法
- 提出时间1991年
- 套用学科信息技术,计算机科学
- 适用领域範围软体下载站、论坛资料库、系统档案安全
发展历史
MD2
Rivest在1989年开发出MD2算法。在这个算法中,对信息进行数据补位,使信息的位元组长度是16的倍数。然后,以一个16位的检验和追加到信息末尾,并且根据这个新产生的信息计算出散列值。后来,Rogier和Chauvaud发现如果忽略了检验和MD2将产生冲突。MD2算法加密后结果是唯一的(即不同信息加密后的结果不同)。
MD4
为了加强算法的安全性,Rivest在1990年又开发出MD4算法。MD4算法同样需要填补信息以确保信息的比特位长度减去448后能被512整除(信息比特位长度mod 512 = 448)。然后,一个以64位二进制表示的信息的最初长度被添加进来。信息被处理成512位damg?rd/merkle叠代结构的区块,而且每个区块要通过三个不同步骤的处理。Den boer和Bosselaers以及其他人很快的发现了攻击MD4版本中第一步和第三步的漏洞。Dobbertin向大家演示了如何利用一部普通的个人电脑在几分钟内找到MD4完整版本中的冲突(这个冲突实际上是一种漏洞,它将导致对不同的内容进行加密却可能得到相同的加密后结果)。毫无疑问,MD4就此被淘汰掉了。
儘管MD4算法在安全上有个这幺大的漏洞,但它对在其后才被开发出来的好几种信息安全加密算法的出现却有着不可忽视的引导作用。
MD5
1991年,Rivest开发出技术上更为趋近成熟的md5算法。它在MD4的基础上增加了"安全-带子"(safety-belts)的概念。虽然MD5比MD4複杂度大一些,但却更为安全。这个算法很明显的由四个和MD4设计有少许不同的步骤组成。在MD5算法中,信息-摘要的大小和填充的必要条件与MD4完全相同。Den boer和Bosselaers曾发现MD5算法中的假冲突(pseudo-collisions),但除此之外就没有其他被发现的加密后结果了。
MD5套用
一致性验证
MD5的典型套用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在Unix下有很多软体在下载的时候都有一个档案名称相同,档案扩展名为.md5的档案,在这个档案中通常只有一行文本,大致结构如
MD5 (tanajiya.tar.gz) = 38b8c2c1093dd0fec383a9d9ac940515
这就是tanajiya.tar.gz档案的数字签名。MD5将整个档案当作一个大文本信息,通过其不可逆的字元串变换算法,产生了这个唯一的MD5信息摘要。为了让读者朋友对MD5的套用有个直观的认识,笔者以一个比方和一个实例来简要描述一下其工作过程
大家都知道,地球上任何人都有自己独一无二的指纹,这常常成为司法机关鉴别罪犯身份最值得信赖的方法;与之类似,MD5就可以为任何档案(不管其大小、格式、数量)产生一个同样独一无二的“数字指纹”,如果任何人对档案做了任何改动,其MD5值也就是对应的“数字指纹”都会发生变化。
我们常常在某些软体下载站点的某软体信息中看到其MD5值,它的作用就在于我们可以在下载该软体后,对下载回来的档案用专门的软体(如Windows MD5 Check等)做一次MD5校验,以确保我们获得的档案与该站点提供的档案为同一档案。
具体来说档案的MD5值就像是这个档案的“数字指纹”。每个档案的MD5值是不同的,如果任何人对档案做了任何改动,其MD5值也就是对应的“数字指纹”就会发生变化。比如下载伺服器针对一个档案预先提供一个MD5值,用户下载完该档案后,用我这个算法重新计算下载档案的MD5值,通过比较这两个值是否相同,就能判断下载的档案是否出错,或者说下载的档案是否被篡改了。MD5实际上一种有损压缩技术,压缩前档案一样MD5值一定一样,反之MD5值一样并不能保证压缩前的数据是一样的。在密码学上发生这样的机率是很小的,所以MD5在密码加密领域有一席之地。专业的黑客甚至普通黑客也可以利用MD5值实际是有损压缩技术这一原理,将MD5的逆运算的值作为一张表俗称彩虹表的散列表来破解密码。
利用MD5算法来进行档案校验的方案被大量套用到软体下载站、论坛资料库、系统档案安全等方面。
数字签名
MD5的典型套用是对一段Message(位元组串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫 readme.txt档案中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个档案给别人,别人如果修改了档案中的任何内容,你对这个档案重新计算MD5时就会发现(两个MD5值不相同)。如果再有一个第三方的认证机构,用MD5还可以防止档案作者的“抵赖”,这就是所谓的数字签名套用。
安全访问认证
MD5还广泛用于作业系统的登入认证上,如Unix、各类BSD系统登录密码、数字签名等诸多方面。如在Unix系统中用户的密码是以MD5(或其它类似的算法)经Hash运算后存储在档案系统中。当用户登录的时候,系统把用户输入的密码进行MD5 Hash运算,然后再去和保存在档案系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这可以避免用户的密码被具有系统管理员许可权的用户知道。MD5将任意长度的“位元组串”映射为一个128bit的大整数,并且是通过该128bit反推原始字元串是困难的,换句话说就是,即使你看到源程式和算法描述,也无法将一个MD5的值变换回原始的字元串,从数学原理上说,是因为原始的字元串有无穷多个,这有点象不存在反函式的数学函式。所以,要遇到了md5密码的问题,比较好的办法是你可以用这个系统中的md5()函式重新设一个密码,如admin,把生成的一串密码的Hash值覆盖原来的Hash值就行了。
正是因为这个原因,现在被黑客使用最多的一种破译密码的方法就是一种被称为"跑字典"的方法。有两种方法得到字典,一种是日常蒐集的用做密码的字元串表,另一种是用排列组合方法生成的,先用MD5程式计算出这些字典项的MD5值,然后再用目标的MD5值在这个字典中检索。我们假设密码的最大长度为8位位元组(8 Bytes),密码只能是字母和数字,共26+26+10=62个位元组,排列组合出的字典的项数则是P(62,1)+P(62,2)….+P(62,8),那也已经是一个很天文的数字了,存储这个字典就需要TB级的磁碟阵列,而且这种方法还有一个前提,就是能获得目标账户的密码MD5值的情况下才可以。这种加密技术被广泛的套用于Unix系统中,这也是为什幺Unix系统比一般作业系统更为坚固一个重要原因。
算法原理
对MD5算法简要的叙述可以为MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
总体流程如下图所示, 表示第i个分组,每次的运算都由前一轮的128位结果值和第i块512bit值进行运算。
代码
C++实现
#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=(5i+1)%16; }else if(i<48){ f=H(b,c,d); g=(3i+5)%16; }else{ f=I(b,c,d); g=(7i)%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[num16]; //64/4=16,所以有16个整数 strlength=num16; for (unsigned int i = 0; i < num16; 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[num16-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>>i8)%(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[i16+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;}
JAVA实现
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=(5i+1)%16; }else if(i<48){ F=b^c^d; g=(3i+5)%16; }else{ F=c^(b|(~d)); g=(7i)%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[num16];//64/4=16,所以有16个整数 for(int i=0;i<num16;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[num16-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[i16+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>>i8)%(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); }}结果错误
VB2010实现
Imports SystemImports System.Security.CryptographyImports System.TextModule 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 EndSubEndModule'此代码示例产生下面的输出'进行MD5加密的字元串为Hello World!,加密的结果是ed076287532e86365e841e92bfc50d8c。'正在验证哈希……'哈希值是相同的。
JavaScript实现
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();}