hash拓展攻击与例题hash_append

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 os

from gmssl import sm3, func



with open('flag') as f:

    flag = f.read()




MySecretInfo = os.urandom(64) # 有密文

HashValue = sm3.sm3_hash(func.bytes_to_list(MySecretInfo))

print('MySecretInfo Hash:', HashValue) # 有密文hash



AppendData = bytes.fromhex(input('Input AppendData: ')) # salt、append数据

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 部分:
alt text
而后是向量 Vector
alt text
这里我们只需要为这个函数添加一个新的参数,作为 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
#!/usr/bin/env python

# -*- coding:utf-8 -*-

"""

@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, func

import random

import my_sm3

import struct



secret = "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):

    # 攻击关键在于重设vector

    """

    SM3长度扩展攻击

    :param old_hash: secret的hash值

    :param secret_len: secret的长度

    :param append_m: 附加的消息

    :return: hash(secret + padding + append_m)

    """

    vectors = []

    message = ""

    # 将old_hash分组,每组8个字节, 并转换为整数

    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 = padding(message)

    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)