其实本人对密码学还是有点兴趣的(?),但是之前并没有记录下之前对于一些密码学攻击的研究,这里还是一点点积累吧
Length Extension Attack – 哈希长度扩展攻击
背景
hash函数一般来说是用来签名的。比如说,如果我们需要使用一个登陆验证的时候,我们可以使用下面的代码
1 | def genMac(name): |
其中Mac(message authentication code),也就是身份认证码。我们将这个Mac交给用户,然后下一次登陆的时候,我们将会利用上面的Mac进行比较,确定是否是同一个登陆:
1 | def checkAuth(name, mac): |
那么很多人都会想到说,这个md5我们本地生成一个就好啦,于是服务器端一般会藏一个secret字符串在服务器,让md5的生成规则不被用户了解:
1 | def genMac(name): |
也就是说这个时候,如果我们不知道服务器端的secret字符串,理论上是没有办法生成我们指定名字的Mac,比如说
1 | def genMac(name): |
这个时候,没有办法生成root的Mac。然后管理员的登陆的确认逻辑变为:
1 | def checkRoot(name, mac): |
这个时候,由于不知道secret,我们就不能够伪造root的Mac值。不过这个可以注意到,这里对于name的判断只是判断其是否包含root,这个地方就暗示此处有可以利用的地方。
MD5类hash函数的原理
网上搜索了一下,MD5,SHA1,SHA2这类算法都属于一种叫做Merkle–Damgård结构的哈希函数,也就是抗冲突加密散列函数,总的来说,这个结构的函数的特征有:
- 按照block划分,并且在结尾存在padding表示长度
- 对于长度不足的block用标志位进行填充
- 长度过长的时候,会迭代进行加密(待考证)
这里我们先研究MD5中的哈希长度扩展攻击,所以简单介绍MD5的加密过程以及部分攻击相关的细节。
MD5加密逻辑
准备材料:
- 8bit的四个寄存器ABCD
- 需要处理的明文
处理过程:
这里主要不是介绍MD5加密,所以省略了大部分的MD5加密细节(比如说,在一轮加密结束之后,会发生寄存器值的交换,但是这里没有展现出来)。这里我们着重介绍对攻击有效的部分
在一开始的时候,ABCD四个寄存器会被初始化成不同的整数,然后进行类似下列的加密:
1 | +--------------+--------------+--------------+--------------+ |
从图中我们能够指导,MD5加密是一个迭代的过程。
- 首先ABCD通过非线性函数和我们输入的明文block进行加密,得到四个不同的数字
- 将四组数字重新放回到到四个寄存器中
- 如果我们输入的字符串长度大于512bit的时候,其就会将第一轮计算得到的ABCD作为递归开始的ABCD
- 反复进行上面123步骤,直到我们输入的字符串长度到头为止。
最后将得到的ABCD连接,就能够得到加密完成的hash值。
Padding的选择
当我们的输入比较短,比如说是secretdata,那么此时的输入只有10个字节,也就是80bit,那么此时MD5会用以下的填充规则进行填充:
- 首先在结尾跟上一个\x80,表示这之后是padding
- 在56字节之后,最后的8个字节用来记录有效输入字符串的bit数
因此,对于字符串secret,得到的结果应该如下:
1 | 0000 73 65 63 72 65 74 64 61 74 61 80 00 00 00 00 00 secretdata...... |
其中0x50 == 80,正好就是当前填入的有效bit数。
攻击 – 攻击原理
攻击的最根本的原理就在之前提到的迭代处理上。我们处理一下当前手上能够得到的信息
- 我们能够得到的mac是hash(secret+data),其中data是我们自己输入的。
- MD5的加密中,ABCD寄存器中的值才是hash值本身
- 在处理过相同的内容的时候,得到的ABCD的值是相等的
我们的目的是
- 得到hash(secret+data+root)的内容(由于判断是find(‘root’)>=0,也就是说不一定要从root开始,前面可以填充不同的内容)
那么,首先想到,如果我们输入的内容是data,那么程序就会将其拼接成
1 | 0000 73 65 63 72 65 74 64 61 74 61 80 00 00 00 00 00 secretdata...... |
那么,在进行MD5加密的时候,加密结束的时候得到的值就会是:
1 | +--------------+--------------+--------------+--------------+ |
得到的hash值就是A1B1C1D1组合而成的。从另一个角度来说,A1B1C1D1表示的是当前上下文的状态。于是,如果我们此时能够构造出下列的情况:
1 | +--------------+--------------+--------------+--------------+ |
那么这个时候,A2B2C2D2就是一个包含了secret和root的hash值。此时完全不需要知道secret的具体内容!
攻击 – 数据伪造
这个地方最关键的就是伪造数据。由于Padding的存在,使得我们可以通过合理增加padding,让关键字符串落在下一段block中的方式,利用上下文。
以上述例子为例,
1 | 0000 73 65 63 72 65 74 64 61 74 61 80 00 00 00 00 00 secretdata...... |
此处是服务器上MD5算法中见到的字符,我们为了满足这个形式,可以主动将第一段数据填充够56字节,形成一个和第一次hash同样的值,然后第二次的时候,我们将我们需要的值放在数据的尾部
1 | 0000 73 65 63 72 65 74 64 61 74 61 80 00 00 00 00 00 secretdata...... |
这样的形式就能够形成我们需求的形式。然后,由于secret在服务器端,那么客户端上构造的字符串就应该是长这个样子的:
1 | 0000 64 61 74 61 80 00 00 00 00 00 00 00 00 00 00 00 data............ |
(倒也没有说是64字节对齐,只是数据secret和append相等罢了)
也就是说,攻击形式是:
1 | 发送data --> 得到包含secret的hash --> 构造数据,造成第一次加密完全相等的形式,并且在尾部添加字符串 --> 计算hash -->通过发送构造字符串和hash通过验证,完成攻击 |
工具的使用
这个东西构造还是蛮麻烦的,python里面直接就有一个库叫做hashpumpy
可以用,用法如下:
1 | hashnum = hashlib.md5(secret+'data').hexdigest() # 假设生成了一个hashnum用作Mac |
其生成的token即为包含了data和root的hash值,而fake则是包含了root的数据。
后记
可能因为看英文会一个一个字去看,最近只有看英文的blog才能够学的到东西(汗),果然还是得静下心来看东西才行。
大部分的数据都来自参考博客
参考博客
(https://blog.skullsecurity.org/2012/everything-you-need-to-know-about-hash-length-extension-attacks)[https://blog.skullsecurity.org/2012/everything-you-need-to-know-about-hash-length-extension-attacks]