SM4算法分析(国密学习 1/5)

注:本blog参考《SM4分组密码算法》(GB/T 32907-2016)与python开源库gmssl中的sm4算法实现

国家标准全文公开系统:https://openstd.samr.gov.cn

算法基本信息

SM4一般用于数据加密,属于分组密码(SM1、SM7也是分组密码,不过算法未公开)
分组长度:128bit,密钥长度:128bit
加密算法和密钥扩展算法均采用非线性迭代结构,轮数:32轮
轮密钥的使用情况和DES类似:解密轮密钥就是加密轮密钥的逆序,且解密和加密的算法结构相同

合成置换 T

合成置换T 是在密钥扩展算法、加密算法和解密算法中使用的函数,因此在这里先说一下这个函数的实现:
合成置换T 是一个可逆变换,由非线性变换函数 $\tau$ 和线性变换函数 $L$ 复合成
$T(,) = L(\tau (,))$
其中 τ 函数就是一个简单的S盒映射函数(非线性变换),没什么好说的
L 函数就是线性变换的,输入输出都是32位,实现:
$L(B) = B \oplus (B <<< 2)\oplus (B <<< 10)\oplus (B <<< 18)\oplus (B <<< 24)$
注: <<< 是循环左移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Expanded SM4 box table  
SM4_BOXES_TABLE = []

# _f 是 T函数,在轮函数f中会被使用
def _sm4_l_t(ka):
b = [0, 0, 0, 0]
a = put_uint32_be(ka) # 将32位的数据转换成大端字节序的4个字节列表
# τ函数
b[0] = SM4_BOXES_TABLE[a[0]]
b[1] = SM4_BOXES_TABLE[a[1]]
b[2] = SM4_BOXES_TABLE[a[2]]
b[3] = SM4_BOXES_TABLE[a[3]]
bb = get_uint32_be(b[0:4]) # put_unit32_be()的逆函数
# 下行是 L 函数
c = bb ^ (rotl(bb,2)) ^ (rotl(bb,10)) ^ (rotl(bb,18)) ^ (rotl(bb,24))
return c

密钥

密钥长度为128bit
密钥表示:$MK=(MK_0,MK_1,MK_2.MK_3)$(每个32bit)
轮密钥表示:$(rk_0,rk_1,…,rk_{31})$

密钥扩展算法

注:该算法用到了系统参数$FK = (FK_0,FK_1,FK_2,FK_3)$和固定参数$CK = (CK_0,CK_1,…,CK_{31})$
轮密钥是密钥用密钥扩展算法得到

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
# Expanded SM4 box table  
SM4_BOXES_TABLE = []

# System parameter
SM4_FK = []

# fixed parameter
SM4_CK = []

# 相当于 T'函数
def _round_key(cls, ka): # ka是T'函数的参数(已经计算好的)
b = [0, 0, 0, 0]
a = put_uint32_be(ka) # 将32位的数据转换成大端字节序的4个字节列表
# 下面4行就是在实现 τ 函数
b[0] = SM4_BOXES_TABLE[a[0]]
b[1] = SM4_BOXES_TABLE[a[1]]
b[2] = SM4_BOXES_TABLE[a[2]]
b[3] = SM4_BOXES_TABLE[a[3]]
bb = get_uint32_be(b[0:4]) # put_unit32_be()的逆函数
rk = bb ^ (rotl(bb, 13)) ^ (rotl(bb, 23)) # 这是 L'函数,对应原文式(8)
return rk

