MD5即Message-Digest Algorithm5(信息摘要算法5),是计算机广泛使用的散列算法之一(又译摘要算法、哈希算法)。经MD2、MD3和MD4发展而来,诞生于20世纪90年代初。用于确保信息传输完整一致。虽然已被破解,但仍然具有较好的安全性,加之可以免费使用,所以仍广泛运用于数字签名、文件完整性验证以及口令加密等领域。
MD5即Message-Digest Algorithm5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。
基本简介
MD5的全称是Message-digest Algorithm5(中文译作信息摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的散列算法之一。它经MD2、MD3和MD4发展而来,在20世纪90年代初诞生。

它的作用是让大容量信息在用数字签名软件签署私人密钥前被“压缩”成一种保密的格式(就是把一个任意长度的字节串变换成一定长的大整数)。不管是MD2、MD4还是MD5,它们都需要获得一个随机长度的信息并产生一个128位的信息摘要。虽然这些算法的结构或多或少有些相似,但MD2的设计与MD4和MD5完全不同,那是因为MD2是为8位机器做过设计优化的,而MD4和MD5却是面向32位的电脑。[1]
发展历史
1992年8月,罗纳德·李维斯特向互联网工程任务组(IETF)提交了一份重要文件,描述了这种算法的原理。由于这种算法的公开性和安全性,在90年代被广泛使用在各种程序语言中,用以确保资料传递无误等。

MD5由MD4、MD3、MD2改进而来,主要增强算法复杂度和不可逆性。MD5算法因其普遍、稳定、快速的特点,仍广泛应用于普通数据的加密保护领域。
MD2
Rivest在1989年开发出MD2算法。在这个算法中,首先对信息进行数据补位,使信息的字节长度是16的倍数。然后,以一个16位的校验和追加到信息末尾,并且根据这个新产生的信息计算出散列值。后来,Rogier和Chauvaud发现如果忽略了校验和MD2将产生冲突。MD2算法加密后结果是唯一的(即不同信息加密后的结果不同)。
MD4
为了加强算法的安全性,Rivest在1990年又开发出MD4算法。MD4算法同样需要填补信息以确保信息的比特位长度减去448后能被512整除(信息比特位长度mod512=448)。然后,一个以64位二进制表示的信息的最初长度被添加进来。信息被处理成512位damgard/merkle迭代结构的区块,而且每个区块要通过三个不同步骤的处理。Denboer和Bosselaers以及其他人很快地发现了攻击MD4版本中第一步和第三步的漏洞。Dobbertin向大家演示了如何利用一部普通的个人电脑在几分钟内找到MD4完整版本中的冲突(这个冲突实际上是一种漏洞,它将导致对不同的内容进行加密却可能得到相同的加密后结果)。
MD5
1991年,Rivest开发出技术上更为趋近成熟的MD5算法。它在MD4的基础上增加了"安全带"(safety-belts)的概念。虽然MD5比MD4复杂度大一些,但却更为安全。这个算法很明显的由四个和MD4设计有少许不同的步骤组成。在MD5算法中,信息-摘要的大小和填充的必要条件与MD4完全相同。Den boer和Bosselaers曾发现MD5算法中的假冲突(pseudo-collisions),但除此之外就没有其他被发现的加密后结果了。
研究简介
2004年8月17日的美国加州圣巴巴拉的国际密码学会议(Crypto2004)上,来自中国山东大学的王小云教授做了破译MD5、HAVAL-128、MD4和RIPEMD算法的报告,公布了MD系列算法的破解结果。宣告了世界通行密码标准MD5不再安全。

2008年,荷兰埃因霍芬技术大学科学家成功把2个可执行文件进行了MD5碰撞,使得这两个运行结果不同的程序被计算出同一个MD。2008年12月一组科研人员通过MD5碰撞成功生成了伪造的SSL证书,这使得在https协议中服务器可以伪造一些根CA的签名。
安全性分析
MD5相对MD4所作的改进:
增加了第四轮。每一步均有唯一的加法常数。减弱第二轮中函数的对称性。第一步加上了上一步的结果,这将引起更快的雪崩效应(就是对明文或者密钥改变1bit都会引起密文的巨大不同)。改变了第二轮和第三轮中访问消息子分组的次序,使其更不相似。近似优化了每一轮中的循环左移位移量以实现更快的雪崩效应,各轮的位移量互不相同。
MD5算法自诞生之日起,就有很多人试图证明和发现它的不安全之处,即存在碰撞(在对两个不同的内容使用MD5算法运算的时候,有可能得到一对相同的结果值)。2009年,中国科学院的谢涛和冯登国仅用了的碰撞算法复杂度,破解了MD5的碰撞抵抗,该攻击在普通计算机上运行只需要数秒钟。
破解方法
对信息系统或者网站系统来说,MD5算法主要用在用户注册口令的加密,对于普通强度的口令加密,可以通过以下三种方式进行破解:
(1)在线查询密码。一些在线的MD5值查询网站提供MD5密码值的查询,输入MD5密码值后,如果在数据库中存在,那么可以很快获取其密码值。
(2)使用MD5破解工具。网络上有许多针对MD5破解的专用软件,通过设置字典来进行破解。
(3)通过社会工程学来获取或者重新设置用户的口令。
因此简单的MD5加密是没有办法达到绝对的安全的,因为普通的MD5加密有多种暴力破解方式,因此如果想要保证信息系统或者网站的安全,需要对MD5进行改造,增强其安全性。但对于公司以及普通用户来说,从算法上来破解MD5非常困难,因此MD5仍然算是一种安全的算法。
代码简介
C++实现
#include<iostream>#include<string>usingnamespacestd;#defineshift(x,n)(((x)<<(n))|((x)>>(32-(n))))//右移的时候,高位一定要补零,而不是补充符号位#defineF(x,y,z)(((x)&(y))|((~x)&(z)))#defineG(x,y,z)(((x)&(z))|((y)&(~z)))#defineH(x,y,z)((x)^(y)^(z))#defineI(x,y,z)((y)^((x)|(~z)))#defineA0x67452301#defineB0xefcdab89#defineC0x98badcfe#defineD0x10325476//strBaye的长度unsignedintstrlength;//A,B,C,D的临时变量unsignedintatemp;unsignedintbtemp;unsignedintctemp;unsignedintdtemp;//常量tiunsignedint(abs(sin(i+1))*(2pow32))constunsignedintk[]={0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,//向左位移数constunsignedints[]={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};constcharstr16[]="0123456789abcdef";voidmainLoop(unsignedintM[]){unsignedintf,g;unsignedinta=atemp;unsignedintb=btemp;unsignedintc=ctemp;unsignedintd=dtemp;for(unsignedinti=0;i<64;i++){if(i<16){f=F(b,c,d);g=i;}elseif(i<32){f=G(b,c,d);g=(5*i+1)%16;}elseif(i<48){f=H(b,c,d);g=(3*i+5)%16;}else{f=I(b,c,d);g=(7*i)%16;}unsignedinttmp=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位的原来长度*/unsignedint*add(stringstr){unsignedintnum=((str.length()+8)/64)+1;//以512位,64个字节为一组unsignedint*strByte=newunsignedint[num*16];//64/4=16,所以有16个整数strlength=num*16;for(unsignedinti=0;i<num*16;i++)strByte[i]=0;for(unsignedinti=0;i<str.length();i++){strByte[i>>2]|=(str[i])<<((i%4)*8);//一个整数存储四个字节,i>>2表示i/4一个unsignedint对应4个字节,保存4个字符信息}strByte[str.length()>>2]|=0x80<<(((str.length()%4))*8);//尾部添加1一个unsignedint保存4个字符信息,所以用128左移/**添加原长度,长度指位的长度,所以要乘8,然后是小端序,所以放在倒数第二个,这里长度只用了32位*/strByte[num*16-2]=str.length()*8;returnstrByte;}stringchangeHex(inta){intb;stringstr1;stringstr="";for(inti=0;i<4;i++){str1="";b=((a>>i*8)%(1<<8))&0xff;//逆序处理每个字节for(intj=0;j<2;j++){str1.insert(0,1,str16[b%16]);b=b/16;}str+=str1;}returnstr;}stringgetMD5(stringsource){atemp=A;//初始化btemp=B;ctemp=C;dtemp=D;unsignedint*strByte=add(source);for(unsignedinti=0;i<strlength/16;i++){unsignedintnum;for(unsignedintj=0;j<16;j++)num[j]=strByte[i*16+j];mainLoop(num);}returnchangeHex(atemp).append(changeHex(btemp)).append(changeHex(ctemp)).append(changeHex(dtemp));}unsignedintmain(){stringss;//cin>>ss;strings=getMD5("abc");cout<<s;return0;}
JAVA实现
publicclassMD5{/**四个链接变量*/privatefinalintA=0x67452301;privatefinalintB=0xefcdab89;privatefinalintC=0x98badcfe;privatefinalintD=0x10325476;/**ABCD的临时变量*/privateintAtemp,Btemp,Ctemp,Dtemp;/**常量ti*公式:floor(abs(sin(i+1))×(2pow32)*/privatefinalintK[]={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};/**向左位移数,计算方法未知*/privatefinalints[]={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};/**初始化函数*/privatevoidinit(){Atemp=A;Btemp=B;Ctemp=C;Dtemp=D;}/**移动一定位数*/privateintshift(inta,ints){return(a<<s)|(a>>>(32-s));//右移的时候,高位一定要补零,而不是补充符号位}/**主循环*/privatevoidMainLoop(intM[]){intF,g;inta=Atemp;intb=Btemp;intc=Ctemp;intd=Dtemp;for(inti=0;i<64;i++){if(i<16){F=(b&c)|((~b)&d);g=i;}elseif(i<32){F=(d&b)|((~d)&c);g=(5*i+1)%16;}elseif(i<48){F=b^c^d;g=(3*i+5)%16;}else{F=c^(b|(~d));g=(7*i)%16;}inttmp=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位的原来长度*/privateint[]add(Stringstr){intnum=((str.length()+8)/64)+1;//以512位,64个字节为一组intstrByte[]=newint[num*16];//64/4=16,所以有16个整数for(inti=0;i<num*16;i++){//全部初始化0strByte[i]=0;}inti;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;returnstrByte;}/**调用函数*/publicStringgetMD5(Stringsource){init();intstrByte[]=add(source);for(inti=0;i<strByte.length/16;i++){intnum[]=newint;for(intj=0;j<16;j++){num[j]=strByte[i*16+j];}MainLoop(num);}returnchangeHex(Atemp)+changeHex(Btemp)+changeHex(Ctemp)+changeHex(Dtemp);}/**整数变成16进制字符串*/privateStringchangeHex(inta){Stringstr="";for(inti=0;i<4;i++){str+=String.format("%2s",Integer.toHexString(((a>>i*8)%(1<<8))&0xff)).replace('','0');}returnstr;}/**单例*/privatestaticMD5instance;publicstaticMD5getInstance(){if(instance==null){instance=newMD5();}returninstance;}privateMD5(){};publicstaticvoidmain(String[]args){Stringstr=MD5.getInstance().getMD5("");System.out.println(str);}}结果错误
VB2010实现
ImportsSystemImportsSystem.Security.CryptographyImportsSystem.TextModuleExample'哈希输入字符串并返回一个32字符的十六进制字符串哈希。FunctionGetMd5Hash(ByValinputAsString)AsString'创建新的一个MD5CryptoServiceProvider对象的实例。Dimmd5HasherAsNewMD5CryptoServiceProvider()'输入的字符串转换为字节数组,并计算哈希。DimdataAsByte()=md5Hasher.ComputeHash(Encoding.Default.GetBytes(input))'创建一个新的StringBuilder收集的字节,并创建一个字符串。DimsBuilderAsNewStringBuilder()'通过每个字节的哈希数据和格式为十六进制字符串的每一个循环。ForiAsInteger=0Todata.Length-1sBuilder.Append(data(i).ToString("x2"))Next'返回十六进制字符串。ReturnsBuilder.ToString()EndFunction'验证对一个字符串的哈希值。FunctionVerifyMd5Hash(ByValinputAsString,ByValhashAsString)AsBoolean'哈希的输入。DimhashOfInputAsString=GetMd5Hash(input)'创建StringComparer的哈希进行比较。DimcomparerAsStringComparer=StringComparer.OrdinalIgnoreCaseReturncomparer.Compare(hashOfInput,hash)=0EndFunctionSubMain()DimsourceAsString="HelloWorld!"DimhashAsString=GetMd5Hash(source)Console.WriteLine($"进行MD5加密的字符串为:{source},加密的结果是:{hash}。")Console.WriteLine("正在验证哈希……")IfVerifyMd5Hash(source,hash)ThenConsole.WriteLine("哈希值是相同的。")ElseConsole.WriteLine("哈希值是不相同的。")EndIfEndSubEndModule'此代码示例产生下面的输出:'进行MD5加密的字符串为:HelloWorld!,加密的结果是:ed076287532e86365e841e92bfc50d8c。'正在验证哈希……'哈希值是相同的。
JavaScript版本的实现代码,可以用于浏览器中运行和计算文本字符串的MD5。
functionmd5(string){functionmd5_RotateLeft(lValue,iShiftBits){return(lValue<<iShiftBits)|(lValue>>>(32-iShiftBits));}functionmd5_AddUnsigned(lX,lY){varlX4,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);}}functionmd5_F(x,y,z){return(x&y)|((~x)&z);}functionmd5_G(x,y,z){return(x&z)|(y&(~z));}functionmd5_H(x,y,z){return(x^y^z);}functionmd5_I(x,y,z){return(y^(x|(~z)));}functionmd5_FF(a,b,c,d,x,s,ac){a=md5_AddUnsigned(a,md5_AddUnsigned(md5_AddUnsigned(md5_F(b,c,d),x),ac));returnmd5_AddUnsigned(md5_RotateLeft(a,s),b);};functionmd5_GG(a,b,c,d,x,s,ac){a=md5_AddUnsigned(a,md5_AddUnsigned(md5_AddUnsigned(md5_G(b,c,d),x),ac));returnmd5_AddUnsigned(md5_RotateLeft(a,s),b);};functionmd5_HH(a,b,c,d,x,s,ac){a=md5_AddUnsigned(a,md5_AddUnsigned(md5_AddUnsigned(md5_H(b,c,d),x),ac));returnmd5_AddUnsigned(md5_RotateLeft(a,s),b);};functionmd5_II(a,b,c,d,x,s,ac){a=md5_AddUnsigned(a,md5_AddUnsigned(md5_AddUnsigned(md5_I(b,c,d),x),ac));returnmd5_AddUnsigned(md5_RotateLeft(a,s),b);};functionmd5_ConvertToWordArray(string){varlWordCount;varlMessageLength=string.length;varlNumberOfWords_temp1=lMessageLength+8;varlNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1%64))/64;varlNumberOfWords=(lNumberOfWords_temp2+1)*16;varlWordArray=Array(lNumberOfWords-1);varlBytePosition=0;varlByteCount=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;returnlWordArray;};functionmd5_WordToHex(lValue){varWordToHexValue="",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);}returnWordToHexValue;};functionmd5_Utf8Encode(string){string=string.replace(/\r\n/g,"\n");varutftext="";for(varn=0;n<string.length;n++){varc=string.charCodeAt(n);if(c<128){utftext+=String.fromCharCode(c);}elseif((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);}}returnutftext;};varx=Array();vark,AA,BB,CC,DD,a,b,c,d;varS11=7,S12=12,S13=17,S14=22;varS21=5,S22=9,S23=14,S24=20;varS31=4,S32=11,S33=16,S34=23;varS41=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();}
加密原理
MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

