长度扩展攻击
1. 起因:SHA-256哈希函数用于签名不安全
我在PentesterLab上做一道Python语言的Code Review。因为PentesterLab平台不希望 用户对PRO练习发布write-up,所以我简单描述一下代码功能。代码的主要功能是, 当用户选择购买一件商品时,将用户的支付信息提交给在线支付平台(如PayPal, 支付宝等),用户的支付信息由原始的支付信息和支付信息的哈希值2个部分组成。 支付信息的哈希值作为支付信息的签名。我没做出来,没想到是签名的部分出了问题。
return hashlib.sha256(data.encode('utf-8')).hexdigest()
代码有漏洞的原因是,SHA-256哈希函数不能用于签名。因为SHA-256哈希函数对 长度扩展攻击很脆弱,需要使用HMAC函数来获取哈希值。应该改为下面的代码:
import hmac
key = b'key'
return hmac.HMAC(key, data.encode('utf-8'),
digestmod=hashlib.sha512).hexdigest()
于是,我有3个疑问:
- 什么是长度扩展攻击?
- 为什么SHA-256哈希函数对长度扩展攻击很脆弱?
- 为什么HMAC函数可以抵抗长度扩展攻击?
2. 什么是长度扩展攻击?
你可能以为我会在这里引用一下长度扩展攻击的定义,但是这样对理解它并没有什么帮助。 我先来介绍一下SHA-256哈希函数,然后自然地引出它的算法设计是如何使它对长度 扩展攻击很脆弱。
SHA-256哈希函数首先对输入的消息(message)进行一个预处理:
- 在消息的最后添加一位比特“1”,这是为了防止当2个不同的消息前面的比特位 相同,而后面有不同个数的0时,如果不在末尾添加比特“1”,那么在填充0后 这两个不同的消息生成的哈希值将会相同。
假设块的大小为16,长度小于16的消息都要在最后填充0。
如果不在最后添加比特“1”:
0110 010 --> 0110 0100 0000 0000
0110 0100 --> 0110 0100 0000 0000
如果在最后添加比特“1”:
0110 010 --> 0110 0101 0000 0000
0110 0100 --> 0110 0100 1000 0000
- 因为SHA-256哈希函数要将消息进行分块,每块的大小是512比特,所以需要对 输入的数据填充,使其长度是512比特的整数倍。在SHA-256哈希函数的设计中, 最后的64比特用来存储初始消息的比特长度。所以,最后一块中,除了最后添加 的比特“1”和倒数64比特之外,其余位置填充比特“0”。
假设消息为字符串“abc”,用8比特的ASCII编码,一共有24比特,
所以需要填充512-24-1-64=423个比特“0”,填充结果如下:
01100001 01100010 01100011 1 00......0 00....011000
|--423--| |----64----|
在预处理之后,数据已经被填充为512比特的整数倍,然后按照每块512比特进行分块。 数据块依次处理,正常情况下的处理如下图。第一个数据块与初始向量作为输入, 通过压缩函数(compress function)生成输出,作为下一个压缩函数的输入之一, 以此类推,最后得到哈希值。
当你想要添加恶意的信息时,恶意的处理如下图。攻击者通过SHA-256填充规则, 人为地在恶意信息前伪造填充部分。
最后的效果如下。这样,即使不知道之前的信息也可以构建出一个与添加了恶意信息 的恶意消息对应的合法哈希值。
我们可以使用开源工具HashPump进行长度扩展攻击。HashPump的使用方式如下:
$ hashpump -h
HashPump [-h help] [-t test] [-s signature] [-d data] [-a additional] [-k keylength]
HashPump generates strings to exploit signatures vulnerable to the Hash Length Extension Attack.
-h --help Display this message.
-t --test Run tests to verify each algorithm is operating properly.
-s --signature The signature from known message.
-d --data The data from the known message.
-a --additional The information you would like to add to the known message.
-k --keylength The length in bytes of the key being used to sign the original message with.
Version 1.2.0 with CRC32, MD5, SHA1, SHA256 and SHA512 support.
<Developed by bwall(@botnet_hunter)>
因为SHA-256不需要加密密钥,所以传给-k的值不会影响最后的结果。HashPump支持的 算法有:MD5,SHA-1,SHA-256,SHA-512。
hashpump -s '6d5f807e23db210bc254a28be2d6759a0f5f5d99' \
-d 'transaction_id=393f5c77cfb4e3bcd3c037b70b5fd6b8&amount=20.00' \
-a '&amount=1.00' -k 0
结果如下:
1ff8b401835a00f4edfb0955c4edab40e479ad2a
transaction_id=393f5c77cfb4e3bcd3c037b70b5fd6b8&amount=20.00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x10&amount=1.00
3. 采用HMAC函数对消息签名
根据RFC 2104,HMAC的数学公式为:
\begin{equation} HMAC(K,m)=H((K’\oplus opad)||H((K’\oplus ipad)||m)) \end{equation}
其中:
- \(H\) 为密码散列函数(如SHA家族)
- \(K\) 为密钥(secret key)
- \(m\) 是要认证的消息
- \(K'\) 是从原始密钥K导出的另一个秘密密钥(如果K短于散列函数的输入块大小, 则向右填充(Padding)零;如果比该块大小更长,则对K进行散列)
- \(\oplus\) 代表异或(XOR)
- \(opad\) 是外部填充(0x5c5c5c…5c5c,一段十六进制常量)
- \(ipad\) 是内部填充(0x363636…3636,一段十六进制常量)
根据HMAC函数的定义可以知道,它可以抵御长度扩展攻击。:)