# 密钥扩展算法
def set_key(self, key, mode):
key = bytes_to_list(key)
MK = [0, 0, 0, 0] # 加密密钥分块
k = [0] * 36 # 用来暂时存放轮密钥,最后会被拷到 self.sk 中
MK[0] = get_uint32_be(key[0:4])
MK[1] = get_uint32_be(key[4:8])
MK[2] = get_uint32_be(key[8:12])
MK[3] = get_uint32_be(key[12:16])
k[0:4] = xor(MK[0:4], SM4_FK[0:4]) # 先计算 k0、k1、k2、k3 ,对应原文式(6)
for i in range(32): # 整个for循环就是轮密钥生成算法,生成的轮密钥暂存在k中,对应原文式(7)
k[i + 4] = k[i] ^ (
self._round_key(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ SM4_CK[i]))
self.sk[i] = k[i + 4]
self.mode = mode
if mode == SM4_DECRYPT: # 如果是解密模式,就将加密的轮密钥逆序
for idx in range(16):
t = self.sk[idx]
self.sk[idx] = self.sk[31 - idx]
self.sk[31 - idx] = t

轮函数 F

主要使用于加密函数中32次迭代运算,rk是轮密钥
结构:$F(X_0,X_1,X_2,X_3,rk) = X_0 \oplus T(X_1 \oplus X_2 \oplus X_3 \oplus rk)$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# _f 是 f函数,在轮函数f中会被使用  
def _f(cls, x0, x1, x2, x3, rk):
# _sm4_l_t是 T函数(就是上文的T函数代码实现)
def _sm4_l_t(ka):
b = [0, 0, 0, 0]
a = put_uint32_be(ka)
# τ函数
b[0] = SM4_BOXES_TABLE[a[0]]
b[1] = SM4_BOXES_TABLE[a[1]]
b[2] = SM4_BOXES_TABLE[a[2]]
b[3] = SM4_BOXES_TABLE[a[3]]
bb = get_uint32_be(b[0:4])
# 下行是 L 函数
c = bb ^ (rotl(bb,2)) ^ (rotl(bb,10)) ^ (rotl(bb,18)) ^ (rotl(bb,24))
return c
return (x0 ^ _sm4_l_t(x1 ^ x2 ^ x3 ^ rk))

加密算法

由32次迭代运算盒1次反序变换 R 组成
每组明文为128bit,分成4组,一组32bit($X_0,X_1,X_2,X_3$),输出密文$(Y_0,Y_1,Y_2,Y_3)$

32次迭代:
$X_{i+4} = F(X_i,X_{i+1},X_{i+2},X_{i+3},rk_i),i = 0,1,…,31$

反序变换R:

$(Y_0,Y_1,Y_2,Y_3) = R(X_{32},X_{33},X_{34},X_{35}) = (X_{35},X_{34},X_{33},X_{32})$

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 加密算法  
def one_round(self, sk, in_put): # 明文分组,一次128bit
out_put = []
ulbuf = [0] * 36
ulbuf[0] = get_uint32_be(in_put[0:4])
ulbuf[1] = get_uint32_be(in_put[4:8])
ulbuf[2] = get_uint32_be(in_put[8:12])
ulbuf[3] = get_uint32_be(in_put[12:16])
# 32轮迭代
for idx in range(32):
ulbuf[idx + 4] = self._f(ulbuf[idx],
ulbuf[idx + 1],
ulbuf[idx + 2],
ulbuf[idx + 3],
sk[idx])

# 反序变换R
out_put += put_uint32_be(ulbuf[35])
out_put += put_uint32_be(ulbuf[34])
out_put += put_uint32_be(ulbuf[33])
out_put += put_uint32_be(ulbuf[32])
return out_put

解密算法

本blog看到这解密算法应该也是很清晰的,将加密轮密钥逆序后同加密过程就能解密了
GB/T中写:本算法的解密变换与加密变换结构相同,不同的仅是轮密钥的使用顺序。解密时,使用轮密钥序 ($rk_{31},rk_{30},…,rk_0$)

算法ecb、cbc模块

gmssl中还实现加密(解密)的ecb和cbc)

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
def crypt_ecb(self, input_data):  
# SM4-ECB block encryption/decryption
input_data = bytes_to_list(input_data) # 将输入字节转成列表
if self.mode == SM4_ENCRYPT: # 判断是否要填充,PKCS7是经典的分组密码填充模式
if self.padding_mode == PKCS7:
input_data = pkcs7_padding(input_data)
elif self.padding_mode == ZERO:
input_data = zero_padding(input_data)