在MD5算法中,首先需要对信息进行填充,使其字节长度对512求余数的结果等于448。因此,信息的字节长度(BitsLength)将被扩展至N*512+448,即N*64+56个字节(Bytes),N为一个正整数。填充的方法如下,在信息的后面填充一个1和无数个0个,直到满足上面的条件时才停止用0对信息的填充。然后再在这个结果后面附加一个以64位二进制表示的填充前的信息长度。经过这两步的处理,现在的信息字节长度=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍数。这样做的原因是为满足后面处理中对信息长度的要求。MD5中有四个32位被称作链接变量(ChainingVariable)的整数参数,他们分别为:A=0x01234567,B=0x89abcdef,C=0xfedcba98,D=0x76543210。当设置好这四个链接变量后,就开始进入算法的四轮循环运算,循环的次数是信息中512位信息分组的数目。
将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。主循环有四轮(MD4只有三轮),每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量(文本中的一个子分组和一个常数)。
再将所得结果向右环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之一。以一下是每次操作中用到的四个非线性函数(每轮一个)。
F(X,Y,Z)=(X∧Y)∨((X)∧Z)
G(X,Y,Z)=(X∧Z)∨(Y∧(Z))
H(X,Y,Z)=X⊕Y⊕Z
I(X,Y,Z)=Y⊕(X∨(Z))
其中,⊕是异或,∧是与,∨是或,是反符号。
如果X、Y和Z的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。F是一个逐位运算的函数。即,如果X,那么Y,否则Z。函数H是逐位奇偶操作符。所有这些完成之后,将A,B,C,D分别加上a,b,c,d。然后用下一分组数据继续运行算法,最后的输出是A,B,C和D的级联。最后得到的A,B,C,D就是输出结果,A是低位,D为高位,DCBA组成128位输出结果。
软件应用
用于密码管理
当我们需要保存某些密码信息以用于身份确认时,如果直接将密码信息以明码方式保存在数据库中,不使用任何保密措施,系统管理员就很容易能得到原来的密码信息,这些信息一旦泄露,密码也很容易被破译。为了增加安全性,有必要对数据库中需要保密的信息进行加密,这样,即使有人得到了整个数据库,如果没有解密算法,也不能得到原来的密码信息。MD5算法可以很好地解决这个问题,因为它可以将任意长度的输入串经过计算得到固定长度的输出,而且只有在明文相同的情况下,才能等到相同的密文,并且这个算法是不可逆的,即便得到了加密以后的密文,也不可能通过解密算法反算出明文。这样就可以把用户的密码以MD5值(或类似的其它算法)的方式保存起来,用户注册的时候,系统是把用户输入的密码计算成MD5值,然后再去和系统中保存的MD5值进行比较,如果密文相同,就可以认定密码是正确的,否则密码错误。通过这样的步骤,系统在并不知道用户密码明码的情况下就可以确定用户登录系统的合法性。这样不但可以避免用户的密码被具有系统管理员权限的用户知道,而且还在一定程度上增加了密码被破解的难度。
电子签名
MD5算法还可以作为一种电子签名的方法来使用,使用MD5算法就可以为任何文件(不管其大小、格式、数量)产生一个独一无二的“数字指纹”,借助这个“数字指纹”,通过检查文件前后MD5值是否发生了改变,就可以知道源文件是否被改动。我们在下载软件的时候经常会发现,软件的下载页面上除了会提供软件的下载地址以外,还会给出一串长长的字符串。这串字符串其实就是该软件的MD5值,它的作用就在于下载该软件后,对下载得到的文件用专门的软件(如Windows MD5 check等)做一次MD5校验,以确保我们获得的文件与该站点提供的文件为同一文件。利用MD5算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面。
垃圾邮件筛选
在电子邮件使用越来越普遍的情况下,可以利用MD5算法在邮件接收服务器上进行垃圾邮件的筛选,以减少此类邮件的干扰,具体思路如下:
建立一个邮件MD5值资料库,分别储存邮件的MD5值、允许出现的次数(假定为3)和出现次数(初值为零)。对每一封收到的邮件,将它的正文部分进行MD5计算,得到MD5值,将这个值在资料库中进行搜索。如未发现相同的MD5值,说明此邮件是第一次收到,将此MD5值存入资料库,并将出现次数置为1,转到第五步。如发现相同的MD5值,说明收到过同样内容的邮件,将出现次数加1,并与允许出现次数相比较,如小于允许出现次数,就转到第五步。否则中止接收该邮件。结束。接收该邮件。
参考资料 1
- 《计算机研究》:MD算法研究 — www.cnki.com.cn