MD5算法
一、摘要算法
(一)介绍
摘要算法:顾名思义,就是从已有数据中抽取出一部分数据片段经过一系列复杂的计算然后糅合在一起形成的密文。
摘要算法对输入数据尤其敏感,输入数据发生任何变化都会导致密文的不一致。
由于生成的密文只是通过一部分数据片段计算出来的,因此摘要算法是一个不可逆的过程(也就是说不可能从已生成的密文推导出其对应的数据)。
(二)应用场景
- 数字签名
- 存储用户密码
如果直接将用户的明文密码存放到数据库中,会产生极大的安全隐患:
- 数据库管理员可以看到用户的明文密码,有泄漏风险。
- 数据库被攻击后,黑客可以获取到所有用户的明文密码
因此在数据库中一般存放的都是用户密码的摘要信息,当用户登录时,可以通过相同的摘要算法计算出摘要信息,然后再与数据库中的摘要信息进行比对。(同时这也是为什么我们忘记密码后不能直接找回密码而是要重置密码的原因)
ps:这个时候黑客大兄弟发话了:“这点问题能难倒我?虽然我不能直接获取到你们的明文密码,但是我还有其他办法可以破解你们的用户密码的 ”
原来这位黑客自己也有一个数据库,他的数据库中保存着我们所有密码的排列组合与摘要的一个对应关系。这样一来他就可以通过用户密码的摘要与自己数据库中的摘要进行匹配。匹配成功后,这串摘要对应的密码就代表着用户的密码。这也就是常说的彩虹表攻击。
有攻击自然就有防御
刚才的黑客之所以还能够破解用户的密码,无非就是因为他知道了我们具体的摘要算法,然后通过相同的算法,列举出我们所有密码的组合结果并保存在了自己数据库中进行匹配 。
既然如此,那么我们可以在生成用户密码摘要时放点额外的东西进去,这样一来就可以生成完全不一样的摘要信息。
同时为了避免黑客知道我们放了什么东西进去,然后采用相同的手法攻击我们,我们还可以针对每一个用户放不同的东西进去(例如用户id、身份证号、手机号等)。这样一来我们的安全性就有了很高的保证。(这个东西被称之为“盐(salt)”)
(三)小结
- 摘要算法计算的过程不需要密钥(但是可以加盐,也就是把密钥作为数据的一部分进行计算)
- 摘要算法是一个输入敏感、输出长度固定的不可逆算法
- 用相同的摘要算法对同一个数据进行两次计算,输出结果必然相同
二、MD5算法
(一)简介
MD5即Message-Digest Algorithm 5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。
MD5算法具有以下特点:
不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样
(一个MD5理论上的确是可能对应无数多个原文的,因为MD5是有限多个的而原文可以是无数多个。比如主流使用的MD5将任意长度的“字节串映射为一个128bit的大整数。也就是一共有2^128种可能,大概是3.4*10^38,这个数字是有限多个的,而但是世界上可以被用来加密的原文则会有无数的可能性)
- 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
- 容易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
(二)用途
1.防止被篡改
比如发送一个电子文档,发送前,我先得到MD5的输出结果a。然后在对方收到电子文档后,对方也得到一个MD5的输出结果b。如果a与b一样就代表中途未被篡改。
比如我提供文件下载,为了防止不法分子在安装程序中添加木马,我可以在网站上公布由安装文件得到的MD5输出结果。
SVN在检测文件是否在CheckOut后被修改过,也是用到了MD5.
2.防止直接看到明文
现在很多网站在数据库存储用户的密码的时候都是存储用户密码的MD5值。这样就算不法分子得到数据库的用户密码的MD5值,也无法知道用户的密码。(比如在UNIX系统中用户的密码就是以MD5(或其它类似的算法)经加密后存储在文件系统中。当用户登录的时候,系统把用户输入的密码计算成MD5值,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这不但可以避免用户的密码被具有系统管理员权限的用户知道,而且还在一定程度上增加了密码被破解的难度。)
3.防止抵赖(数字签名)
这需要一个第三方认证机构。例如A写了一个文件,认证机构对此文件用MD5算法产生摘要信息并做好记录。若以后A说这文件不是他写的,权威机构只需对此文件重新产生摘要信息,然后跟记录在册的摘要信息进行比对,相同的话,就证明是A写的了。这就是所谓的“数字签名”。
(三)MD5加密步骤
1.填充
在MD5算法中,首先需要对信息进行填充,使其位长对512求余的结果等于448,并且填充必须进行,即使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N*512+448,N为一个非负整数,N可以是零。
填充的方法如下:
- 在信息的后面填充一个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中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向左环移一个不定的数,并加上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) )
(&是与(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=2的32次方)
现定义:
- 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例子
现以字符串“jklmn”为例。
该字符串在内存中表示为:6A 6B 6C 6D 6E(从左到右为低地址到高地址,后同),信息长度为40 bits, 即0x28。
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值。
(五)代码实现
import java.security.MessageDigest; import java.util.Arrays; public class MD5Util { private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) { resultSb.append(byteToHexString(b[i])); System.out.println(b[i]+"-->"+byteToHexString(b[i])); } return resultSb.toString(); } private static String byteToHexString(byte b) { int n = b; if (n < 0) { n += 256; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) { resultString = byteArrayToHexString(md.digest(resultString .getBytes())); } else { System.out.println("resultString: "+ resultString); System.out.println("resultString.getBytes(): "+ Arrays.toString(resultString.getBytes())); System.out.println("md.digest(resultString.getBytes(): "+ Arrays.toString(md.digest(resultString.getBytes()))); resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } } catch (Exception exception) { System.out.println("MD5Encode Exception"); } return resultString; } private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; }
System.out.println(MD5Util.MD5Encode(str, "UTF-8"));
输入:jklmn