length = len(input_data)
i = 0
output_data = []
while length > 0: # ecb的加密
output_data += self.one_round(self.sk, input_data[i:i + 16])
i += 16
length -= 16
if self.mode == SM4_DECRYPT: # 解密的话要先去填充
if self.padding_mode == PKCS7:
return list_to_bytes(pkcs7_unpadding(output_data))
elif self.padding_mode == ZERO:
return list_to_bytes(zero_unpadding(output_data))
return list_to_bytes(output_data)

def crypt_cbc(self, iv, input_data):
# SM4-CBC buffer encryption/decryption
i = 0
output_data = []
tmp_input = [0] * 16
iv = bytes_to_list(iv) # cbc模式的特征,引入一个初始向量iv
if self.mode == SM4_ENCRYPT:
input_data = pkcs7_padding(bytes_to_list(input_data)) # 加密填充
length = len(input_data)
while length > 0: # cbc模式加密,很经典的实现方式
tmp_input[0:16] = xor(input_data[i:i + 16], iv[0:16])
output_data += self.one_round(self.sk, tmp_input[0:16])
iv = copy.deepcopy(output_data[i:i + 16])
i += 16
length -= 16
return list_to_bytes(output_data)
else: # 解密流程
length = len(input_data)
while length > 0:
output_data += self.one_round(self.sk, input_data[i:i + 16])
output_data[i:i + 16] = xor(output_data[i:i + 16], iv[0:16])
iv = copy.deepcopy(input_data[i:i + 16])
i += 16
length -= 16
return list_to_bytes(pkcs7_unpadding(output_data))

总源码

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# -*-coding:utf-8-*-
import copy
from .func import xor, rotl, get_uint32_be, put_uint32_be, \
bytes_to_list, list_to_bytes, pkcs7_padding, pkcs7_unpadding, zero_padding, zero_unpadding

# Expanded SM4 box table
SM4_BOXES_TABLE = [
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c,
0x05, 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86,
0x06, 0x99, 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed,
0xcf, 0xac, 0x62, 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa,
0x75, 0x8f, 0x3f, 0xa6, 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c,
0x19, 0xe6, 0x85, 0x4f, 0xa8, 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb,
0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25,
0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87, 0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52,
0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38,
0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34,
0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, 0x1d, 0xf6, 0xe2, 0x2e, 0x82,
0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f, 0xd5, 0xdb, 0x37, 0x45,
0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, 0x8d, 0x1b, 0xaf,
0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, 0x0a, 0xc1,
0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, 0x89,
0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39,
0x48,
]

# System parameter
SM4_FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]

# fixed parameter
SM4_CK = [
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
]

SM4_ENCRYPT = 0
SM4_DECRYPT = 1

PKCS7 = 0
ZERO = 1


class CryptSM4(object):

def __init__(self, mode=SM4_ENCRYPT, padding_mode=PKCS7):
self.sk = [0] * 32
self.mode = mode
self.padding_mode = padding_mode
# Calculating round encryption key.
# args: [in] a: a is a 32 bits unsigned value;
# return: sk[i]: i{0,1,2,3,...31}.

@classmethod
# 相当于 T'函数
def _round_key(cls, ka): # T'函数的参数(已经计算好的)
b = [0, 0, 0, 0]
a = put_uint32_be(ka) # 将32位的密钥转换成大端字节序的4个字节列表
# 下面4行就是在实现 τ 函数
b[0] = SM4_BOXES_TABLE[a[0]]
b[1] = SM4_BOXES_TABLE[a[1]]
b[2] = SM4_BOXES_TABLE[a[2]]
b[3] = SM4_BOXES_TABLE[a[3]]
bb = get_uint32_be(b[0:4]) # put_unit32_be()的逆函数
rk = bb ^ (rotl(bb, 13)) ^ (rotl(bb, 23)) # 这是 L'函数
return rk

