今年熵密杯马上开始了,做一下去年熵密杯题目复健一下。
初始谜题1
task.py: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
133import binascii
from pyasn1.codec.der.decoder import decode
from pyasn1.type import univ, namedtype
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from gmssl import sm3, func, sm2
from pyasn1.codec.der.encoder import encode
class SM2Cipher(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('xCoordinate', univ.Integer()), # -- x 分量
namedtype.NamedType('yCoordinate', univ.Integer()), # -- y 分量
namedtype.NamedType('hash', univ.OctetString()), # --哈希值
namedtype.NamedType('cipherText', univ.OctetString()) # -- SM4密钥密文
)
class EncryptedData(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('algorithm', univ.ObjectIdentifier('1.2.156.10197.1.104.2')), # -- SM4-CBC OID
namedtype.NamedType('iv', univ.OctetString()), # -- SM4-CBC加密使用的初始化向量(IV)
namedtype.NamedType('cipherText', univ.OctetString()) # -- SM4加密的密文
)
class EnvelopedData(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('encryptedKey', SM2Cipher()), # -- 使用SM2公钥加密SM4密钥的密文
namedtype.NamedType('encryptedData', EncryptedData()), # -- 使用SM4密钥对明文加密的密文
namedtype.NamedType('digestAlgorithm', univ.ObjectIdentifier('1.2.156.10197.1.401.1')), # -- SM3算法OID
namedtype.NamedType('digest', univ.OctetString()) # -- 对明文计算的摘要值
)
def sm4_cbc_encrypt(plaintext: bytes, key: bytes, iv: bytes):
backend = default_backend()
cipher = Cipher(algorithms.SM4(key), modes.CBC(iv), backend=backend) #填充模式 nopadding
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
return ciphertext
def sm2_encrypt(plaintext: bytes,public_key:bytes) -> bytes:
sm2_crypt = sm2.CryptSM2(private_key="",public_key=public_key.hex())
ciphertext = sm2_crypt.encrypt(plaintext)
return ciphertext
def sm3_hash(text:bytes):
hash_value = sm3.sm3_hash(func.bytes_to_list(text))
return hash_value
def read_key_from_file(file_path):
try:
with open(file_path, 'r') as file:
key = file.read().strip()
return key
except FileNotFoundError:
print(f"错误: 文件 {file_path} 未找到。")
except Exception as e:
print(f"错误: 发生了未知错误 {e}。")
return None
# 对由abcd组成的字符串加密的方法
def sm4_encrypt(plaintext:str,sm2_public_key: str,sm4_iv:str):
sm4_key = bytes.fromhex(read_key_from_file("key.txt")) #从文件读取固定的key
# sm4
envelope = EnvelopedData()
plaintext_bytes = plaintext.encode('utf-8')
ciphertext = sm4_cbc_encrypt(plaintext_bytes,sm4_key,bytes.fromhex(sm4_iv))
# sm2
encrypted_key = sm2_encrypt(sm4_key,bytes.fromhex(sm2_public_key))
# sm3
digest = sm3_hash(plaintext_bytes)
envelope['encryptedData'] = EncryptedData()
envelope['encryptedData']['iv'] = univ.OctetString(bytes.fromhex(sm4_iv))
envelope['encryptedData']['cipherText'] = univ.OctetString(ciphertext)
envelope['encryptedKey'] = SM2Cipher()
envelope['encryptedKey']['xCoordinate'] = univ.Integer(int.from_bytes(encrypted_key[:32], 'big'))
envelope['encryptedKey']['yCoordinate'] = univ.Integer(int.from_bytes(encrypted_key[32:64], 'big'))
envelope['encryptedKey']['hash'] = univ.OctetString(encrypted_key[64:96])
envelope['encryptedKey']['cipherText'] = univ.OctetString(encrypted_key[96:])
envelope['digest'] = univ.OctetString(bytes.fromhex(digest))
return encode(envelope).hex()
# 从asn1格式的16进制字符串提取参数
def asn1_parse(asn1_hex_str:str,asn1_spec):
# 将16进制字符串转换为字节
der_bytes = binascii.unhexlify(asn1_hex_str)
# 解码为ASN.1对象
enveloped_data, _ = decode(der_bytes, asn1Spec=asn1_spec)
# sm2
sm2_x = hex(int(enveloped_data['encryptedKey']['xCoordinate']))[2:]
sm2_y = hex(int(enveloped_data['encryptedKey']['yCoordinate']))[2:]
sm2_hash = enveloped_data['encryptedKey']['hash'].asOctets().hex()
sm2_ciphertext = enveloped_data['encryptedKey']['cipherText'].asOctets().hex()
# sm4
sm4_algorithm = str(enveloped_data['encryptedData']['algorithm'])
sm4_iv = enveloped_data['encryptedData']['iv'].asOctets().hex()
sm4_cipherText = enveloped_data['encryptedData']['cipherText'].asOctets().hex()
# sm3
digestAlgorithm = str(enveloped_data['digestAlgorithm'])
digest = enveloped_data['digest'].asOctets().hex()
# 输出提取的值
print("asn1格式的16进制字符串:")
print(f" asn1: {asn1_hex_str}")
print("SM2参数:")
print(f" xCoordinate: {sm2_x}")
print(f" yCoordinate: {sm2_y}")
print(f" hash: {sm2_hash}")
print(f" cipherText: {sm2_ciphertext}")
print("SM4参数:")
print(f" algorithm: {sm4_algorithm}")
print(f" iv: {sm4_iv}")
print(f" cipherText: {sm4_cipherText}")
print("SM3参数:")
print(f" digestAlgorithm: {digestAlgorithm}")
print(f" digest: {digest}")
if __name__ == "__main__":
plaintext = "6163616263626161626461646464636361626263626464626361616164636462636462646461646461626462646361636264616364646462646462626261636261646163626463636262616462646462616362616363646463646361616263646261636164636263646163646161636164646364646261626463636462636162636162646261626163636161616463616261646264616162646162626162626462616363616161636362616461626463616462646261626264626464626262636363636162616261626163616164616462626163636164646161646361626363646462626261636261636164646262646362616263636363626461636164646261636361646463616161626164626461636163636461646164616161616163616164636164646261646163626163636164616162636263616461636261646264626263626264636164646263616164626463626461646364616362626261616262616264616361626264636264616461646163626364626462636161636262636163616261616262626362636463616263616364616363626163636363636262646363616464626461616363646361626162636261636364646362626462616364626462626161616264636162626263626462626264646162626462616261616264626161616363636364616263626461636162616462616363616461646363636261636363616162646164626361616464646463646263646363636164626164646463646361636364616261626261646461646463626161616361626161626362626262636164626463636163626163616163636262646463646162616363616364636164646364626464626164626162636161616263646164636461626161636262646463636462646161636462626264626463646364636362626264616362646462636263616361626262616464636263616464616363646163616262616162626261626261616461636361636164636162626461646264636162646363636263616363646161636464626161616462636464646164616361646264616361626263646264616162636164636462616164646163616461646362626464"
sm2_key = "044f66804d1d30f4499377b96dc8e18faab8300ebddf3eb0fa2065214c260d64c08c6dfe7d9923d6d5baa3a0512a2ede03357c723230ebf77906f82dc1b0fccc1e"
iv = "43d4192f9f74e90543d4192f9f74e905"
asn1_hex_str = sm4_encrypt(bytes.fromhex(plaintext).decode('utf-8'),sm2_key,iv)
asn1_parse(asn1_hex_str,EnvelopedData())
数据:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21已知明文字符串由小写字母abcd(对应16进制数分别为61,62,63,64)组成
历史明文(16进制):
6462646361626161636364626364646263646161626463616261646262636462646162626263626164636363636463616163626462616363626163616362646261616463646161616264636264626364646363616163626464616263636461636262646162636363646161626263646161636262636462646262626361616161626361636364646463616463626361636164626162646162646463636162616161646161636264646161646364626164636264626463636463636361646464616363616463626462636162636163636361636161636161646163646164636462636162646262616363646361616264616263646163636362646362636361626162646163636164646361616364616363646362636364636262616262636261616264636162636462646164636461646264646262636161636163636162616461636261636463636263636264616261626164636461616364636461626363636262636461626362616463636362616263626263636463646161636361616162646362626261636363616464636362616264636362626261626264626262636164626462636464616164646263646462616261616161646464616464636361646163646363646361636463636162626262616363626463636461626361626463616263616261626464636261616461616162626264616162646463646363626464626162626162636263616262636461646162646263646164636362646362646264636364616361616262646262636462626261646261646461646264616362646463646362616363616164646361636264636262626263636264646363616462616161626163636464646161636464646462616461636261616263616262626262646161636163646363626362646462626261646264636362626264646263646263616362626164646164626164646162636161636162626462616461646262626362636264646461646261636161636463626464636361626164646264616364636364616163616161646263626162626164646164616162636462636362626461646163636361646464646462626162646163616263636163626362616164
对历史明文加密后的密文(16进制、ASN.1结构):
308203ec3079022100ff5673947ad9c366d38f0cd132ad7ba0b6a63ea59f76f0e25b67df69798a89d5022012c812d006516f4e88b555598ab2e550dcd2d2a9b9b556983b611b9f128f9bd7042032a5dec2edaabfba2eb099e58d7750968f575ec528cb2ad5e0d030c6e0abe62004101707b9e19548c1dd622a08bded3727f43082034006082a811ccf550168020410f69f35ded931fdc163a92981588a6f1a04820320de8e708aedddec471bc15f01d9d53369b6e1dfecce398d2fda760bf2a6116aae9140dd6f9e02b01dfcdf51e220b257f1d076cb53c5dd742894bd171fd15e18ad222391cd03d3b6577ff95c3b57074b53544554b5c1a08ecd4bfc7185520c074acac24a76dc46d6d0754dcd56b493e92bd9b4305e0b9708405b14a9f7c62830a06b4f89b1d06a0d941cae1a42aa768895c67f4012676ffdcd0f76d2af9fd018272efa11fbf07b98a9043391937e51a9b6677ae78451f1dcbcc869a4118a6b7f39e6e628924c4956723287db493e69f6f79879280f935ba4dd4177edc6da70c03cde16050fbc149a02fdf0a892a3210c0ab63e9931e1d1b17bf4fe5945906d5b2e4e4b279c6f22003f18a12541d2d09136d1f528d753bd31a41763ff4170da7b2e051c3412b17d62b58475c76d8745d9c15a53caf8ee591be9454e9f32554acf9f869f9eea4f9e5ca15df807452582d4597cde3747df56feb0fc4e27b2682ad14887a3f36b255ec22f9b9e303671e5e1d558d2940bb6a56d16ce1de148f1fb9ee07146ac2d8204ff090574fe783820a0f07370a726bd287a0b3da1afe21a1eb388ebf09782c60f98583d6460fddd2e31785c6faf2698ba4a00555a9b15a457ff3ac255583b8d57f4b7cfa1772b360b42327792c9fc96502b575336adc28b99fd2e3d418d460f086600f75eecaeea5504ac5a4a8ad0a283d7b1a64669e65d7ea3590535e1efb67e50da31d50497300791c2e29a19bf7e67fa98422fb9bc7153c2a2b508aef00ebf37a24736c71f24e3d2bcbd316dd4479acf38f8a6863e1f2cfbcec958288a95291bf11a3a79ad63e705c038527bdd5b85283e2efd8ea20fefac8e5d11d82f1b0874b3cc78fbf5e98aa905f3d158fe1b2ab173e292ee026f1c22118e75036c556b36aefaa7c7e5b153046bd7b77e11caae014073efde16c9b8d0532ac62de2550e58644bd198cc44ab052db66dad05cadebc5e0ae49eb95b9ef33140bb4dc7d1aa080e34aabd383365e34a2eb84b6b76d55670d194a6aa86be0a92e994f0a920ea9a89406dd186cdf0d0fc55a44782d6ae6edfee03129ef819bfa92f5da5714e149f682f744064365bc7bc35f040de2fe51e8964e656588f47939f06092a811ccf5501831101042042ebbdaa6ea569a1073a487750eb91c25ee940a60c243d0408a9d479737bbd99
目标密文(16进制、ASN.1结构):
3081e83079022100ff5673947ad9c366d38f0cd132ad7ba0b6a63ea59f76f0e25b67df69798a89d5022012c812d006516f4e88b555598ab2e550dcd2d2a9b9b556983b611b9f128f9bd7042032a5dec2edaabfba2eb099e58d7750968f575ec528cb2ad5e0d030c6e0abe62004101707b9e19548c1dd622a08bded3727f4303e06082a811ccf550168020410e59d1eba7b65ff98422fb9bc7052c5a40420b508aef00ebf37a24736c71f24e3d2bc27e4c3f59aa4f7a73659c4e0726befaa06092a811ccf550183110104201a643eca445db2a1af15a09dc7e19adc4816348b683935384639079b9b844482
要求破解目标密文
注意:task.py中的数据一点用都没有!纯纯晃人,想了半天有什么用,发现一点用都没有。
对于数据中给出历史密文和目标的ASN.1结构,我们只需要用题目给出的解封就可以提取出我们需要的信息,结果如下:1
2
3
4
5his_iv = 'f69f35ded931fdc163a92981588a6f1a'
his_cipherText = 'de8e708aedddec471bc15f01d9d53369b6e1dfecce398d2fda760bf2a6116aae9140dd6f9e02b01dfcdf51e220b257f1d076cb53c5dd742894bd171fd15e18ad222391cd03d3b6577ff95c3b57074b53544554b5c1a08ecd4bfc7185520c074acac24a76dc46d6d0754dcd56b493e92bd9b4305e0b9708405b14a9f7c62830a06b4f89b1d06a0d941cae1a42aa768895c67f4012676ffdcd0f76d2af9fd018272efa11fbf07b98a9043391937e51a9b6677ae78451f1dcbcc869a4118a6b7f39e6e628924c4956723287db493e69f6f79879280f935ba4dd4177edc6da70c03cde16050fbc149a02fdf0a892a3210c0ab63e9931e1d1b17bf4fe5945906d5b2e4e4b279c6f22003f18a12541d2d09136d1f528d753bd31a41763ff4170da7b2e051c3412b17d62b58475c76d8745d9c15a53caf8ee591be9454e9f32554acf9f869f9eea4f9e5ca15df807452582d4597cde3747df56feb0fc4e27b2682ad14887a3f36b255ec22f9b9e303671e5e1d558d2940bb6a56d16ce1de148f1fb9ee07146ac2d8204ff090574fe783820a0f07370a726bd287a0b3da1afe21a1eb388ebf09782c60f98583d6460fddd2e31785c6faf2698ba4a00555a9b15a457ff3ac255583b8d57f4b7cfa1772b360b42327792c9fc96502b575336adc28b99fd2e3d418d460f086600f75eecaeea5504ac5a4a8ad0a283d7b1a64669e65d7ea3590535e1efb67e50da31d50497300791c2e29a19bf7e67fa98422fb9bc7153c2a2b508aef00ebf37a24736c71f24e3d2bcbd316dd4479acf38f8a6863e1f2cfbcec958288a95291bf11a3a79ad63e705c038527bdd5b85283e2efd8ea20fefac8e5d11d82f1b0874b3cc78fbf5e98aa905f3d158fe1b2ab173e292ee026f1c22118e75036c556b36aefaa7c7e5b153046bd7b77e11caae014073efde16c9b8d0532ac62de2550e58644bd198cc44ab052db66dad05cadebc5e0ae49eb95b9ef33140bb4dc7d1aa080e34aabd383365e34a2eb84b6b76d55670d194a6aa86be0a92e994f0a920ea9a89406dd186cdf0d0fc55a44782d6ae6edfee03129ef819bfa92f5da5714e149f682f744064365bc7bc35f040de2fe51e8964e656588f47939f'
target_iv = 'e59d1eba7b65ff98422fb9bc7052c5a4'
target_cipherText = 'b508aef00ebf37a24736c71f24e3d2bc27e4c3f59aa4f7a73659c4e0726befaa'
根据题目信息,我们知道加密明文使用的算法为SM4-CBC,由于是分组密码,每一块运算是相对独立的,这道题中没有除了iv和cipher之外的信息,是没有办法入手的。本题的历史密文和明文有什么用呢?搜索可以发现目标密文的第一分组和历史密文中有重复部分,而历史明文我们又知道,这意味着我们可以得知目标明文的第一部分。
1
2
3
4
5
6
7
8
9for i in range(0, len(his_cipherText), 32):
if his_cipherText[i:i+32] == 'b508aef00ebf37a24736c71f24e3d2bc':
block = i // 32
msg = his_msg[block * 32: 32 * (block + 1)]
lst_cipher = his_cipherText[(block - 1) * 32: 32 * block]
tmp = xor(bytes.fromhex(msg), bytes.fromhex(lst_cipher))
msg1 = xor(tmp, bytes.fromhex(target_iv))
print(msg1)
#cddadadabbdbcbcd
对于明文的第二部分,并没有什么好方法,明文都为‘abcd’组成,所以说明文空间并不算大(真不大吗?$2^{32}$,python你就跑吧,一跑一个不吱声),直接爆破,用sm3值进行校验,C++跑了大概6min: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
std::atomic<uint64_t> progress{0};
std::atomic<bool> found(false);
void worker(uint64_t begin, uint64_t end);
std::string prefix = "cddadadabbdbcbcd";
uint8_t target[32];
size_t outlen;
std::string result;
std::string generate(uint64_t x)
{
static const char table[] = {'a','b','c','d'};
std::string s(16, 'a');
for (int i = 15; i >= 0; --i)
{
s[i] = table[x & 3]; // x % 4
x >>= 2; // x /= 4
}
return s;
}
void monitor(uint64_t total)
{
using namespace std::chrono;
auto start = steady_clock::now();
while (!found.load(std::memory_order_acquire))
{
std::this_thread::sleep_for(std::chrono::seconds(1));
auto now = steady_clock::now();
double elapsed = duration<double>(now - start).count();
uint64_t done = progress.load();
double speed = done / elapsed;
double percent = done * 100.0 / total;
double remain = (total - done) / speed;
std::cout
<< "\rProgress: "
<< std::fixed
<< std::setprecision(2)
<< percent
<< "% Speed: "
<< speed / 1000000
<< " MH/s ETA: "
<< remain
<< " s"
<< std::flush;
}
std::cout << std::endl;
}
void worker(uint64_t begin, uint64_t end) {
SM3_CTX sm3_ctx;
for(uint64_t i = begin; i < end; i++)
{
progress.fetch_add(1, std::memory_order_relaxed);
if(found.load(std::memory_order_acquire))
return;
auto s = prefix + generate(i);
uint8_t digest[32];
sm3_init(&sm3_ctx);
sm3_update(&sm3_ctx, (const uint8_t*)s.data(), s.size());
sm3_finish(&sm3_ctx, digest);
if (memcmp(digest, target, 32) == 0)
{
result = s;
found.store(true, std::memory_order_release);
return;
}
}
}
int main() {
hex_to_bytes("1a643eca445db2a1af15a09dc7e19adc4816348b683935384639079b9b844482", 64, target, &outlen);
std::vector<std::thread> threads;
uint64_t total = 1ULL << 32; // 4^16
unsigned n = std::thread::hardware_concurrency(); // 获取cpu核心数
if (n == 0) n = 8;
uint64_t step = total / n;
std::thread monitorThread(monitor, total);
for(unsigned i = 0; i < n; i++)
{
uint64_t begin = i * step;
uint64_t end = (i == n - 1) ? total : begin + step;
threads.emplace_back(worker, begin, end);
}
for(auto &t : threads)
t.join();
found.store(true);
monitorThread.join();
std::cout << "Found: " << result << std::endl;
}
// cddadadabbdbcbcddbbdbabdaaabcdba
初始谜题2
task.py: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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274import binascii
from datetime import datetime
from pyasn1.type import univ, namedtype
from pyasn1.codec.der.encoder import encode
from pyasn1.codec.der.decoder import decode
from gmssl import sm2
from pyasn1.codec.der import decoder, encoder
from pyasn1_modules import rfc2459
from gmssl.sm2 import CryptSM2
from pyasn1.type.useful import GeneralizedTime
from pyasn1.type.univ import Sequence
from pyasn1.type import useful
class ECPrimeFieldConfig(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('fieldType', univ.ObjectIdentifier('1.2.840.10045.1.1')), # Prime field OID
namedtype.NamedType('prime', univ.Integer()), # Prime number p
)
class ECCurveParameters(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('coefficientA', univ.OctetString()), # Curve coefficient a
namedtype.NamedType('coefficientB', univ.OctetString()), # Curve coefficient b
)
class ECDomainParameters(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('version', univ.Integer(1)), # Version number (1)
namedtype.NamedType('fieldParameters', ECPrimeFieldConfig()), # Field parameters 包含参数oid,p
namedtype.NamedType('curveParameters', ECCurveParameters()), # Curve parameters 包含参数a,b
namedtype.NamedType('basePoint', univ.OctetString()), # Base point G 基点
namedtype.NamedType('order', univ.Integer()), # Order n of base point 参数n
namedtype.NamedType('cofactor', univ.Integer(1)), # Cofactor 余因子 固定值为1
)
class SM2SignatureValue(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('r', univ.Integer()), # First part of signature
namedtype.NamedType('s', univ.Integer()), # Second part of signature
)
class SM2SignedData(univ.Sequence):
componentType = namedtype.NamedTypes(
# version
namedtype.NamedType('version', univ.Integer()),
# 哈希算法 OID(SM3)
namedtype.NamedType('digestAlgorithms', univ.ObjectIdentifier()),
# 签名值 r, s
namedtype.NamedType('sm2Signature', SM2SignatureValue()),
# 曲线参数
namedtype.NamedType('ecDomainParameters', ECDomainParameters()),
# 证书
namedtype.NamedType('certificate', univ.OctetString()),
# 签名时间
namedtype.NamedType('timestamp', GeneralizedTime()),
)
# 输入值全部为16进制字符串,g为x,y坐标的16进制字符串进行拼接
# p,a,b,n,g对应曲线参数;r,s为签名值的两部分
def asn1_package(version, oid, signature, curve_params, cert_hex, time_stamp):
sm2_signed_data = SM2SignedData()
# version
sm2_signed_data['version'] = version
# 哈希算法 OID(SM3)
sm2_signed_data['digestAlgorithms'] = oid
# 签名值 r, s
sm2_signed_data["sm2Signature"] = SM2SignatureValue()
sm2_signed_data["sm2Signature"]['r'] = int(signature[:64], 16)
sm2_signed_data["sm2Signature"]['s'] = int(signature[64:], 16)
# 曲线参数
sm2_signed_data["ecDomainParameters"] = ECDomainParameters()
sm2_signed_data["ecDomainParameters"]["fieldParameters"] = ECPrimeFieldConfig()
sm2_signed_data["ecDomainParameters"]["fieldParameters"]["prime"] = int(curve_params['p'], 16)
sm2_signed_data["ecDomainParameters"]["curveParameters"] = ECCurveParameters()
sm2_signed_data["ecDomainParameters"]["curveParameters"]["coefficientA"] = univ.OctetString(
bytes.fromhex(curve_params['a']))
sm2_signed_data["ecDomainParameters"]["curveParameters"]["coefficientB"] = univ.OctetString(
bytes.fromhex(curve_params['b']))
sm2_signed_data["ecDomainParameters"]['basePoint'] = univ.OctetString(bytes.fromhex('04' + curve_params['g']))
sm2_signed_data["ecDomainParameters"]['order'] = int(curve_params['n'], 16)
# 证书
sm2_signed_data["certificate"] = univ.OctetString(bytes.fromhex(cert_hex))
# 时间
dt = datetime.strptime(time_stamp, "%Y-%m-%d %H:%M:%S")
asn1_time_str = dt.strftime("%Y%m%d%H%M%SZ")
sm2_signed_data["timestamp"] = GeneralizedTime(asn1_time_str)
return encode(sm2_signed_data).hex()
class Sm2CertVerifier:
def __init__(self, cert_hex: str):
ca_pubkey = "8E1860588D9900C16BD19A0FE0A5ACC600224DBD794FFD34179E03698D52421F46E6D8C6E8AADE512C7B543395AC39C76384726C7F8BA537ABCA0C129ECD9882"
self.sm2_crypt = sm2.CryptSM2(public_key=ca_pubkey, private_key=None)
self.cert_tbs, self.signature_bytes, self.cert = self.parse_cert(bytes.fromhex(cert_hex))
def parse_cert(cert_der_bytes: bytes):
cert, _ = decoder.decode(cert_der_bytes, asn1Spec=rfc2459.Certificate())
tbs = cert.getComponentByName('tbsCertificate')
signature_bytes = cert.getComponentByName('signatureValue').asOctets()
return tbs, signature_bytes, cert
# 获取签名值
def decode_rs_from_der(self, signature: bytes) -> bytes:
seq, _ = decode(signature, asn1Spec=Sequence())
r = int(seq[0])
s = int(seq[1])
r_bytes = r.to_bytes(32, byteorder='big')
s_bytes = s.to_bytes(32, byteorder='big')
return r_bytes + s_bytes
def verify_signature(self, signature: bytes, tbs: str):
inter_cert_tbs_der = encoder.encode(tbs)
inter_signature = self.decode_rs_from_der(signature)
# 验证签名(tbs_der必须完整,签名必须64字节)
return self.sm2_crypt.verify_with_sm3(inter_signature.hex(), inter_cert_tbs_der)
def verify_certificate_expiration_date(self, tbs):
validity = tbs.getComponentByName('validity')
not_before = validity.getComponentByName('notBefore').getComponent()
not_after = validity.getComponentByName('notAfter').getComponent()
# 处理 UTCTime 和 GeneralizedTime 两种类型
if isinstance(not_before, useful.UTCTime):
not_before_time = datetime.strptime(str(not_before), "%y%m%d%H%M%SZ")
elif isinstance(not_before, useful.GeneralizedTime):
not_before_time = datetime.strptime(str(not_before), "%Y%m%d%H%M%SZ")
else:
raise ValueError("Unsupported notBefore time format")
if isinstance(not_after, useful.UTCTime):
not_after_time = datetime.strptime(str(not_after), "%y%m%d%H%M%SZ")
elif isinstance(not_after, useful.GeneralizedTime):
not_after_time = datetime.strptime(str(not_after), "%Y%m%d%H%M%SZ")
else:
raise ValueError("Unsupported notAfter time format")
now = datetime.now()
return not_before_time <= now <= not_after_time
def verify(self):
# 验证中间证书有效期
if not self.verify_certificate_expiration_date(self.cert_tbs):
print("证书已过期或尚未生效")
return False
# 验证中间证书签名
if not self.verify_signature(self.signature_bytes, self.cert_tbs):
print("证书验证未通过")
return False
return True
class SM2Config:
# sm2参数初始化
def __init__(self, asn1_str):
self.sm2_signed_data,asn1_acess = self.hex_to_asn1(asn1_str, SM2SignedData())
if len(asn1_acess) != 0:
raise ValueError("asn1长度有问题")
cert_hex = self.get_hex_value(self.sm2_signed_data['certificate'])
sm2_cert_verifier = Sm2CertVerifier(cert_hex)
valid = sm2_cert_verifier.verify()
if not valid:
raise TypeError("证书验证不通过")
g = self.get_hex_value(self.sm2_signed_data['ecDomainParameters']['basePoint'])
g = g[2:] if g.startswith("04") else g
self.ecc_table = {
'n': self.get_hex_value(self.sm2_signed_data['ecDomainParameters']['order']),
'p': self.get_hex_value(self.sm2_signed_data['ecDomainParameters']['fieldParameters']['prime']),
'g': g,
'a': self.get_hex_value(self.sm2_signed_data['ecDomainParameters']['curveParameters']['coefficientA']),
'b': self.get_hex_value(self.sm2_signed_data['ecDomainParameters']['curveParameters']['coefficientB']),
}
public_key = self.extract_public_key(sm2_cert_verifier.cert_tbs)
self.sm2_crypt = CryptSM2(
private_key="",
public_key=public_key,
ecc_table=self.ecc_table
)
self.sign = (int(self.sm2_signed_data['sm2Signature']['r']).to_bytes(32, 'big').hex().upper() +
int(self.sm2_signed_data['sm2Signature']['s']).to_bytes(32, 'big').hex().upper())
def hex_to_asn1(hex_str, asn1_spec):
"""
将16进制字符串转换回ASN.1对象
:param hex_str: 16进制字符串
:param asn1_spec: ASN.1结构定义
:return: ASN.1对象
"""
# 将16进制字符串转换为字节
der_bytes = binascii.unhexlify(hex_str)
# 解码为ASN.1对象
asn1_object, excess = decode(der_bytes, asn1Spec=asn1_spec)
return asn1_object,excess
def get_hex_value(value):
"""通用转换函数:将 ASN.1 值转换为 16 进制字符串(大写,无前缀)"""
if isinstance(value, univ.Integer):
return format(int(value), 'X') # Integer -> 直接转十六进制
elif isinstance(value, univ.OctetString):
return value.asOctets().hex().upper() # OctetString -> 字节转十六进制
else:
raise TypeError(f"Unsupported type: {type(value)}")
def extract_public_key(tbs):
spki = tbs.getComponentByName('subjectPublicKeyInfo')
public_key_bitstring = spki.getComponentByName('subjectPublicKey')
# 提取位串内容(包含开头的 0x04)
pubkey_bytes = bytearray(public_key_bitstring.asOctets())
# 转成十六进制字符串
return pubkey_bytes.hex()
def verify_misc(self):
if (int(self.sm2_signed_data['version']) != 1 or
str(self.sm2_signed_data['digestAlgorithms']) != '1.2.156.10197.1.401.1' or
str(self.sm2_signed_data['timestamp']) != "20250520101000Z"):
return False
return True
# sm2验签
def verify(self, data):
valid = self.verify_misc()
if not valid:
return valid
valid = self.sm2_crypt.verify_with_sm3(self.sign, data)
return valid
# 通过该函数可以产生一个合法的SM2SignedData
def generateSM2SignedDataExample():
# 版本
version = 1
# 哈希算法oid
oid = '1.2.156.10197.1.401.1'
# 签名值r, s
signature = '6f8eaff551d0f3fa6de74b75b33e1e58f9fdb4dc58e61c82e11e717ffcf168c4db3d5a90ff3625d12b8b658f8dbab34340c278b412b3aff25489e7feb1c75598'
r = signature[:64]
s = signature[64:]
# 曲线参数
curve_params = {
"n": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',
"p": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
"g": '32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0',
"a": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
"b": '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
}
# 证书
cert_hex = '3082017C30820122A003020102020D00947C8427D3E849B48A7E5136300A06082A811CCF550183753036310B300906035504061302434E31133011060355040A130A5368616E674D6942656931123010060355040313095368616E674D694341301E170D3235303532303035353330365A170D3330303531393035353330365A304D310B300906035504061302434E3110300E060355040A1307496E746572434131173015060355040B130E5368616E674D6942656932303235311330110603550403130A7368616E676D696265693059301306072A8648CE3D020106082A811CCF5501822D03420004CECC0005AED684A1E7E39C316E7F3F39BDD0490936BC0E1AFDDC1B9627A05B4418809E5327746EE1977913F036EF0A9A255C27D73C00E45D0BB205B34D2C80D4300A06082A811CCF5501837503480030450220360779CBF5AA6E5E9CC073D95E22C52C09E81CFC06A3916559063A3C8C1DFDE6022100ED0E5E5E51F3894A3EAC11F247739D9F6A88C961D89F68337972BC3CC6BB6706' # 证书16进制格式
# 时间
time_stamp = '2025-05-20 10:10:00'
# asn1封装
asn1_package_hex = asn1_package(version, oid, signature, curve_params, cert_hex, time_stamp)
return(asn1_package_hex)
if __name__ == '__main__':
# 验签
data = b"Hello, CryptoCup!"
asn1_package_hex = generateSM2SignedDataExample()
sm2_config = SM2Config(asn1_package_hex)
result = sm2_config.verify(data)
print(result)
SM2SignedData.txt:1
308202CD02010106092A811CCF5501831101304502206F8EAFF551D0F3FA6DE74B75B33E1E58F9FDB4DC58E61C82E11E717FFCF168C4022100DB3D5A90FF3625D12B8B658F8DBAB34340C278B412B3AFF25489E7FEB1C755983081E0020101302C06072A8648CE3D0101022100FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF30440420FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC042028E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E9304410432C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0022100FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123020101048201803082017C30820122A003020102020D00947C8427D3E849B48A7E5136300A06082A811CCF550183753036310B300906035504061302434E31133011060355040A130A5368616E674D6942656931123010060355040313095368616E674D694341301E170D3235303532303035353330365A170D3330303531393035353330365A304D310B300906035504061302434E3110300E060355040A1307496E746572434131173015060355040B130E5368616E674D6942656932303235311330110603550403130A7368616E676D696265693059301306072A8648CE3D020106082A811CCF5501822D03420004CECC0005AED684A1E7E39C316E7F3F39BDD0490936BC0E1AFDDC1B9627A05B4418809E5327746EE1977913F036EF0A9A255C27D73C00E45D0BB205B34D2C80D4300A06082A811CCF5501837503480030450220360779CBF5AA6E5E9CC073D95E22C52C09E81CFC06A3916559063A3C8C1DFDE6022100ED0E5E5E51F3894A3EAC11F247739D9F6A88C961D89F68337972BC3CC6BB6706180F32303235303532303130313030305A
数据:1
2
3
4待签名消息(字符串):
XWNKFFBLMLAGRVTWSRKEGDWAGQKPIAGI
给出能通过后台验签的SM2SignedData
大概意思就是他给了你用他的签名程序签发的SM2SignedData,这个SM2SignedData能够通过他的校验程序(校验程序则是task.py中的sm2_config.verify()),现在你需要做的是将待签名消息进行签名使得其能通过他的校验程序。
仔细观察这个SM2SignedData,把它还原成asn.1格式:1
2
3sig = "308202CD02010106092A811CCF5501831101304502206F8EAFF551D0F3FA6DE74B75B33E1E58F9FDB4DC58E61C82E11E717FFCF168C4022100DB3D5A90FF3625D12B8B658F8DBAB34340C278B412B3AFF25489E7FEB1C755983081E0020101302C06072A8648CE3D0101022100FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF30440420FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC042028E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E9304410432C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0022100FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123020101048201803082017C30820122A003020102020D00947C8427D3E849B48A7E5136300A06082A811CCF550183753036310B300906035504061302434E31133011060355040A130A5368616E674D6942656931123010060355040313095368616E674D694341301E170D3235303532303035353330365A170D3330303531393035353330365A304D310B300906035504061302434E3110300E060355040A1307496E746572434131173015060355040B130E5368616E674D6942656932303235311330110603550403130A7368616E676D696265693059301306072A8648CE3D020106082A811CCF5501822D03420004CECC0005AED684A1E7E39C316E7F3F39BDD0490936BC0E1AFDDC1B9627A05B4418809E5327746EE1977913F036EF0A9A255C27D73C00E45D0BB205B34D2C80D4300A06082A811CCF5501837503480030450220360779CBF5AA6E5E9CC073D95E22C52C09E81CFC06A3916559063A3C8C1DFDE6022100ED0E5E5E51F3894A3EAC11F247739D9F6A88C961D89F68337972BC3CC6BB6706180F32303235303532303130313030305A"
sm2_signed_data,asn1_acess = hex_to_asn1(sig, SM2SignedData())
会得到:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23SM2SignedData:
version=1
digestAlgorithms=1.2.156.10197.1.401.1
sm2Signature=SM2SignatureValue:
r=50458832895354975404858210280433212468117735357467179639784622485710402840772
s=99164916576967261174991099442785947164942731917419269323510274019773278279064
ecDomainParameters=ECDomainParameters:
version=1
fieldParameters=ECPrimeFieldConfig:
fieldType=1.2.840.10045.1.1
prime=115792089210356248756420345214020892766250353991924191454421193933289684991999
curveParameters=ECCurveParameters:
coefficientA=0xfffffffeffffffffffffffffffffffffffffffff00000000fffffffffffffffc
coefficientB=0x28e9fa9e9d9f5e344d5a9e4bcf6509a7f39789f515ab8f92ddbcbd414d940e93
basePoint=0x0432c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0
order=115792089210356248756420345214020892766061623724957744567843809356293439045923
cofactor=1
certificate=0x3082017c30820122a003020102020d00947c8427d3e849b48a7e5136300a06082a811ccf550183753036310b300906035504061302434e31133011060355040a130a5368616e674d6942656931123010060355040313095368616e674d694341301e170d3235303532303035353330365a170d3330303531393035353330365a304d310b300906035504061302434e3110300e060355040a1307496e746572434131173015060355040b130e5368616e674d6942656932303235311330110603550403130a7368616e676d696265693059301306072a8648ce3d020106082a811ccf5501822d03420004cecc0005aed684a1e7e39c316e7f3f39bdd0490936bc0e1afddc1b9627a05b4418809e5327746ee1977913f036ef0a9a255c27d73c00e45d0bb205b34d2c80d4300a06082a811ccf5501837503480030450220360779cbf5aa6e5e9cc073d95e22c52c09e81cfc06a3916559063a3c8c1dfde6022100ed0e5e5e51f3894a3eac11f247739d9f6a88c961d89f68337972bc3cc6bb6706
timestamp=20250520101000Z
会发现里面包含了它签名时用的curve_params,还有个certificate。观察Sm2CertVerifier这个类可以知道是用来校验这个certificate的正确性的,且certificate中包含了验签程序所使用的public_key。
而Sm2CertVerifier这个类则是用来校验这个certificate的正确性的。SM2Config在Sm2CertVerifier校验成功后,会提取其中的public_key:public_key = self.extract_public_key(sm2_cert_verifier.cert_tbs)
所以我们可以用Sm2CertVerifier中的parse_cert将tbs解析出来,从而得到public_key:1
2
3
4
5certificate = sm2_signed_data.getComponentByName('certificate').asOctets()
tbs, signature_bytes, cert = parse_cert(certificate)
pub = extract_public_key(tbs)[2:] #去掉开头的0x04
print(pub)
#cecc0005aed684a1e7e39c316e7f3f39bdd0490936bc0e1afddc1b9627a05b4418809e5327746ee1977913f036ef0a9a255c27d73c00e45d0bb205b34d2c80d4
再来看SM2Config,可以知道它对certificate和一些杂项信息进行了校验。如果我们想通过伪造certificate去换掉public_key就得面对Sm2CertVerifier的一长串校验,并不是很现实。然后再看可以发现并没有对curveParameters做任何校验,也就是说你可以随意的替换curveParameters。
众所周知,SM2的密钥生成流程中时先选取曲线参数curveParameters,再决定私钥d,根据$P=d\cdot G$计算出公钥P。
现在我们只要保证公钥P不变,那我们大可以令$G=P$,这样私钥$d=1$,直接签名即可得到一份伪造好的签名。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
48from gmssl.sm2 import CryptSM2
certificate = '3082017c30820122a003020102020d00947c8427d3e849b48a7e5136300a06082a811ccf550183753036310b300906035504061302434e31133011060355040a130a5368616e674d6942656931123010060355040313095368616e674d694341301e170d3235303532303035353330365a170d3330303531393035353330365a304d310b300906035504061302434e3110300e060355040a1307496e746572434131173015060355040b130e5368616e674d6942656932303235311330110603550403130a7368616e676d696265693059301306072a8648ce3d020106082a811ccf5501822d03420004cecc0005aed684a1e7e39c316e7f3f39bdd0490936bc0e1afddc1b9627a05b4418809e5327746ee1977913f036ef0a9a255c27d73c00e45d0bb205b34d2c80d4300a06082a811ccf5501837503480030450220360779cbf5aa6e5e9cc073d95e22c52c09e81cfc06a3916559063a3c8c1dfde6022100ed0e5e5e51f3894a3eac11f247739d9f6a88c961d89f68337972bc3cc6bb6706'
msg = b'XWNKFFBLMLAGRVTWSRKEGDWAGQKPIAGI'
coefficientA = 0xfffffffeffffffffffffffffffffffffffffffff00000000fffffffffffffffc
coefficientB = 0x28e9fa9e9d9f5e344d5a9e4bcf6509a7f39789f515ab8f92ddbcbd414d940e93
basePoint = 0x0432c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0
order = 115792089210356248756420345214020892766061623724957744567843809356293439045923
prime = 115792089210356248756420345214020892766250353991924191454421193933289684991999
curve_params = {
"n": hex(order)[2:],
"p": hex(prime)[2:],
"g": pub,
"a": hex(coefficientA)[2:],
"b": hex(coefficientB)[2:],
}
sm2_crypt = CryptSM2(
private_key="1",
public_key=pub,
ecc_table=curve_params
)
sig = sm2_crypt.sign_with_sm3(msg)
def generateSM2SignedDataExample(sig, curve_params, certificate):
# 版本
version = 1
# 哈希算法oid
oid = '1.2.156.10197.1.401.1'
# 签名值r, s
signature = sig
r = signature[:64]
s = signature[64:]
# 证书
cert_hex = certificate # 证书16进制格式
# 时间
time_stamp = '2025-05-20 10:10:00'
# asn1封装
asn1_package_hex = asn1_package(version, oid, signature, curve_params, cert_hex, time_stamp)
return(asn1_package_hex)
fogery_sig = generateSM2SignedDataExample(sig, curve_params, certificate)
print(fogery_sig)
#308202cc02010106092a811ccf5501831101304402202efab06c4c0f5f2641feb0edc59f4330ba4b97b33345d0c903d9fd63f2d75c28022027eb474af2819210a9055bda754018a3bfbdfa12499fcfc11e3020dd53c289a33081e0020101302c06072a8648ce3d0101022100fffffffeffffffffffffffffffffffffffffffff00000000ffffffffffffffff30440420fffffffeffffffffffffffffffffffffffffffff00000000fffffffffffffffc042028e9fa9e9d9f5e344d5a9e4bcf6509a7f39789f515ab8f92ddbcbd414d940e93044104cecc0005aed684a1e7e39c316e7f3f39bdd0490936bc0e1afddc1b9627a05b4418809e5327746ee1977913f036ef0a9a255c27d73c00e45d0bb205b34d2c80d4022100fffffffeffffffffffffffffffffffff7203df6b21c6052b53bbf40939d54123020101048201803082017c30820122a003020102020d00947c8427d3e849b48a7e5136300a06082a811ccf550183753036310b300906035504061302434e31133011060355040a130a5368616e674d6942656931123010060355040313095368616e674d694341301e170d3235303532303035353330365a170d3330303531393035353330365a304d310b300906035504061302434e3110300e060355040a1307496e746572434131173015060355040b130e5368616e674d6942656932303235311330110603550403130a7368616e676d696265693059301306072a8648ce3d020106082a811ccf5501822d03420004cecc0005aed684a1e7e39c316e7f3f39bdd0490936bc0e1afddc1b9627a05b4418809e5327746ee1977913f036ef0a9a255c27d73c00e45d0bb205b34d2c80d4300a06082a811ccf5501837503480030450220360779cbf5aa6e5e9cc073d95e22c52c09e81cfc06a3916559063a3c8c1dfde6022100ed0e5e5e51f3894a3eac11f247739d9f6a88c961d89f68337972bc3cc6bb6706180f32303235303532303130313030305a
初始谜题3
task.py: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
100from typing import List, Callable
from hashlib import sha256
def hex_to_32byte_chunks(hex_str):
# 确保十六进制字符串长度是64的倍数(因为32字节 = 64个十六进制字符)
if len(hex_str) % 64 != 0:
raise ValueError("十六进制字符串长度必须是64的倍数")
# 每64个字符分割一次,并转换为字节
return [bytes.fromhex(hex_str[i:i + 64]) for i in range(0, len(hex_str), 64)]
def openssl_sha256(message: bytes) -> bytes:
return sha256(message).digest()
class WOTSPLUS:
def __init__(
self,
w: int = 16, # Winternitz 参数,控制空间与时间的复杂度
hashfunction: Callable = openssl_sha256, # 哈希函数
digestsize: int = 256, # 摘要大小,单位为比特
pubkey: List[bytes] = None,
) -> None:
self.w = w
if not (2 <= w <= (1 << digestsize)):
raise ValueError("规则错误:2 <= w <= 2^digestsize")
# 消息摘要所需的密钥数量(默认8个)
self.msg_key_count = 8
# 校验和密钥数量
self.cs_key_count = 0
# 总密钥数量 = 消息密钥 + 校验和密钥
self.key_count = self.msg_key_count + self.cs_key_count
self.hashfunction = hashfunction
self.digestsize = digestsize
self.pubkey = pubkey
def number_to_base(num: int, base: int) -> List[int]:
if num == 0:
return [0] # 如果数字是 0,直接返回 0
digits = [] # 存储转换后的数字位
while num:
digits.append(int(num % base)) # 获取当前数字在目标进制下的个位,并添加到结果列表
num //= base # 对数字进行整除,处理下一位
return digits[::-1] # 返回按顺序排列的结果
def _chain(self, value: bytes, startidx: int, endidx: int) -> bytes:
for i in range(startidx, endidx):
value = self.hashfunction(value) # 每次迭代对当前哈希值进行哈希操作
return value
def get_signature_base_message(self, msghash: bytes) -> List[int]:
# 将消息哈希从字节转换为整数
msgnum = int.from_bytes(msghash, "big")
# 将消息的数字表示转换为特定进制下的比特组表示
msg_to_sign = self.number_to_base(msgnum, self.w)
# 校验消息比特组的数量是否符合预期
if len(msg_to_sign) > self.msg_key_count:
err = (
"The fingerprint of the message could not be split into the"
+ " expected amount of bitgroups. This is most likely "
+ "because the digestsize specified does not match to the "
+ " real digestsize of the specified hashfunction Excepted:"
+ " {} bitgroups\nGot: {} bitgroups"
)
raise IndexError(err.format(self.msg_key_count, len(msg_to_sign)))
print(msg_to_sign)
return msg_to_sign
def get_pubkey_from_signature(
self, digest: bytes, signature: List[bytes]
) -> List[bytes]:
msg_to_verify = self.get_signature_base_message(digest)
result = []
for idx, val in enumerate(msg_to_verify):
sig_part = signature[idx]
chained_val = self._chain(sig_part, val, self.w - 1)
result.append(chained_val)
return result
def verify(self, digest: bytes, signature: List[bytes]) -> bool:
pubkey = self.get_pubkey_from_signature(digest, signature)
return True if pubkey == self.pubkey else False
if __name__ == "__main__":
pubkey_hex = "5057432973dc856a7a00272d83ea1c14de52b5eb3ba8b70b373db8204eb2f902450e38dbade5e9b8c2c3f8258edc4b7e8101e94ac86e4b3cba92ddf3d5de2a2b454c067a995060d1664669b45974b15b3423cec342024fe9ccd4936670ec3abaae4f6b97279bd8eb26463a8cb3112e6dcbf6301e4142b9cdc4adfb644c7b114af4f0cf8f80e22c3975ba477dc4769c3ef67ffdf2090735d81d07bc2e6235af1ee41ef332215422d31208c2bc2163d6690bd32f4926b2858ca41c12eec88c0a300571901a3f674288e4a623220fb6b70e558d9819d2f23da6d897278f4056c346d7f729f5f70805ad4e5bd25cfa502c0625ac02185e014cf36db4ebcdb3ed1a38"
pubkey_list_bytes = hex_to_32byte_chunks(pubkey_hex)
wots = WOTSPLUS(pubkey = pubkey_list_bytes)
digest_hex = "84ffb82e"
signature_hex = "25d5a0e650d683506bfe9d2eca6a3a99b547a4b99398622f6666ce10131e971b6bd36841c9074fe9b4de2900ebe3fadb3202a173be486da6cf8f3d8c699c95c3454c067a995060d1664669b45974b15b3423cec342024fe9ccd4936670ec3abaae4f6b97279bd8eb26463a8cb3112e6dcbf6301e4142b9cdc4adfb644c7b114a4966398a789b56bdb09ea195925e7e8cde372305d244604c48db08f08a6e8a38951030deb25a7aaf1c07152a302ebc07d5d0893b5e9a5953f3b8500179d138b9aa90c0aaacea0c23d22a25a86c0b747c561b480175b548fcb1f4ad1153413bc74d9c049d43ffe18ceee31e5be8bdb9968103ef32fb4054a4a23c400bbfe0d89f"
digest_bytes = bytes.fromhex(digest_hex)
signature = hex_to_32byte_chunks(signature_hex)
valid = wots.verify(digest_bytes,signature)
print(valid)
数据:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19历史消息摘要(16进制):
44a8e947
历史签名(16进制):
672147aae80a80ef10b9b98c76a1aee8d3817f2903f8f2490e10e10dda576c3666ce4364508089a5c53ff0bf7c58fe9071d13ac7dcf5423ab18e68026915f41544441c7f354adff1d7d1fd359e1a47cd755162b5c745bb1099894ae29c71c6dddd23e44dbc143c34291bacc7d8c525cae3147ec5794843a71388e7eabf0816d845dfaddb0c902bb7fffe35efba99755359c3d16d98a4e74488db7e7c6b1c38c7b541069a2212ef996c572104b827c1e71d515afd3e9eb08ef9f979209fa798ccb438f7b9f6fb7383c72be73fa1499e1e9da786ab8a4ec3d30c1c1076cbe93593f9e7f55b25879d0a9d7c32fea02a1affaf2e65fb58a9ec95be8e34fb6995a8dd
公钥(16进制):
fff5b12dfb1247a8fac88884466c5f51bc9404cf2c6f7d354cff26181c813daa975d4e9af1430a59ae67df93e6acaef879c3b5bda6e2615f685f71fc650df6cba5c401868483165b9d1524ff5cf94a29eaf209678c62635e8a7f498289d471d4e4d76945c45bbfbe4a1e749962db885a9d820b0428f63d199e938779da4b938604cdd70d5200ec90a09f93e44951a5b40907f97015336fe105ee97cadebd72ab2da99a292ff53eb37514afad5e7d59de68feed49e8fdea15d442ea204c4ab064203fa932f6b7ec73cc36139ce81bfd71103490eed1e8c8e3d38dede84336774e66714b087ebc9e2ed8bb4ce8e4e6937396c38ba05f027ac3ef12315453cb5803
给出一组摘要和签名可以通过验签
题目很简单明确,就是让你构造一组和历史不同的摘要和签名能够通过它给出的公钥的验证。
观察验签流程,可以知道如下:将signature每32byte为一组,分为若干组。将消息摘要转化为十六进制列表的形式(比如'44a8e947'转为[4,4,a,8,e,9,4,7])。对signature对应的分组做$(15-x)$次数的hash操作(比如十六进制列表第一个元素为4,则对signature的第一个分组做(15-4)次hash操作)。最后将hash操作完的所有分组和pubkey的对应分组一一进行比对,如果全部都对应,则校验成功。
我们可以在历史签名上操作,在签名某一分组上多做一次hash操作,然后修改对应的摘要位置即可成功伪造。
或者还有更简单的办法,直接将摘要设置为ffffffff,那么签名和公钥则完全相同。