Hash一般译作散列,也有直接音译做哈希,本文就直接音译吧,哈哈!所谓散列算法就是,把任意长度的输入,经过复杂的运算,转化为固定长度的输出。简单来说,就是把任意长度的字节压缩为固定长度的函数。
攻击条件:
1.知道密文(SECRET)的哈希。
2.知道密文的长度。
原理:
当知道MD5(secret)时,在不知道secret的情况下,可以轻松推算出MD5(secret||padding||m’)。
在这里m’是任意数据,||是连接符,可以为空。padding是secret最后的填充字节。MD5的padding字节包含整个消息的长度,因此,为了能准确计算出padding的值,secret的长度我们也是需要知道的。
算法简析
MD5算法会对消息进行分组,每组64字节,不足64字节的部分用padding补齐。padding的规则是,在最末一个字节之后填充ox80,其余部分填充为ox00,padding最后的8个字节用来哈希的消息长度。
这是一张哈希算法流程图,根据这张图我们可以把哈希算法的流程,简单分为下面几步:
1.把消息分为n个消息块。
2.对最后一个消息块进行消息填充。
3.每个消息块会和一个输入量做运算,把运算结果作为下一个输入量。
理解MD5
这个算法十分复杂,至于算法实现细节,在这里便不再班门弄斧,我们只关心那些我们需要的部分。
根据上面讲解下面更加专业的流程:
1. Append Padding Bits(填充bits)
2. Append Length(填充长度)
3.Initialize MD Buffer(初始化向量)
4.Process Message in 16-Word Blocks(复杂的函数运算)
而要实现我们的攻击,我们只关心前三步,也就是不再再纠结复杂的算法运算。
下面用例子来解释下前三个流程:
1.MD5中填充bits的算法,该算法的流程如下:
- 根据消息的长度确定填充的字节数,即填充后消息长度 mod 512bit = 448bit。举个例子:一个消息是”message”,则这个消息是56bit,所以需要填充392bit。
- 最小填充1bit最多填充512bit,即使消息长度 mod 512 = 448bit。也就是说,不管消息长度是多少,都必须进行填充。
- 填充信息的第一个字节是0x80,剩余数据用0x00填充。
2.填充长度的流程:
填充长度的大小是64bit
长度是小端存储的,也就是说高字节放在高地址中
如果消息的长度大于2 ^ 64,也就是大于2048PB。那么64bit无法存储消息的长度,则取低64bit。
下图是补位的示例,要进行哈希运算的消息是字符串”message”,则:
3.初始化向量为固定值(硬编码):
A. 01 23 45 67 0x67452301
B .89 AB CD EF 0xEFCDAB89
C. FE DC BA 98 0x98BADCFE
D .76 54 32 10 0x10325476
然后,用初始化向量和补位后的消息进行复杂的函数运算,最终得到消息”message”的MD5值为78e731027d8fd50ed642340b7c9a63b3。
理解MD5长度扩展攻击
如果一个消息长度大于512bit,则会对消息按512bit进行切分,最后一个消息块进行填充操作。
假设我们知道一个7字节也就是56bit的消息的MD5值是78e731027d8fd50ed642340b7c9a63b3。
则MD5算法对其进行运算时,会先补位,由于消息的内容我们不知道,所以补位的结果如下图
然后会和初始向量进行复杂的函数运算,因为MD5值为78e731027d8fd50ed642340b7c9a63b3,故得到的结果为
A=0x0231e778
B=0x0ed58f7d
C=0x0b3442d6
D=0xb3639a7c
若向补位后的消息再追加一条消息字符串”admin”,则会对这个字符串进行补位,再利用上一个运算算出的值作为初始向量进行函数运算,最终得到的MD5值为e53a681a30ff99e3f6522270ca7db244。
这样就完成了在不知道消息的情况下,计算出消息+填充+追加消息的MD5值。
我们可以验证一下,假设验证用户登录的程序是这样的:
1.import sys
2. from urllib import unquote
3.
4. def login(password, hash_val):
5. m = hashlib.md5()
6. secret_key = "message"
7. m.update(secret_key + password)
8. if(m.hexdigest() == hash_val):
9. print "Login Successful!"
10. else:
11. print "Login Failed"
12.
13. if __name__ == "__main__":
14. password = unquote(sys.argv[1])
15. hash_val = unquote(sys.argv[2])
16. login(password, hash_val)
现在只知道一组用户名和hash,root和f3c36e01c874865bc081e4ae7af037ea
由分析可知,我们在知道secret_key长度的情况下,可以伪造padding,再通过追加字符串可以算出secret+padding+追加字符串的MD5值。假设我们追加的字符串为admin,则通过哈希扩展攻击计算出md5(secret+padding+追加字符串) = e53a681a30ff99e3f6522270ca7db244。然后我们测试一下,显示登录成功:
总结来说,长度扩展的原理就是将已知压缩后的结果,直接拿过来作为新的压缩输入。在这个过程中,只需要上次压缩后的结果,而不需要知道原来的消息内容时什么。假如我们知道了,md5(“secret”)我们不需要知道secret是什么,只要知道长度,人为将secret填充完,在新加一块假设为add,之前得到的MD5值作为最后一块加密的初始向量IV,最后加密得到的结果和MD5(secret+add)结果是一样的!
、