# Calculating and getting encryption/decryption contents.
# args: [in] x0: original contents;
# args: [in] x1: original contents;
# args: [in] x2: original contents;
# args: [in] x3: original contents;
# args: [in] rk: encryption/decryption key;
# return the contents of encryption/decryption contents.
@classmethod
# _f 是 f函数,在轮函数f中会被使用
def _f(cls, x0, x1, x2, x3, rk):
# _sm4_l_t是 T函数
def _sm4_l_t(ka):
b = [0, 0, 0, 0]
a = put_uint32_be(ka)
# τ函数
b[0] = SM4_BOXES_TABLE[a[0]]
b[1] = SM4_BOXES_TABLE[a[1]]
b[2] = SM4_BOXES_TABLE[a[2]]
b[3] = SM4_BOXES_TABLE[a[3]]
bb = get_uint32_be(b[0:4])
# 下行是 L 函数
c = bb ^ (rotl(bb,2)) ^ (rotl(bb,10)) ^ (rotl(bb,18)) ^ (rotl(bb,24))
return c
return (x0 ^ _sm4_l_t(x1 ^ x2 ^ x3 ^ rk))

# 密钥扩展算法
def set_key(self, key, mode):
key = bytes_to_list(key)
MK = [0, 0, 0, 0] # 加密密钥分块
k = [0] * 36 # 用来暂时存放轮密钥,最后会被拷到 self.sk 中
MK[0] = get_uint32_be(key[0:4])
MK[1] = get_uint32_be(key[4:8])
MK[2] = get_uint32_be(key[8:12])
MK[3] = get_uint32_be(key[12:16])
k[0:4] = xor(MK[0:4], SM4_FK[0:4]) # 先计算 k0、k1、k2、k3 ,对应原文式(6)
for i in range(32): # 整个for循环就是轮密钥生成算法,生成的轮密钥暂存在k中,对应原文式(7)
k[i + 4] = k[i] ^ (
self._round_key(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ SM4_CK[i]))
self.sk[i] = k[i + 4]
self.mode = mode
if mode == SM4_DECRYPT: # 如果是解密模式,就将加密的轮密钥逆序
for idx in range(16):
t = self.sk[idx]
self.sk[idx] = self.sk[31 - idx]
self.sk[31 - idx] = t
# 轮函数
def one_round(self, sk, in_put): # 明文分组,一次128bit
out_put = []
ulbuf = [0] * 36
ulbuf[0] = get_uint32_be(in_put[0:4])
ulbuf[1] = get_uint32_be(in_put[4:8])
ulbuf[2] = get_uint32_be(in_put[8:12])
ulbuf[3] = get_uint32_be(in_put[12:16])
# 32轮迭代
for idx in range(32):
ulbuf[idx + 4] = self._f(ulbuf[idx],
ulbuf[idx + 1],
ulbuf[idx + 2],
ulbuf[idx + 3],
sk[idx])

# 反序变换R
out_put += put_uint32_be(ulbuf[35])
out_put += put_uint32_be(ulbuf[34])
out_put += put_uint32_be(ulbuf[33])
out_put += put_uint32_be(ulbuf[32])
return out_put

def crypt_ecb(self, input_data):
# SM4-ECB block encryption/decryption
input_data = bytes_to_list(input_data) # 将输入字节转成列表
if self.mode == SM4_ENCRYPT: # 判断是否要填充,PKCS7是经典的分组密码填充模式
if self.padding_mode == PKCS7:
input_data = pkcs7_padding(input_data)
elif self.padding_mode == ZERO:
input_data = zero_padding(input_data)

length = len(input_data)
i = 0
output_data = []
while length > 0: # ecb的加密
output_data += self.one_round(self.sk, input_data[i:i + 16])
i += 16
length -= 16
if self.mode == SM4_DECRYPT: # 解密的话要先去填充
if self.padding_mode == PKCS7:
return list_to_bytes(pkcs7_unpadding(output_data))
elif self.padding_mode == ZERO:
return list_to_bytes(zero_unpadding(output_data))
return list_to_bytes(output_data)

