hash 长度拓展攻击是一个效果还挺“惊人”的攻击。简单的来说,他能在只知道“一部分”密文的情况下,获得和“完整”密文相同的 hash 结果。只要满足条件,这个攻击现在对 MD5、SHA1、SHA256、SHA512 和 SM3 都能生效。
这个攻击的原理是目前这些主流哈希计算的一个薄弱点。这些哈希计算是基于对明文的分组。比如 64 个字节为一组,不满 64 的倍数则 padding 为 64 位。 对于每一组的明文来说,它需要与一组“向量”混合加密、输出为一个 hash。而如果不止一组明文,每组明文的 hash 会作为下一组的“向量”,这里的向量可理解为会变的加密密钥。
于是,在分组加密的情况下,实际上是: ① 每组独立做明文+向量混合运算 ② 向量由上一组的运算结果决定(等于) 这里要留意这个“独立”。因为这实际上隐含了一个意思: 假设有一个八个分组的明文,我们不需要知道前七组的明文,只需要知道最后一组——以及第七组(前七组)的运算结果,就能算出这第八组——也就是整个明文的哈希结果。
更进一步的,把前七组的明文看作 flag,第八组的明文看作独立 padding、append 拓展数据、salt 盐,那么这就是长度拓展 攻击了。
以 MD5 为例子:
放到例题中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import osfrom gmssl import sm3, funcwith open ('flag' ) as f: flag = f.read() MySecretInfo = os.urandom(64 ) HashValue = sm3.sm3_hash(func.bytes_to_list(MySecretInfo)) print ('MySecretInfo Hash:' , HashValue) AppendData = bytes .fromhex(input ('Input AppendData: ' )) assert len (AppendData) == 64 NewSecretInfo = MySecretInfo + AppendData GeneratedHash = input ('Input NewSecretInfo Hash: ' ) NewHashValue = sm3.sm3_hash(func.bytes_to_list(NewSecretInfo)) print (NewHashValue)if GeneratedHash == NewHashValue: print (flag) else : print ('Nope' )
sm3 的分组标准是 64 位一组,因此 secret 和 appendData 是分别两组,基本要求满足。 知晓 secret 的 hash,appendData 自己构造,这就很清晰了。 但落到实现上:①gmssl 库会默认在加密时做一个 64 位的 padding② 我们如何将 secret 的 hash 构造成加密的向量? ② 可以翻阅库实现解决: 首先是 padding 部分: 而后是向量 Vector 这里我们只需要为这个函数添加一个新的参数,作为 V[0]的初始值即可(同时记得 group_count -1) 参考攻击实现:https://github.com/hjzin/SM3LengthExtensionAttack 而对于 ①,这里很有趣 我们之前的实现逻辑是: 知道 Hash(Secret),知道 append,可知 Hash(Secret+append) 而默认 padding 时:Hash(Secret+padding),知道 append,可知 Hash(Secret+append+padding) 发现了么?这两次 hash 的值似乎对不上。
但这里,我要说拓展攻击,是一个很“模板”的攻击。这里不要被 append 这个名词所束缚。它能是 append,它能是 salt,甚至它也能是“padding”——是第一次 hash 的“padding”。 阅读 padding 部分的代码可知这个 padding 是很朴素的一个 padding,有变化,但变化不多。明文为一组时 padding 固定是: b’\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\x00\x00\x02\x00’ 明文为两组时 padding 固定是: b’\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\x00\x00\x04\x00’ 仅仅在末尾会有一些改变。 那么我们可以把用第一次 padding 的值去 append,这样就有: Hash(Secret+padding1),知道 append,可知 Hash(Secret+padding1+padding2) bingo~攻击完成 √ 参考 exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 """ @Time:2019/10/12 @Author: hhzjj @Description:SM3长度扩展攻击 1.随机生成一个secret,算出secret的hash值 2.根据hash值推出第一次压缩之后各个寄存器里的值 3.在secret+padding之后附加一段消息,用上一步寄存器里的值作为IV去加密附加的那段消息,得到hash 4.用sm3去加密secret+padding+m',得到hash 5.第3步和第4步得到的hash值应该相等 """ from gmssl import sm3, funcimport randomimport my_sm3import structsecret = "3131313131313131313131313131313131313131313131313131313131313131" a = func.bytes_to_list(bytes (secret, encoding='utf-8' )) secret_hash = "f6dd54733108fa21bb08102f367787ed9ac388b8bc8311a1e999d5b5d09ba0a6" print (secret_hash)secret_len = len (secret) append_m = b'\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\x00\x00\x02\x00' pad_str = "" pad = [] print (my_sm3.sm3_hash2(func.bytes_to_list(secret.encode()+append_m)))def generate_guess_hash (old_hash, secret_len, append_m ): """ SM3长度扩展攻击 :param old_hash: secret的hash值 :param secret_len: secret的长度 :param append_m: 附加的消息 :return: hash(secret + padding + append_m) """ vectors = [] message = "" for r in range (0 , len (old_hash), 8 ): vectors.append(int (old_hash[r:r + 8 ], 16 )) if secret_len > 64 : for i in range (0 , int (secret_len / 64 ) * 64 ): message += 'a' for i in range (0 , secret_len): message += 'a' message = func.bytes_to_list(bytes (message, encoding='utf-8' )) message.extend(func.bytes_to_list( append_m)) print ("meesage len" , len (message)) return my_sm3.sm3_hash(message, vectors) guess_hash = generate_guess_hash(secret_hash, secret_len, append_m) print (guess_hash)