3.1 单向散列算法详解
单向散列算法,又称Hash函数,Hash函数(也称杂凑函数或杂凑算法)就是把任意长的输入消息串变化成固定长的输出串的一种函数。这个输出串称为该消息的杂凑值。一般用于产生消息摘要,密钥加密等。
3.1.1 单向散列算法
单向散列函数指的是根据输入消息计算后,输出固定长度数值的算法,输出数值也称为“散列值”或“消息摘要”,其长度通常在128~256位之间。
一个安全的杂凑函数应该至少满足以下几个条件。
① 输入长度是任意的。
② 输出长度是固定的,根据目前的计算技术应至少取128bits长,以便抵抗生日攻击。
③ 对每一个给定的输入,计算输出即杂凑值是很容易的。
④ 给定杂凑函数的描述,找到两个不同的输入消息杂凑到同一个值是计算上不可行的,或给定杂凑函数的描述和一个随机选择的消息,找到另一个与该消息不同的消息,使得它们杂凑到同一个值是计算上不可行的。
单向散列函数的安全性是由于它的单向性,其输出不依赖于输入。平均而言,预映射值单个位的改变,将引起散列值中一半位的改变。已知一个散列值,要找到预映射的值,使它的散列值等于已知的散列值在计算上是不可行的。单向散列函数的安全性使它能用于完整性校验和提高数字签名的有效性。
常见散列函数(Hash函数)有以下几种。
· MD5(Message Digest Algorithm 5):是RSA数据安全公司开发的一种单向散列算法,MD5被广泛使用,可以用来把不同长度的数据块进行暗码运算成一个128位的数值。
· SHA(Secure Hash Algorithm):这是一种较新的散列算法,可以对任意长度的数据运算生成一个160位的数值。
· MAC(Message Authentication Code):消息认证代码,是一种使用密钥的单向函数,可以用它们在系统上或用户之间认证文件或消息,常见的是HMAC(用于消息认证的密钥散列算法)。
· CRC(Cyclic Redundancy Check):循环冗余校验码,CRC校验由于实现简单,检错能力强,被广泛使用在各种数据校验应用中。它占用系统资源少,用软硬件均能实现,是进行数据传输差错检测的一种很好的手段(CRC并不是严格意义上的散列算法,但它的作用与散列算法大致相同,所以归于此类)。
3.1.2 安全哈希算法
安全哈希算法(Secure Hash Algorithm,SHA)主要适用于数字签名标准(Digital Signature Standard,DSS)里面定义的数字签名算法(Digital Signature Algorithm,DSA)。对于长度小于264位的消息,SHA1会产生一个160位的消息摘要。该算法经过加密专家多年来的发展和改进已日益完善,并被广泛使用。该算法的思想是接收一段明文,然后以一种不可逆的方式将它转换成一段(通常更小)密文,也可以简单地理解为取一串输入码(称为预映射或信息),并把它们转化为长度较短、位数固定的输出序列即散列值(也称为信息摘要或信息认证代码)的过程。散列函数值可以说是对明文的一种“指纹”或是“摘要”,所以对散列值的数字签名就可以视为对此明文的数字签名。
安全散列算法SHA是美国国家标准技术研究所发布的国家标准FIPS PUB 180,最新的标准已经于2008年更新到FIPS PUB 180-3。其中规定了SHA-1,SHA-224,SHA-256, SHA-384和SHA-512这几种单向散列算法。SHA-1,SHA-224和SHA-256适用于长度不超过264二进制位的消息。SHA-384和SHA-512适用于长度不超过2128二进制位的消息。
在1993年,安全散列算法(SHA)由美国国家标准和技术协会(NIST)提出,并作为联邦信息处理标准(FIPS PUB 180)公布;1995年又发布了一个修订版FIPS PUB 180-1,通常被称为SHA-1。SHA-1是基于MD4算法的,并且它的设计在很大程度上模仿MD4,现在已成为公认的最安全的散列算法之一,并被广泛使用。
单向散列函数的安全性在于其产生散列值的操作过程具有较强的单向性。如果在输入序列中嵌入密码,那么任何人在不知道密码的情况下都不能产生正确的散列值,从而保证了其安全性。SHA将输入流按照每块512位(64个字节)进行分块,并产生20个字节的称为信息认证代码或信息摘要的输出。
该算法输入报文的长度不限,产生的输出是一个160位的报文摘要。输入是按512位的分组进行处理的。SHA-1是不可逆的、防冲突,并具有良好的雪崩效应。
通过散列算法可实现数字签名,数字签名的原理是将要传送的明文通过一种函数运算(Hash)转换成报文摘要(不同的明文对应不同的报文摘要),报文摘要加密后与明文一起传送给接收方,接收方将接收的明文产生新的报文摘要与发送方的发来报文摘要解密比较,比较结果一致表示明文未被改动,如果不一致表示明文已被篡改。
MAC(信息认证代码)就是一个散列结果,其中部分输入信息是密码,只有知道这个密码的参与者才能再次计算和验证MAC码的合法性。
SHA-1 Java实现源码 /* 安全散列算法SHA (Secure Hash Algorithm, SHA) */ public class SHA1 { private final int[] abcde = { 0x67452301,0xefcdab89,0x98badcfe, 0x10325476,0xc3d2e1f0 }; // 摘要数据存储数组 private int[] digestInt = new int[5]; // 计算过程中的临时数据存储数组 private int[] tmpData = new int[80]; // 计算sha-1摘要 private int process_input_bytes(byte[] bytedata) { // 初始化常量 System.arraycopy(abcde,0, digestInt,0, abcde.length); // 格式化输入字节数组,补10及长度数据 byte[] newbyte = byteArrayFormatData(bytedata); // 获取数据摘要计算的数据单元个数 int MCount = newbyte.length / 64; // 循环对每个数据单元进行摘要计算 for (int pos = 0; pos < MCount; pos++) { // 将每个单元的数据转换成16个整型数据,并保存到tmpData的前16个数组元素中 for (int j = 0; j < 16; j++) { tmpData[j] = byteArrayToInt(newbyte, (pos * 64)+ (j * 4)); } // 摘要计算函数 encrypt(); } return 20; } // 格式化输入字节数组格式 private byte[] byteArrayFormatData(byte[] bytedata) { // 补0数量 int zeros = 0; // 补位后总位数 int size = 0; // 原始数据长度 int n = bytedata.length; // 模64后的剩余位数 int m = n % 64; // 计算添加0的个数以及添加10后的总长度 if (m < 56) { zeros = 55- m; size = n - m + 64; } else if (m == 56) { zeros = 63; size = n + 8 + 64; } else { zeros = 63- m + 56; size = (n + 64)- m + 64; } // 补位后生成的新数组内容 byte[] newbyte = new byte[size]; // 复制数组的前面部分 System.arraycopy(bytedata,0, newbyte,0, n); // 获得数组Append数据元素的位置 int l = n; // 补1操作 newbyte[l++] = (byte) 0x80; // 补0操作 for (int i = 0; i < zeros; i++) { newbyte[l++] = (byte) 0x00; } // 计算数据长度,补数据长度位共8字节,长整型 long N = (long) n * 8; byte h8 = (byte) (N & 0xFF); byte h7 = (byte) ((N >> 8)& 0xFF); byte h6 = (byte) ((N >> 16)& 0xFF); byte h5 = (byte) ((N >> 24)& 0xFF); byte h4 = (byte) ((N >> 32)& 0xFF); byte h3 = (byte) ((N >> 40)& 0xFF); byte h2 = (byte) ((N >> 48)& 0xFF); byte h1 = (byte) (N >> 56); newbyte[l++] = h1; newbyte[l++] = h2; newbyte[l++] = h3; newbyte[l++] = h4; newbyte[l++] = h5; newbyte[l++] = h6; newbyte[l++] = h7; newbyte[l++] = h8; return newbyte; } private int f1(int x, int y, int z) { return (x & y) | (~x & z); } private int f2(int x, int y, int z) { return x ^ y ^ z; } private int f3(int x, int y, int z) { return (x & y) | (x & z) | (y & z); } private int f4(int x, int y) { return (x << y) | x >>>(32- y); } // 单元摘要计算函数 private void encrypt() { for (int i = 16; i <= 79; i++) { tmpData[i] = f4(tmpData[i -3] ^ tmpData[i -8] ^ tmpData[i -14] ^ tmpData[i -16],1); } int[] tmpabcde = new int[5]; for (int i1 = 0; i1 < tmpabcde.length; i1++) { tmpabcde[i1] = digestInt[i1]; } for (int j = 0; j <= 19; j++) { int tmp = f4(tmpabcde[0],5) + f1(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[j] + 0x5a827999; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1],30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int k = 20; k <= 39; k++) { int tmp = f4(tmpabcde[0],5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[k] + 0x6ed9eba1; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1],30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int l = 40; l <= 59; l++) { int tmp = f4(tmpabcde[0],5) + f3(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[l] + 0x8f1bbcdc; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1],30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int m = 60; m <= 79; m++) { int tmp = f4(tmpabcde[0],5) + f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] + tmpData[m] + 0xca62c1d6; tmpabcde[4] = tmpabcde[3]; tmpabcde[3] = tmpabcde[2]; tmpabcde[2] = f4(tmpabcde[1],30); tmpabcde[1] = tmpabcde[0]; tmpabcde[0] = tmp; } for (int i2 = 0; i2 < tmpabcde.length; i2++) { digestInt[i2] = digestInt[i2] + tmpabcde[i2]; } for (int n = 0; n < tmpData.length; n++) { tmpData[n] = 0; } } // 4字节数组转换为整数 private int byteArrayToInt(byte[] bytedata, int i) { return ((bytedata[i] & 0xff) << 24) | ((bytedata[i + 1] & 0xff) << 16)|((bytedata[i + 2] & 0xff) << 8) | (bytedata[i + 3] & 0xff); } // 整数转换为4字节数组 private void intToByteArray(int intValue, byte[] byteData, int i) { byteData[i] = (byte) (intValue >>> 24); byteData[i + 1] = (byte) (intValue >>> 16); byteData[i + 2] = (byte) (intValue >>> 8); byteData[i + 3] = (byte) intValue; } // 将字节转换为十六进制字符串 private static String byteToHexString(byte ib) { char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] ob = new char[2]; ob[0] = Digit[(ib >>> 4)& 0X0F]; ob[1] = Digit[ib & 0X0F]; String s = new String(ob); return s; } // 将字节数组转换为十六进制字符串 private static String byteArrayToHexString(byte[] bytearray) { String strDigest = ""; for (int i = 0; i < bytearray.length; i++) { strDigest += byteToHexString(bytearray[i]); } return strDigest; } // 计算sha-1摘要,返回相应的字节数组 public byte[] getDigestOfBytes(byte[] byteData) { process_input_bytes(byteData); byte[] digest = new byte[20]; for (int i = 0; i < digestInt.length; i++) { intToByteArray(digestInt[i], digest, i * 4); } return digest; } // 计算sha-1摘要,返回相应的十六进制字符串 public String getDigestOfString(byte[] byteData) { return byteArrayToHexString(getDigestOfBytes(byteData)); } // 测试 public static void main(String[] args) { String param = "wxzzs"; System.out.println("加密前:" + param); String digest = new SHA1().getDigestOfString(param.getBytes()); System.out.println("加密后:" + digest); } }
3.1.3 信息摘要算法的特点及应用
Message Digest Algorithm 5(消息摘要算法第五版,MD5)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。该算法的文件号为RFC 1321(R. Rivest, MIT Laboratory for Computer Science and RSA Data Security Inc. April 1992)。
MD5用于确保信息传输完整一致,是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已由MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。
MD5算法具有以下特点。
· 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
· 容易计算:从原数据计算出MD5的值很容易。
· 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
· 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被“压缩”成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串)。除了MD5以外,其中比较有名的还有sha-1、RIPEMD以及Haval等。
MD5的应用有以下几个方面。
1.一致性验证
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算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面。
2.数字签名
MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫readme.txt文件中,并对这个readme.txt产生一个MD5的值且记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现,两个MD5值不相同。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
3.安全访问认证
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位散列值。
总体流程如下图所示,从图中可知每次的运算都由前一轮的128位结果值和第i块512bit值进行运算。
MD5算法的整体流程图
(1)填充
在MD5算法中,首先需要对信息进行填充,使其位长对512求余的结果等于448,并且填充必须进行,即使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N×512+448, N为一个非负整数,N可以是0。
填充的方法如下。
① 在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。
② 在这个结果后面附加一个以64位二进制表示的填充前信息长度(单位为bit),如果二
进制表示的填充前信息长度超过64位,则取低64位。
经过这两步的处理,信息的位长=N×512+448+64=(N+1)×512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。
(2)初始化变量
初始的128位值为初试链接变量,这些参数用于第一轮的运算,以大端字节序来表示,它们分别为: A=0x01234567,B=0x89ABCDEF,C=0xFEDCBA98,D=0x76543210。
每一个变量给出的数值是高字节存于内存低地址,低字节存于内存高地址,即大端字节序。在程序中变量A、B、C、D的值分别为0x67452301,0xEFCDAB89,0x98BADCFE, 0x10325476。
(3)处理分组数据
每一分组的算法流程如下。
第一分组需要将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。从第二分组开始的变量为上一分组的运算结果,即A = a, B = b, C = c, D = d。
主循环有四轮(MD4只有三轮),每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中3个做一次非线性函数运算,然后将所得结果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向左环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之一。
以下是每次操作中用到的4个非线性函数(每轮一个)。
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) )
“&”是与(And),“|”是或(Or),“~”是非(Not),“^”是异或(Xor)。
如果X、Y和Z的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。
F是一个逐位运算的函数。即,如果X,那么Y,否则Z。函数H是逐位奇偶操作符。
假设Mj表示消息的第j个子分组(从0~15),常数ti是4294967296*abs( sin(i))的整数部分,i取值从1~64,单位是弧度。(4294967296=232)
现定义内容如下。
FF(a ,b ,c ,d ,Mj ,s ,ti) 操作为a = b + ( (a + F(b,c,d) + Mj + ti) << s)
GG(a ,b ,c ,d ,Mj ,s ,ti) 操作为a = b + ( (a + G(b,c,d) + Mj + ti) << s)
HH(a ,b ,c ,d ,Mj ,s ,ti) 操作为a = b + ( (a + H(b,c,d) + Mj + ti) << s)
II(a ,b ,c ,d ,Mj ,s ,ti) 操作为a = b + ( (a + I(b,c,d) + Mj + ti) << s)
注意:“<<”表示循环左移位,不是左移位。
这四轮(共64步)内容如下。
第一轮
FF(a ,b ,c ,d ,M0 ,7 ,0xd76aa478 )
FF(d , a , b , c , M1 ,12 ,0xe8c7b756 )
FF(c , d , a , b , M2 ,17 ,0x242070db )
FF(b , c , d , a , M3 ,22 ,0xc1bdceee )
FF(a ,b ,c ,d ,M4 ,7 ,0xf57c0faf )
FF(d , a , b , c , M5 ,12 ,0x4787c62a )
FF(c , d , a , b , M6 ,17 ,0xa8304613 )
FF(b , c , d , a , M7 ,22 ,0xfd469501)
FF(a ,b ,c ,d ,M8 ,7 ,0x698098d8 )
FF(d ,a ,b ,c ,M9 ,12 ,0x8b44f7af )
FF(c ,d ,a ,b ,M10 ,17 ,0xffff5bb1 )
FF(b , c , d , a , M11 ,22 ,0x895cd7be )
FF(a , b , c , d , M12 ,7 ,0x6b901122 )
FF(d , a , b , c , M13 ,12 ,0xfd987193 )
FF(c , d , a , b , M14 ,17 ,0xa679438e )
FF(b , c , d , a , M15 ,22 ,0x49b40821 )
第二轮
GG(a ,b ,c ,d ,M1 ,5 ,0xf61e2562 )
GG(d ,a ,b ,c ,M6 ,9 ,0xc040b340 )
GG(c , d , a , b , M11 ,14 ,0x265e5a51 )
GG(b , c , d , a , M0 ,20 ,0xe9b6c7aa )
GG(a , b , c , d , M5 ,5 ,0xd62f105d )
GG(d , a , b , c , M10 ,9 ,0x02441453 )
GG(c , d , a , b , M15 ,14 ,0xd8a1e681 )
GG(b , c , d , a , M4 ,20 ,0xe7d3fbc8 )
GG(a , b , c , d , M9 ,5 ,0x21e1cde6 )
GG(d , a , b , c , M14 ,9 ,0xc33707d6 )
GG(c , d , a , b , M3 ,14 ,0xf4d50d87 )
GG(b , c , d , a , M8 ,20 ,0x455a14ed )
GG(a , b , c , d , M13 ,5 ,0xa9e3e905 )
GG(d , a , b , c , M2 ,9 ,0xfcefa3f8 )
GG(c , d , a , b , M7 ,14 ,0x676f02d9 )
GG(b , c , d , a , M12 ,20 ,0x8d2a4c8a )
第三轮
HH(a ,b ,c ,d ,M5 ,4 ,0xfffa3942 )
HH(d , a , b , c , M8 ,11 ,0x8771f681 )
HH(c , d , a , b , M11 ,16 ,0x6d9d6122 )
HH(b , c , d , a , M14 ,23 ,0xfde5380c )
HH(a , b , c , d , M1 ,4 ,0xa4beea44 )
HH(d , a , b , c , M4 ,11 ,0x4bdecfa9 )
HH(c , d , a , b , M7 ,16 ,0xf6bb4b60 )
HH(b , c , d , a , M10 ,23 ,0xbebfbc70 )
HH(a , b , c , d , M13 ,4 ,0x289b7ec6 )
HH(d , a , b , c , M0 ,11 ,0xeaa127fa )
HH(c , d , a , b , M3 ,16 ,0xd4ef3085 )
HH(b , c , d , a , M6 ,23 ,0x04881d05 )
HH(a , b , c , d , M9 ,4 ,0xd9d4d039 )
HH(d , a , b , c , M12 ,11 ,0xe6db99e5 )
HH(c , d , a , b , M15 ,16 ,0x1fa27cf8 )
HH(b , c , d , a , M2 ,23 ,0xc4ac5665 )
第四轮
II(a ,b ,c ,d ,M0 ,6 ,0xf4292244 )
II(d ,a ,b ,c ,M7 ,10 ,0x432aff97 )
II(c ,d ,a ,b ,M14 ,15 ,0xab9423a7 )
II(b ,c ,d ,a ,M5 ,21 ,0xfc93a039 )
II(a ,b ,c ,d ,M12 ,6 ,0x655b59c3 )
II(d ,a ,b ,c ,M3 ,10 ,0x8f0ccc92 )
II(c ,d ,a ,b ,M10 ,15 ,0xffeff47d )
II(b ,c ,d ,a ,M1 ,21 ,0x85845dd1 )
II(a ,b ,c ,d ,M8 ,6 ,0x6fa87e4f )
II(d ,a ,b ,c ,M15 ,10 ,0xfe2ce6e0 )
II(c ,d ,a ,b ,M6 ,15 ,0xa3014314 )
II(b ,c ,d ,a ,M13 ,21 ,0x4e0811a1 )
II(a ,b ,c ,d ,M4 ,6 ,0xf7537e82 )
II(d ,a ,b ,c ,M11 ,10 ,0xbd3af235 )
II(c ,d ,a ,b ,M2 ,15 ,0x2ad7d2bb )
II(b ,c ,d ,a ,M9 ,21 ,0xeb86d391 )
所有这些完成之后,将a、b、c、d分别在原来基础上再加上A、B、C、D。
即a = a + A,b = b + B,c = c + C,d = d + D
然后用下一分组数据继续运行以上算法。
(4)输出
最后的输出是a、b、c和d的级联。
当按照上面所说的方法实现MD5算法以后,你可以用以下几个信息对做出来的程序作一个简单的测试,看看程序有没有错误。
MD5 ("") = d41d8cd98f00b204e9800998ecf8427e MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661 MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72 MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0 MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca 67e13b MD5 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") = f29939a25efabaef3b87e2cbfe641315 MD5 ("8a683566bcc7801226b3d8b0cf35fd97") =cf2cb5c89c5e5eeebef4a 76becddfcfd MD5加密字符串实例
现以字符串“jklmn”为例。
该字符串在内存中表示为:6A 6B 6C 6D 6E(从左到右为低地址到高地址,后同),信息长度为40 bits,即0x28。
对其填充,填充至448位,即56字节。结果为:
6A 6B 6C 6D 6E 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
剩下64位,即8字节填充填充前信息位长,按小端字节序填充剩下的8字节,结果为:
6A 6B 6C 6D 6E 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00
(64字节,512 bits)
初始化A、B、C、D四个变量。
将这64字节填充后数据分成16个小组(程序中对应为16个数组),内容如下。
M0:6A 6B 6C 6D(这是内存中的顺序,按照小端字节序原则,对应数组M(0)的值为0x6D6C6B6A,下同)
M1:6E 80 00 00
M2:00 00 00 00
…
M14:28 00 00 00
M15:00 00 00 00
经过“3. 处理分组数据”后,a、b、c、d值分别为0xD8523F60、0x837E0144、0x517726CA、0x1BB6E5FE。
在内存中为:
a:60 3F 52 D8
b:44 01 7E 83
c:CA 26 77 51
d:FE E5 B6 1B
a、b、c、d按内存顺序输出即为最终结果:603F52D844017E83CA267751FEE5B61B。这就是字符串“jklmn”的MD5值。
C++实现 #include<iostream> #include<string> using namespace std; #define shift(x, n) (((x) << (n)) | ((x) >> (32-(n))))//右移的时候, 高位一定要补0,而不是补充符号位 #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,其他位补0 *最后加上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; } 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 *公式:fl oor(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)); //右移的时候,高位一定要补0,而不是补充符号位 } /* *主循环 */ 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,其他位补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); } } VB2010实现 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 '正在验证哈希…… '哈希值是相同的 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(); } 伪代码实现 //Note:Allvariablesareunsigned32bitsandwrapmodulo2^32whencalcul atingvarint[64]r, k//rspecifi estheper-roundshiftamountsr[0..15]:= {7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22}r[16..31]:={5,9,14,20, 5,9,14,20,5,9,14,20,5,9,14,20}r[32..47]:={4,11,16,23,4,11,16,23,4,11, 16,23,4,11,16,23}r[48..63]:={6,10,15,21,6,10,15,21,6,10,15,21,6,10,1 5,21}//Usebinaryintegerpartofthesinesofi ntegersasconstants:forifrom 0to63k[i]:=fl oor(abs(sin(i+1))×2^32)//Initializevariables:varinth 0:=0x67452301varinth1:=0xEFCDAB89varinth2:=0x98BADCFEvarinth3:= 0x10325476//Pre-processing:append"1"bittomessageappend"0"bitsuntilm essagelengthinbits≡448(mod512)appendbitlengthofmessageas64- bitlittle-endianintegertomessage//Processthemessageinsuccessive512- bitchunks:foreach512-bitchunkofmessagebreakchunkintosixteen32- bitlittle-endianwordsw[i],0≤i≤15//Initializehashvalueforthischunk:v arinta:=h0varintb:=h1varintc:=h2varintd:=h3//Mainloop:forifrom0to 63if0≤i≤15thenf:=(bandc)or((notb)andd)g:=ielseif16≤i≤31f:=(dandb) or((notd)andc)g:=(5×i+1)mod16elseif32≤i≤47f:=bxorcxordg:=(3×i+5) mod16elseif48≤i≤63f:=cxor(bor(notd))g:=(7×i)mod16temp:=dd:=cc:=bb: =((a+f+k[i]+w[g])leftrotater[i])+ba:=temp//Addthischunk'shashtoresu ltsofar:h0:=h0+ah1:=h1+bh2:=h2+ch3:=h3+dvarintdigest:=h0appendh1ap pendh2appendh3//(expressedaslittle-endian)MD5加密工具
利用MD5的算法原理,可以使用各种计算机语言进行实现,形成各种各样的MD5加密校验工具。有很多的在线工具可以实现这一点,这些在线工具一般是采用JavaScript语言实现,使用非常方便快捷。
3.1.4 MD5的优势
Van oorschot和Wiener曾经考虑过一个在散列中暴力搜寻冲突的函数(brute-force hash function),而且他们猜测一个被设计专门用来搜索MD5冲突的机器(这台机器在1994年的制造成本大约是100万美元)可以平均每24天就找到一个冲突。但从1991年到2001年这10年间,竟没有出现替代MD5算法的MD6或被叫作其他什么名字的新算法这一点,我们就可以看出这个瑕疵并没有太多地影响MD5的安全性。上面所有这些都不足以成为MD5的在实际应用中的问题。并且,由于MD5算法的使用不需要支付任何版权费用,所以在一般的情况下(非绝密应用领域,但即便是应用在绝密领域内,MD5也不失为一种非常优秀的中间技术),MD5怎么都应该算得上是非常安全的了。
SHA-1与MD5的比较
因为二者均由MD4导出,SHA-1和MD5彼此很相似。相应地,它们的强度和其他特性也是相似的,但还有以下几点不同。
① 对强行攻击的安全性:较显著和较重要的区别是SHA-1摘要比MD5摘要长32位。使用强行技术,产生任何一个报文使其摘要等于给定报文摘要的难度对MD5是2128数量级的操作,而对SHA-1则是2160数量级的操作。这样,SHA-1对强行攻击有更大的强度。
② 对密码分析的安全性:MD5的设计易受密码分析的攻击,SHA-1显得不易受这样的攻击。
③ 速度:在相同的硬件上,SHA-1的运行速度比MD5慢。