def crypt_cbc(self, iv, input_data):
# SM4-CBC buffer encryption/decryption
i = 0
output_data = []
tmp_input = [0] * 16
iv = bytes_to_list(iv) # cbc模式的特征,引入一个初始向量iv
if self.mode == SM4_ENCRYPT:
input_data = pkcs7_padding(bytes_to_list(input_data)) # 加密填充
length = len(input_data)
while length > 0: # cbc模式加密,很经典的实现方式
tmp_input[0:16] = xor(input_data[i:i + 16], iv[0:16])
output_data += self.one_round(self.sk, tmp_input[0:16])
iv = copy.deepcopy(output_data[i:i + 16])
i += 16
length -= 16
return list_to_bytes(output_data)
else: # 解密流程
length = len(input_data)
while length > 0:
output_data += self.one_round(self.sk, input_data[i:i + 16])
output_data[i:i + 16] = xor(output_data[i:i + 16], iv[0:16])
iv = copy.deepcopy(input_data[i:i + 16])
i += 16
length -= 16
return list_to_bytes(pkcs7_unpadding(output_data))

题目:

UNICTF2025-Subgroup-Scribe

题目:

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
from os import urandom
from random import randint
from secret import flag

sbox = [147, 138, 104, 87, 5, 201, 249, 141, 243, 72, 71, 221, 97, 174, 48, 155,
114, 225, 117, 105, 224, 70, 7, 108, 190, 146, 145, 130, 46, 209, 229, 226,
15, 112, 103, 27, 91, 181, 253, 183, 152, 165, 110, 44, 160, 66, 116, 0,
75, 26, 61, 96, 127, 157, 197, 164, 172, 20, 37, 68, 202, 101, 9, 3,
109, 31, 208, 98, 11, 144, 79, 25, 239, 231, 43, 36, 10, 2, 170, 251,
161, 135, 134, 166, 136, 177, 215, 82, 244, 218, 47, 137, 242, 76, 233, 115,
182, 153, 214, 84, 13, 159, 60, 74, 65, 54, 163, 56, 180, 30, 139, 236,
67, 64, 80, 119, 40, 206, 148, 93, 217, 81, 126, 162, 185, 186, 77, 234,
45, 142, 230, 179, 34, 193, 124, 107, 125, 198, 90, 23, 12, 232, 100, 16,
120, 59, 1, 6, 102, 24, 133, 176, 150, 187, 28, 51, 195, 85, 196, 219,
167, 227, 38, 55, 248, 241, 204, 235, 192, 194, 52, 252, 247, 4, 212, 58,
78, 245, 240, 21, 14, 29, 169, 8, 121, 86, 118, 184, 143, 129, 69, 205,
132, 213, 246, 238, 73, 53, 122, 62, 35, 210, 250, 149, 17, 203, 111, 18,
158, 33, 151, 50, 83, 57, 92, 123, 95, 63, 216, 189, 173, 175, 220, 94,
106, 41, 222, 154, 89, 156, 171, 32, 200, 88, 254, 99, 140, 228, 188, 207,
19, 113, 255, 49, 237, 223, 191, 168, 42, 211, 22, 199, 128, 39, 178, 131]


FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC]
CK = [0x00070E15, 0x8C939AA1, 0x181F262D, 0xA4ABB2B9, 0xAC019832, 0XD8DFE6ED, 0X2C333A41]


def rotl(x, n):
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))

def tau(a):
return ((sbox[(a >> 24) & 0xFF] << 24) |
(sbox[(a >> 16) & 0xFF] << 16) |
(sbox[(a >> 8) & 0xFF] << 8) |
sbox[a & 0xFF])

def L(b):
return b ^ rotl(b, 2) ^ rotl(b, 10) ^ rotl(b, 18) ^ rotl(b, 24)

def T(x):
return L(tau(x))

def T_prime(x):
b = tau(x)
return b ^ rotl(b, 13) ^ rotl(b, 23)

def gen_key(mk_int):
k = [(mk_int >> 96) & 0xFFFFFFFF, (mk_int >> 64) & 0xFFFFFFFF,
(mk_int >> 32) & 0xFFFFFFFF, mk_int & 0xFFFFFFFF]
k = [k[0] ^ FK[0], k[1] ^ FK[1], k[2] ^ FK[2], k[3] ^ FK[3]]
rk = []
for i in range(7):
k_next = k[i] ^ T_prime(k[i+1] ^ k[i+2] ^ k[i+3] ^ CK[i])
k.append(k_next)
rk.append(k_next)
return rk

def encrypt(msg, key):
assert len(msg) == len(key) == 16

msg = int.from_bytes(msg)
key = int.from_bytes(key)
rk = gen_key(key)
x = [(msg >> 96) & 0xFFFFFFFF, (msg >> 64) & 0xFFFFFFFF,
(msg >> 32) & 0xFFFFFFFF, msg & 0xFFFFFFFF]

for i in range(7):
x_next = x[0] ^ T(x[1] ^ x[2] ^ x[3] ^ rk[i])
x = x[1:] + [x_next]

y = x[::-1]
return int.to_bytes((y[0] << 96) | (y[1] << 64) | (y[2] << 32) | y[3], 16)

def enc_ecb(msg, key):
assert len(msg) % 16 == len(key) % 16 == 0
msg = [msg[16 * i : 16 * i + 16] for i in range(len(msg) // 16)]
enc = []
for i in msg:
enc += [encrypt(i, key)]
key = bytes([sbox[i] for i in key]) #
return b''.join(enc)

ROUNDS = 128
for r in range(ROUNDS):
print(f"--- Round {r + 1}/{ROUNDS} ---")
key = urandom(16)
coin = randint(0, 1)
msg = bytes.fromhex(input('msg > '))
if len({*zip(*[iter(msg)] * 16)}) * 16 != len(msg): print('🤡'); exit()
enc = enc_ecb(msg, key)
print(f'hint: {[enc.hex(), urandom(len(enc)).hex()][coin]}')
if int(input('give me coin > ')) != coin: print('🤬'); exit()
print(f'😊: {flag}')

官方wp:https://my.feishu.cn/docx/IB4Ad9hP3o0HWFxRxVtcBIKNnJb?from=from_copylink
wp中说:
首先 sbox 的循环节太短了,只有 128,所以加密 129 块即可得到两个 key 完全相同的块。

下面就是要想办法使得两个不同的明文加密后能看出来关联。这里使用的两个初始状态为 [0, 0, 0, 0] 和 [1, 1, 1, 0]。

[0, 0, 0, 0] 的密文为:$$[C_1=T(k_1), C_2 = T(C_1 \oplus k_2), C_3 = T(C_1 \oplus C_2 \oplus k_3), C_4 = T(C_1 \oplus C_2 \oplus C_3 \oplus C_4 \oplus k_4)]$$

[1, 1, 1, 0] 的密文为:$$[1 \oplus C_1, 1 \oplus C_2, 1 \oplus C_3, T(C_1 \oplus C_2 \oplus C_3 \oplus C_4 \oplus k_4 \oplus 1)]$$

这两者的密文最后只差 T 中的一个 $\oplus 1$,而由于 T 对于 sbox 后的内容是线性的,sbox 只有低 1 byte 会改变,所以只有可能有 256 种变化,直接将这 256 种变化存到一个 list 里,将两个密文异或看是不是在这里面就可以了。

脚本:

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
#!/usr/bin/env python3
from pwn import *
from os import urandom

sbox = [147, 138, 104, 87, 5, 201, 249, 141, 243, 72, 71, 221, 97, 174, 48, 155,
114, 225, 117, 105, 224, 70, 7, 108, 190, 146, 145, 130, 46, 209, 229, 226,
15, 112, 103, 27, 91, 181, 253, 183, 152, 165, 110, 44, 160, 66, 116, 0,
75, 26, 61, 96, 127, 157, 197, 164, 172, 20, 37, 68, 202, 101, 9, 3,
109, 31, 208, 98, 11, 144, 79, 25, 239, 231, 43, 36, 10, 2, 170, 251,
161, 135, 134, 166, 136, 177, 215, 82, 244, 218, 47, 137, 242, 76, 233, 115,
182, 153, 214, 84, 13, 159, 60, 74, 65, 54, 163, 56, 180, 30, 139, 236,
67, 64, 80, 119, 40, 206, 148, 93, 217, 81, 126, 162, 185, 186, 77, 234,
45, 142, 230, 179, 34, 193, 124, 107, 125, 198, 90, 23, 12, 232, 100, 16,
120, 59, 1, 6, 102, 24, 133, 176, 150, 187, 28, 51, 195, 85, 196, 219,
167, 227, 38, 55, 248, 241, 204, 235, 192, 194, 52, 252, 247, 4, 212, 58,
78, 245, 240, 21, 14, 29, 169, 8, 121, 86, 118, 184, 143, 129, 69, 205,
132, 213, 246, 238, 73, 53, 122, 62, 35, 210, 250, 149, 17, 203, 111, 18,
158, 33, 151, 50, 83, 57, 92, 123, 95, 63, 216, 189, 173, 175, 220, 94,
106, 41, 222, 154, 89, 156, 171, 32, 200, 88, 254, 99, 140, 228, 188, 207,
19, 113, 255, 49, 237, 223, 191, 168, 42, 211, 22, 199, 128, 39, 178, 131]


FK = [0xA3B1BAC6, 0x56AA3350, 0x677D9197, 0xB27022DC]
CK = [0x00070E15, 0x8C939AA1, 0x181F262D, 0xA4ABB2B9, 0xAC019832, 0XD8DFE6ED, 0X2C333A41]


def rotl(x, n):
return ((x << n) & 0xFFFFFFFF) | (x >> (32 - n))

def tau(a):
return ((sbox[(a >> 24) & 0xFF] << 24) |
(sbox[(a >> 16) & 0xFF] << 16) |
(sbox[(a >> 8) & 0xFF] << 8) |
sbox[a & 0xFF])

def L(b):
return b ^ rotl(b, 2) ^ rotl(b, 10) ^ rotl(b, 18) ^ rotl(b, 24)

def T(x):
return L(tau(x))

T_maps = set()
for i in range(256):
idx = sbox[i] ^ sbox[i ^ 1]
T_maps.add(L(idx))


p = remote("nc1.ctfplus.cn", 42974)
# scontext(log_level='debug')

for i in range(128):
msg = [b'\x00' * 16]

for _ in range(127):
msg.append(urandom(16))

# 第 129 个块:[1, 1, 1, 0]
msg.append(b'\x00\x00\x00\x01' * 3 + b'\x00\x00\x00\x00')

msg_hex = b''.join(msg).hex()

p.recvuntil(b'msg > ')
p.sendline(msg_hex.encode())

p.recvuntil(b'hint: ')
hint = bytes.fromhex(p.recvline()[:-1].decode())
#print(hint)

enc1 = hint[0:16]
enc2 = hint[-16:]
# 取第四个字节
enc1 = int.from_bytes(enc1[12:16],'big')
enc2 = int.from_bytes(enc2[12:16],'big')

x = enc1 ^ enc2
if x in T_maps:
coin = 0
else:
coin = 1

p.recvuntil(b'give me coin > ')

p.sendline(str(coin).encode())
print(i)

p.interactive()

SM4算法分析(国密学习 1/5)
https://baymax-fools.github.io/2026/03/21/crypto/SM4算法分析/
Author
Baymax
Posted on
March 21, 2026
Updated on
June 17, 2026
Licensed under