第四届熵密杯——初始谜题

admin 2026-08-08 06:31:21 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文档为第四届熵密杯初始谜题题解。谜题一剖析了自实现SM2签名的逻辑缺陷,其验证函数在取模前校验参数,攻击者设r为n减1且s为1使取模后为零,触发错误倍点逻辑,成功构造万能签名。谜题二涉及AES加密但分析中断。核心建议是密码学开发务必在取模后执行参数校验以防范逻辑绕过。 综合评分: 89 文章分类: CTF,漏洞分析,安全开发,代码审计


cover_image

第四届熵密杯——初始谜题

原创

Van1sh Van1sh

Van1sh

2026年7月29日 10:45 中国香港

在小说阅读器读本章

去阅读

第三次参加熵密杯了,在同事的助力下,这次侥幸拿下一等奖。美中不足的是最后一道题只差临门一脚,赛后检查发现是代码中有一些数据处理的小bug,可惜。 由于篇幅原因,这次题解会分成三篇文章,分别是初始谜题、工程师站路线和日志管理服务器路线。

初始谜题一

题目代码

import secrets

default_table = {
    'n': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',
    'p': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
    'g': '32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7'
         'bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0',
    'a': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
    'b': '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
}

class Crypt():

    def __init__(self, private_key, public_key, mode=0):
        self.private_key = private_key
        if public_key.startswith("04"):
            self.public_key = public_key[2:]
        else:
            self.public_key = public_key
        self.para_len = len(default_table['n'])
        self.ecc_a3 = (
            int(default_table['a'], base=16) + 3) % int(default_table['p'], base=16)
        assert mode in (0, 1), 'mode must be one of (0, 1)'
        self.mode = mode

    def _kg(self, k, Point):  # kP运算
        if k == 0:
            return None #无穷远点
        Point = '%s%s' % (Point, '1')
        mask_str = '8'
        for i in range(self.para_len - 1):
            mask_str += '0'
        mask = int(mask_str, 16)
        Temp = Point
        flag = False
        for n in range(self.para_len * 4):
            if (flag):
                Temp = self._double_point(Temp)
            if (k & mask) != 0:
                if (flag):
                    Temp = self._add_point(Temp, Point)
                else:
                    flag = True
                    Temp = Point
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; k = k <<&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;self._convert_jacb_to_nor(Temp)

&nbsp; &nbsp;&nbsp;def&nbsp;_double_point(self, Point):&nbsp;&nbsp;# 倍点
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;Point&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp; l = len(Point)
&nbsp; &nbsp; &nbsp; &nbsp; len_2 =&nbsp;2&nbsp;* self.para_len
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;l < self.para_len *&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x1 = int(Point[0:self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; y1 = int(Point[self.para_len:len_2],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;l == len_2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; z1 =&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; z1 = int(Point[len_2:],&nbsp;16)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T6 = (z1 * z1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T2 = (y1 * y1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T3 = (x1 + T6) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T4 = (x1 - T6) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (T3 * T4) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T3 = (y1 * z1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T4 = (T2 *&nbsp;8) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T5 = (x1 * T4) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (T1 *&nbsp;3) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T6 = (T6 * T6) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T6 = (self.ecc_a3 * T6) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (T1 + T6) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; z3 = (T3 + T3) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T3 = (T1 * T1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T2 = (T2 * T4) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x3 = (T3 - T5) % int(default_table['p'], base=16)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(T5 %&nbsp;2) ==&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T4 = (T5 + ((T5 + int(default_table['p'], base=16)) >>&nbsp;1) - T3) % int(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T4 = (T5 + (T5 >>&nbsp;1) - T3) % int(default_table['p'], base=16)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (T1 * T4) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; y3 = (T1 - T2) % int(default_table['p'], base=16)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; form =&nbsp;'%%0%dx'&nbsp;% self.para_len
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; form = form *&nbsp;3
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;form % (x3, y3, z3)

&nbsp; &nbsp;&nbsp;def&nbsp;_add_point(self, P1, P2):&nbsp;&nbsp;# 点加函数,P2点为仿射坐标即z=1,P1为Jacobian加重射影坐标
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;P1&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;P2
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;P2&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;P1
&nbsp; &nbsp; &nbsp; &nbsp; len_2 =&nbsp;2&nbsp;* self.para_len
&nbsp; &nbsp; &nbsp; &nbsp; l1 = len(P1)
&nbsp; &nbsp; &nbsp; &nbsp; l2 = len(P2)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(l1 < len_2)&nbsp;or&nbsp;(l2 < len_2):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; X1 = int(P1[0:self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Y1 = int(P1[self.para_len:len_2],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(l1 == len_2):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Z1 =&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Z1 = int(P1[len_2:],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x2 = int(P2[0:self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; y2 = int(P2[self.para_len:len_2],&nbsp;16)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (Z1 * Z1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T2 = (y2 * Z1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T3 = (x2 * T1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (T1 * T2) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T2 = (T3 - X1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T3 = (T3 + X1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T4 = (T2 * T2) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (T1 - Y1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Z3 = (Z1 * T2) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T2 = (T2 * T4) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T3 = (T3 * T4) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T5 = (T1 * T1) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T4 = (X1 * T4) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; X3 = (T5 - T3) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T2 = (Y1 * T2) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T3 = (T4 - X3) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; T1 = (T1 * T3) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Y3 = (T1 - T2) % int(default_table['p'], base=16)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; form =&nbsp;'%%0%dx'&nbsp;% self.para_len
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; form = form *&nbsp;3
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;form % (X3, Y3, Z3)

&nbsp; &nbsp;&nbsp;def&nbsp;_convert_jacb_to_nor(self, Point):&nbsp;&nbsp;# Jacobian加重射影坐标转换成仿射坐标
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;Point&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp; len_2 =&nbsp;2&nbsp;* self.para_len
&nbsp; &nbsp; &nbsp; &nbsp; x = int(Point[0:self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; y = int(Point[self.para_len:len_2],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; z = int(Point[len_2:],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; z_inv = pow(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; z, int(default_table['p'], base=16) -&nbsp;2, int(default_table['p'], base=16))
&nbsp; &nbsp; &nbsp; &nbsp; z_invSquar = (z_inv * z_inv) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; z_invQube = (z_invSquar * z_inv) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; x_new = (x * z_invSquar) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; y_new = (y * z_invQube) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp; z_new = (z * z_inv) % int(default_table['p'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;z_new ==&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; form =&nbsp;'%%0%dx'&nbsp;% self.para_len
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; form = form *&nbsp;2
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;form % (x_new, y_new)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None

&nbsp; &nbsp;&nbsp;def&nbsp;verify(self, Sign, data):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;Sign&nbsp;is&nbsp;None&nbsp;or&nbsp;Sign ==&nbsp;'':
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp; r = int(Sign[0:self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; s = int(Sign[self.para_len:2*self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; e = int(data.hex(),&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 参数合法性
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;(1&nbsp;<= r < int(default_table['n'], base=16)&nbsp;and&nbsp;1&nbsp;<= s < int(default_table['n'], base=16)):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp; t = r + s
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;t ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t = t % int(default_table['n'], base=16)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;self.public_key&nbsp;is&nbsp;None&nbsp;or&nbsp;self.public_key ==&nbsp;'':
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None

&nbsp; &nbsp; &nbsp; &nbsp; P1 = self._kg(s, default_table['g'])
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;P1&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp; P2 = self._kg(t, self.public_key)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;P1 == P2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P1 =&nbsp;'%s%s'&nbsp;% (P1,&nbsp;1)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P1 = self._double_point(P1)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P1 =&nbsp;'%s%s'&nbsp;% (P1,&nbsp;1)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P1 = self._add_point(P1, P2)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P1 = self._convert_jacb_to_nor(P1)

&nbsp; &nbsp; &nbsp; &nbsp; x = int(P1[0:self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;r == ((e + x) % int(default_table['n'], base=16))

&nbsp; &nbsp;&nbsp;def&nbsp;sign(self, data):
&nbsp; &nbsp; &nbsp; &nbsp; k = secrets.randbelow(int(default_table['n'],&nbsp;16) -&nbsp;1) +&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;(1&nbsp;<= k <= int(default_table['n'], base=16)-&nbsp;1):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp; E = data.hex()
&nbsp; &nbsp; &nbsp; &nbsp; e = int(E,&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;self.private_key&nbsp;is&nbsp;None&nbsp;or&nbsp;self.private_key ==&nbsp;'':
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None

&nbsp; &nbsp; &nbsp; &nbsp; d = int(self.private_key,&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; P1 = self._kg(k, default_table['g'])

&nbsp; &nbsp; &nbsp; &nbsp; x = int(P1[0:self.para_len],&nbsp;16)
&nbsp; &nbsp; &nbsp; &nbsp; A = ((e + x) % int(default_table['n'], base=16))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;A ==&nbsp;0&nbsp;or&nbsp;A + k == int(default_table['n'], base=16):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp; d_1 = pow(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; d+1, int(default_table['n'], base=16) -&nbsp;2, int(default_table['n'], base=16))
&nbsp; &nbsp; &nbsp; &nbsp; B = (d_1*(k + A) - A) % int(default_table['n'], base=16)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;B ==&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;None
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;'%064x%064x'&nbsp;% (A, B)

if&nbsp;__name__ ==&nbsp;'__main__':
&nbsp; &nbsp; crypt = Crypt(
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 公钥格式为64字节的16进制字符串
&nbsp; &nbsp; &nbsp; &nbsp; public_key='',
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 私钥格式为32字节的16进制字符串
&nbsp; &nbsp; &nbsp; &nbsp; private_key=''
&nbsp; &nbsp; )
&nbsp; &nbsp; data =&nbsp;'919535c3ef53d3fa359196b5229c4bbb386ce209f5905d33fc7bcdff46ae27c2'
&nbsp; &nbsp; sign = crypt.sign(bytes.fromhex(data))
&nbsp; &nbsp; print(sign)
&nbsp; &nbsp; verify = crypt.verify(sign,bytes.fromhex(data))
&nbsp; &nbsp; print(verify)

题目要求

构造一组可以通过验签的摘要值以及对应的签名值,并提交答案。

题目考点

SM2签名自实现缺陷

解题思路

初始谜题一自实现了一个 SM2 的签名,并要求我们构造一组可以通过验签的摘要值以及对应的签名值,并提交答案。

由于题目并没有给出公钥,因此可以断定我们需要构造一组能够绕过Verify的消息签名,即对于任意公钥均能验签成功,因此我们关注 Verify 函数的实现。

这段代码虽然检查了  不能等于0,但是取余运算却放在了最后,因此我们可以构造

,使得  值先通过判断,然后再取余后为0。方便起见,我们可以设 。

于是根据验签公式,我们有

最后验签是要求

而正好,这里的消息哈希 e 我们也是能够直接控制的,于是我们设  即可

(当然也可以设,,此时摘要就是,答案不唯一)

参考答案

摘要值:r-G.x % n
cd3b51d2e0e67ee6a066fbb995c6366ae220d3ab2f5ff949e261ae800688cc5b
签名值:n-1||1
FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D541220000000000000000000000000000000000000000000000000000000000000001

初始谜题二

题目代码

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import struct

def encrypt_card_data(item) -> str:
&nbsp; &nbsp; result = f"{item.card},{item.pin},{item.id}"
&nbsp; &nbsp; plaintext = result.encode('utf-8')
&nbsp; &nbsp; key = get_key()
&nbsp; &nbsp;&nbsp;# item.index 数据序号
&nbsp; &nbsp; nonce = get_iv_byte(item.index)
&nbsp; &nbsp; tmp = encrypt(key,nonce,plaintext).hex()
&nbsp; &nbsp;&nbsp;return&nbsp;tmp

def get_iv_byte(index: int, prefix=b'', length=16) -> bytes:
&nbsp; &nbsp;&nbsp;if&nbsp;not (0 <= index < 2**32):
&nbsp; &nbsp; &nbsp; &nbsp; raise ValueError("Index 必须在 0 到 2^32-1 之间")
&nbsp; &nbsp; index_bytes = struct.pack('>I', index)
&nbsp; &nbsp; pad_len = length - len(prefix) - len(index_bytes)
&nbsp; &nbsp;&nbsp;if&nbsp;pad_len < 0:
&nbsp; &nbsp; &nbsp; &nbsp; prefix=b''
&nbsp; &nbsp; &nbsp; &nbsp; pad_len = length - len(index_bytes)
&nbsp; &nbsp;&nbsp;return&nbsp;prefix + (b'\x00'&nbsp;* pad_len) + index_bytes

def encrypt(key,nonce,plaintext) -> bytes:
&nbsp; &nbsp; algorithm = algorithms.SM4(key)
&nbsp; &nbsp; mode = modes.CTR(nonce)
&nbsp; &nbsp; cipher = Cipher(algorithm, mode)
&nbsp; &nbsp; encryptor = cipher.encryptor()
&nbsp; &nbsp; ciphertext = encryptor.update(plaintext) + encryptor.finalize()
&nbsp; &nbsp;&nbsp;return&nbsp;ciphertext

相关数据

序号 &nbsp;密文(十六进制)
1000 &nbsp;7d2a3dc99109ff913cc52877cf3578621eaf51d256b043550eac8c377c98357de1b0d616039e6f821850386d86d96057f195539c28b8ed31430f27b16ca55f
1001 &nbsp;1cae58cb5cb947500aa4963f7d93607eeeb4d717069f678b0053226887cd6754fdc24cd32bebb8395f0a72b86ee90c278c4323c288be33e5d9282884acf9fb
1002 &nbsp;eeb6d40e049f6f8003523e6f8fc33854f0ce46d229e3bf3d45083cb96efd0b25d4453c8e8db930ecc67c2ed7a8edff97a668a0c45d36b0c2b36257a2039117
序号 &nbsp; &nbsp;银行卡号 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PIN码 &nbsp; &nbsp; 用户ID
1000 &nbsp; 6205310997298382439 &nbsp; &nbsp;984631 &nbsp; 019f1942-4000-7650-a25c-b7ce812783a2
1001 &nbsp; 6205310379680232603 &nbsp; &nbsp;118954 &nbsp; 019f1942-4000-7b11-a3a2-a17392cc649e
1002

题目要求

提交序号为1003用户的银行卡号、PIN码

题目考点

SM4-CTR 模式 nonce 选取不正确导致密钥流复用

解题思路

题目利用SM4-CTR 模式对明文数据进行加密,但是注意到加密时nonce的取值来自item.index,而明文数据中的index是连续递增的。因此加密第一份明文的密钥流的第二组,就是加密第二份明文密钥流的第一组,如下图所示

于是想要恢复第三组明文,我们可以取第一组的明文和密文,异或得到密钥流;从第三组开始,即截取32字节后的部分,用于解密第三组密文,即可得到题目所需银行卡号与PIN码(用户ID我们无法恢复完全)

(当然也可以取第一组的明文和密文,异或得到密钥流;从第二组开始,即截取16字节后的部分,用于解密第三组密文)

解题代码

from&nbsp;pwn&nbsp;import&nbsp;xor
from&nbsp;Crypto.Util.number&nbsp;import&nbsp;*

a =&nbsp;b"6205310997298382439,984631,019f1942-4000-7650-a25c-b7ce812783a2"
b =&nbsp;b"6205310379680232603,118954,019f1942-4000-7b11-a3a2-a17392cc649e"

c = bytes.fromhex('7d2a3dc99109ff913cc52877cf3578621eaf51d256b043550eac8c377c98357de1b0d616039e6f821850386d86d96057f195539c28b8ed31430f27b16ca55f')
d = bytes.fromhex('eeb6d40e049f6f8003523e6f8fc33854f0ce46d229e3bf3d45083cb96efd0b25d4453c8e8db930ecc67c2ed7a8edff97a668a0c45d36b0c2b36257a2039117')
print(xor(xor(a,c)[32:],d))

初始谜题三

题目代码

"""

参数:
&nbsp; &nbsp; n &nbsp; &nbsp; = 17669 &nbsp; (环维度,要求为素数(保证 X^n-1 在 GF(2) 上仅有两个不可约因子))
&nbsp; &nbsp; k &nbsp; &nbsp; = 16 &nbsp; &nbsp; &nbsp;(消息长度,字节)
&nbsp; &nbsp; n1 &nbsp; &nbsp;= 46 &nbsp; &nbsp; &nbsp;(外码 RS[46, 16, 31] 的码长)
&nbsp; &nbsp; n2 &nbsp; &nbsp;= 384 &nbsp; &nbsp; (内码重复 RM 的码长(RM(1,7)×3 = [384, 8, 192]))
&nbsp; &nbsp; delta = 15 &nbsp; &nbsp; &nbsp;(RS纠错能力)
&nbsp; &nbsp; w &nbsp; &nbsp; = 66 &nbsp; &nbsp; &nbsp;(私钥向量 x, y 的 Hamming 重量)
&nbsp; &nbsp; w_r &nbsp; = 75 &nbsp; &nbsp; &nbsp;(随机向量 r1, r2 的 Hamming 重量)
&nbsp; &nbsp; w_e &nbsp; = 75 &nbsp; &nbsp; &nbsp;(误差向量 e 的 Hamming 重量)
&nbsp; &nbsp; 级联码结构:RS[46,16,31] over GF(2^8) ⊗ RM(1,7)×3
"""

import&nbsp;os, hashlib, random, reedsolo
from&nbsp;typing&nbsp;import&nbsp;Tuple, List

# ============================================================
# 参数
# ============================================================
PARAM_N &nbsp; &nbsp; =&nbsp;17669&nbsp; &nbsp; &nbsp;# 环 R = GF(2)[X]/(X^17669 - 1) 的维度
PARAM_K &nbsp; &nbsp; =&nbsp;16&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 消息长度 (字节)
PARAM_N1 &nbsp; &nbsp;=&nbsp;46&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 外码 RS[46, 16, 31] 的码长
PARAM_N2 &nbsp; &nbsp;=&nbsp;384&nbsp; &nbsp; &nbsp; &nbsp;# 内码重复 RM 的码长(RM(1,7)×3 = [384, 8, 192])
PARAM_DELTA =&nbsp;15&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# RS可纠错数
PARAM_W &nbsp; &nbsp; =&nbsp;66&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 私钥向量 x, y 的 Hamming 重量
PARAM_W_R &nbsp; =&nbsp;75&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 随机向量 r1, r2 的 Hamming 重量
PARAM_W_E &nbsp; =&nbsp;75&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 误差向量 e 的 Hamming 重量
PARAM_RM_M &nbsp;=&nbsp;7&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# RM(1,m)
PARAM_RM_LEN =&nbsp;128&nbsp; &nbsp; &nbsp;&nbsp;# RM(1,7) 码长 = 2^7
PARAM_REP &nbsp; =&nbsp;3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# RM 码重复次数

# GF(2^8) 不可约多项式: x^8 + x^4 + x^3 + x^2 + 1 = 0x11D
GF256_MOD =&nbsp;0x11D

# ============================================================
# Reed-Muller RM(1,7) 编码与解码
# ============================================================
# RM(1,7) 是一阶 Reed-Muller 码,参数为 [128, 8, 64]:
# &nbsp; 码长 &nbsp;n = 2^7 = 128
# &nbsp; 维度 &nbsp;k = 1 + 7 = 8(常数项 + 7 个坐标函数)
# &nbsp; 最小距离 d = 2^(7-1) = 64

def&nbsp;rm17_encode(symbol: int)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; RM(1,7) 编码:8 位符号 → 128 位码字。
&nbsp; &nbsp; 符号格式:bit7 = 常数项,bits[6:0] = 线性项系数。
&nbsp; &nbsp; """
&nbsp; &nbsp; codeword =&nbsp;0
&nbsp; &nbsp; const_bit = (symbol >>&nbsp;7) &&nbsp;1&nbsp;&nbsp;# 最高位 = 常数项
&nbsp; &nbsp;&nbsp;for&nbsp;pt&nbsp;in&nbsp;range(128): &nbsp;# 在所有点评估仿射函数
&nbsp; &nbsp; &nbsp; &nbsp; val = const_bit
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;bit&nbsp;in&nbsp;range(7):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(symbol >> bit) &&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; val ^= (pt >> bit) &&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;val:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; codeword |= (1&nbsp;<< pt)
&nbsp; &nbsp;&nbsp;return&nbsp;codeword

def&nbsp;rm17_decode(codeword: int)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; RM(1,7) 解码:128 位码字 → 8 位符号(Walsh-Hadamard 变换)。
&nbsp; &nbsp; """
&nbsp; &nbsp;&nbsp;# Map {0,1} -> {+1,-1}
&nbsp; &nbsp; signal = [1&nbsp;-&nbsp;2&nbsp;* ((codeword >> i) &&nbsp;1)&nbsp;for&nbsp;i&nbsp;in&nbsp;range(128)]
&nbsp; &nbsp;&nbsp;# 快速Walsh-Hadamard变换
&nbsp; &nbsp; wht = list(signal)
&nbsp; &nbsp; h_step =&nbsp;1
&nbsp; &nbsp;&nbsp;while&nbsp;h_step <&nbsp;128:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0,&nbsp;128, h_step *&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;in&nbsp;range(i, i + h_step):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; x, y = wht[j], wht[j + h_step]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; wht[j], wht[j + h_step] = x + y, x - y
&nbsp; &nbsp; &nbsp; &nbsp; h_step *=&nbsp;2
&nbsp; &nbsp;&nbsp;# 找到绝对值最大值
&nbsp; &nbsp; best_idx = max(range(128), key=lambda&nbsp;i: abs(wht[i]))
&nbsp; &nbsp; sign_bit =&nbsp;1&nbsp;if&nbsp;wht[best_idx] <&nbsp;0&nbsp;else&nbsp;0
&nbsp; &nbsp;&nbsp;return&nbsp;(sign_bit <<&nbsp;7) | best_idx

# ============================================================
# 重复 Reed-Muller 码 [384, 8, 192] = RM(1,7) × 3
# ============================================================
# 将 RM(1,7) 码重复 3 次:
# &nbsp; 编码:128 位码字复制 3 份,拼接为 384 位
# &nbsp; 解码:将 3 份 128 位段逐位取多数表决(bitwise majority vote),
# &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;再调用 RM(1,7) 解码
# 重复设计将最小距离从 64 提升至 192,代价是码率降低为 1/3

def&nbsp;dup_rm_encode(symbol: int)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""重复 RM(1,7)×3 编码:8 位符号 → 384 位码字。"""
&nbsp; &nbsp; base = rm17_encode(symbol)
&nbsp; &nbsp;&nbsp;return&nbsp;base | (base <<&nbsp;128) | (base <<&nbsp;256)

def&nbsp;dup_rm_decode(bits: int)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""重复 RM(1,7)×3 解码:384 位码字 → 8 位符号。"""
&nbsp; &nbsp; mask = (1&nbsp;<<&nbsp;128) -&nbsp;1
&nbsp; &nbsp; seg0 = bits & mask
&nbsp; &nbsp; seg1 = (bits >>&nbsp;128) & mask
&nbsp; &nbsp; seg2 = (bits >>&nbsp;256) & mask
&nbsp; &nbsp;&nbsp;# 按位多数表决
&nbsp; &nbsp; majority = (seg0 & seg1) | (seg1 & seg2) | (seg0 & seg2)
&nbsp; &nbsp;&nbsp;return&nbsp;rm17_decode(majority)

# ============================================================
# Reed-Solomon RS[46, 16, 31] over GF(2^8)
# ============================================================
# RS 码参数:
# &nbsp; 码长 &nbsp;n1 = 46,信息位 k = 16,最小距离 d = 31
# &nbsp; 纠错能力 δ = (d-1)/2 = 15 个 GF(2^8) 符号
# 系统码形式:码字 = [消息符号 | 校验符号],前 16 个符号即为原始消息
_rs_codec = reedsolo.RSCodec(nsym=PARAM_N1 - PARAM_K, fcr=1, c_exp=8) &nbsp;# 默认 GF(2^8)

def&nbsp;rs_encode(msg: bytes)&nbsp;-> List[int]:
&nbsp; &nbsp;&nbsp;"""编码:消息 -> 系统码码字(消息 + 校验)"""
&nbsp; &nbsp;&nbsp;if&nbsp;len(msg) != PARAM_K:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(f"消息长度必须为&nbsp;{PARAM_K}&nbsp;字节")
&nbsp; &nbsp; encoded_bytes = _rs_codec.encode(msg)
&nbsp; &nbsp;&nbsp;return&nbsp;list(encoded_bytes)

def&nbsp;rs_decode(symbols: List[int])&nbsp;-> bytes:
&nbsp; &nbsp;&nbsp;if&nbsp;len(symbols) != PARAM_N1:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(f"接收码字长度必须为&nbsp;{PARAM_N1}&nbsp;个符号")
&nbsp; &nbsp; received_bytes = bytes(symbols)
&nbsp; &nbsp;&nbsp;try:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 兼容不同版本 reedsolo 的返回值
&nbsp; &nbsp; &nbsp; &nbsp; decoded_bytes = _rs_codec.decode(received_bytes)[0]
&nbsp; &nbsp;&nbsp;except&nbsp;reedsolo.ReedSolomonError&nbsp;as&nbsp;e:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;raise&nbsp;ValueError(f"RS解码失败:{e}")&nbsp;from&nbsp;e
&nbsp; &nbsp;&nbsp;return&nbsp;decoded_bytes[:PARAM_K]

# ============================================================
# 级联码 Concatenated Code 编码与解码
# ============================================================
# 编码流程(外码先行):
# &nbsp; 16 字节消息
# &nbsp; → RS[46,16,31] 外码编码 → 46 个 GF(2^8) 符号
# &nbsp; → 每个符号经 dup_rm_encode → 46 × 384 = 17664 位
# &nbsp; → 嵌入 n=17669 维环 R 中
#
# 解码流程(内码先行):
# &nbsp; 17664+ 位码字
# &nbsp; → 按 384 位分组为 46 组,每组经 dup_rm_decode → 46 个 GF(2^8) 符号
# &nbsp; → RS 解码(综合征检验 + 可选 BM 纠错)→ 16 字节消息

def&nbsp;concat_encode(msg: bytes)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""级联码编码:16 字节消息 → 17664 位码字(Python 整数形式)。"""
&nbsp; &nbsp; rs_symbols = rs_encode(msg)
&nbsp; &nbsp; codeword =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;i, sym&nbsp;in&nbsp;enumerate(rs_symbols):
&nbsp; &nbsp; &nbsp; &nbsp; inner = dup_rm_encode(sym)
&nbsp; &nbsp; &nbsp; &nbsp; codeword |= (inner << (i * PARAM_N2))
&nbsp; &nbsp;&nbsp;return&nbsp;codeword

def&nbsp;concat_decode(codeword: int)&nbsp;-> bytes:
&nbsp; &nbsp;&nbsp;"""级联码解码:17664+ 位码字(Python 整数)→ 16 字节消息。"""
&nbsp; &nbsp; symbols = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(PARAM_N1):
&nbsp; &nbsp; &nbsp; &nbsp; chunk = (codeword >> (i * PARAM_N2)) & ((1&nbsp;<< PARAM_N2) -&nbsp;1)
&nbsp; &nbsp; &nbsp; &nbsp; symbols.append(dup_rm_decode(chunk))
&nbsp; &nbsp;&nbsp;return&nbsp;rs_decode(symbols)

# ============================================================
# 多项式环 R = GF(2)[X]/(X^n - 1) 上的运算
# ============================================================
# 全部公钥/密文运算均在此环上进行。
# 元素以 Python 整数表示:第 i 位对应 X^i 的系数。
# 重要性质:
# &nbsp; 加法 = XOR(GF(2) 特征为 2,加减等价)
# &nbsp; 乘法 = 循环卷积(模 X^n - 1 的多项式乘法)
# &nbsp; 公钥 s = x + h·y 的安全性依赖于求解 QCSD 问题的困难性

def&nbsp;poly_add(a: int, b: int)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;return&nbsp;a ^ b

def&nbsp;poly_mul_mod(a: int, b: int, n: int = PARAM_N)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 循环多项式乘法:(a · b) mod (X^n - 1)。
&nbsp; &nbsp; 实现策略:枚举 a 的非零位(稀疏乘法),对 b 施加相应的循环移位后 XOR 累加。
&nbsp; &nbsp; 时间复杂度 O(wt(a) · n),适合 Hamming 重量较小的向量(如私钥和随机向量)。
&nbsp; &nbsp; """
&nbsp; &nbsp; result =&nbsp;0
&nbsp; &nbsp; mask = (1&nbsp;<< n) -&nbsp;1
&nbsp; &nbsp;&nbsp;# 优化:遍历a的置位
&nbsp; &nbsp; temp = a
&nbsp; &nbsp; i =&nbsp;0
&nbsp; &nbsp;&nbsp;while&nbsp;temp:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;temp &&nbsp;1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shifted = b << i
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result ^= (shifted & mask) ^ (shifted >> n)
&nbsp; &nbsp; &nbsp; &nbsp; temp >>=&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp; i +=&nbsp;1
&nbsp; &nbsp;&nbsp;return&nbsp;result & mask

def&nbsp;poly_weight(a: int)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;return&nbsp;bin(a).count("1")

def&nbsp;poly_to_bytes(p: int)&nbsp;-> bytes:
&nbsp; &nbsp;&nbsp;return&nbsp;p.to_bytes((PARAM_N +&nbsp;7) //&nbsp;8,&nbsp;"little")

def&nbsp;poly_from_bytes(data: bytes)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;return&nbsp;int.from_bytes(data,&nbsp;"little") & ((1&nbsp;<< PARAM_N) -&nbsp;1)

# ============================================================
# 随机向量采样
# ============================================================
# 安全性要求密钥和随机向量的 Hamming 重量精确固定。
# 采样算法:从 {0,...,n-1} 中均匀无重复地抽取 w 个位置,置为 1。

def&nbsp;sample_fixed_weight(n: int, w: int, rng=None)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""采样 Hamming 重量恰好为 w 的随机二进制向量。"""
&nbsp; &nbsp;&nbsp;if&nbsp;rng&nbsp;is&nbsp;None: rng = random.SystemRandom()
&nbsp; &nbsp; positions = rng.sample(range(n), w)
&nbsp; &nbsp; result =&nbsp;0
&nbsp; &nbsp;&nbsp;for&nbsp;p&nbsp;in&nbsp;positions: result |= (1&nbsp;<< p)
&nbsp; &nbsp;&nbsp;return&nbsp;result

def&nbsp;sample_random_poly(n: int, rng=None)&nbsp;-> int:
&nbsp; &nbsp;&nbsp;"""采样均匀随机的 n 位多项式(用于公钥 h 的生成)。"""
&nbsp; &nbsp; nbytes = (n +&nbsp;7) //&nbsp;8
&nbsp; &nbsp;&nbsp;if&nbsp;rng&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; raw = os.urandom(nbytes)
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; raw = bytes(rng.getrandbits(8)&nbsp;for&nbsp;_&nbsp;in&nbsp;range(nbytes))
&nbsp; &nbsp;&nbsp;return&nbsp;int.from_bytes(raw,&nbsp;"little") & ((1&nbsp;<< n) -&nbsp;1)

# ============================================================
# 密钥生成/封装
# ============================================================

def&nbsp;keygen(seed: bytes = None)&nbsp;-> Tuple[dict, dict]:
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 密钥生成。
&nbsp; &nbsp; 公钥 pk = (h, s):h 为均匀随机多项式,s = x + h·y
&nbsp; &nbsp; 私钥 sk = (x, y):两个 Hamming 重量为 w 的低权重多项式
&nbsp; &nbsp; 安全性基于求解 2-QCSD 问题的困难性。
&nbsp; &nbsp; """
&nbsp; &nbsp;&nbsp;if&nbsp;seed:
&nbsp; &nbsp; &nbsp; &nbsp; rng = random.Random(seed)
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; rng = random.SystemRandom()
&nbsp; &nbsp; h = sample_random_poly(PARAM_N, rng)
&nbsp; &nbsp; x = sample_fixed_weight(PARAM_N, PARAM_W, rng)
&nbsp; &nbsp; y = sample_fixed_weight(PARAM_N, PARAM_W, rng)
&nbsp; &nbsp; s = poly_add(x, poly_mul_mod(h, y))
&nbsp; &nbsp;&nbsp;return&nbsp;{"h": h,&nbsp;"s": s}, {"x": x,&nbsp;"y": y}

def&nbsp;encapsulate(pk: dict, m: bytes, r1: int, r2: int, e: int):
&nbsp; &nbsp; h, s = pk["h"], pk["s"]
&nbsp; &nbsp; u = poly_add(r1, poly_mul_mod(h, r2))
&nbsp; &nbsp; v = poly_add(poly_add(concat_encode(m), poly_mul_mod(s, r2)), e)
&nbsp; &nbsp; shared_key = hashlib.sha512(m).digest()[:32]
&nbsp; &nbsp;&nbsp;return&nbsp;u, v, shared_key

相关数据

密钥封装过程中参数(数字很长,篇幅原因就不全展示了,不影响解题思路,可利用代码本地自行生成一组数据)

h
bef1c1d67d3e1e8039dcd...
s
75b9007fbaf0946a16d19...
u
14506bf660b1098c4cd35...
v
1be57933bc5febab3dc71...

题目要求

计算上述密钥封装过程中所使用的协商密钥。

题目考点

HQC 密钥封装随机向量重用

解题思路

初始谜题三 实现了HQC(Hamming Quasi-Cyclic)码基的密钥封装(赛后查的,其实我并没有见过)

但是我们能根据 keygen 和 encapsulate 函数的代码写出下面的公式

然后我们尝试调用题目提供的密钥封装服务,会发现密钥封装过程参量中的  是不变的

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
u
14506bf660b109...
v
be40236919fab1...

根据 ,也就意味着每次加密, 的值都是不变的 ( 是随机向量和误差向量)

w_r &nbsp; = 75 &nbsp; &nbsp; &nbsp;(随机向量 r1, r2 的 Hamming 重量)
w_e &nbsp; = 75 &nbsp; &nbsp; &nbsp;(误差向量 e 的 Hamming 重量)

甚至有理由怀疑  中的 e 也是不变的

于是我们设题目的共享密钥为 ,我们自己设定 ,分别进行密钥封装,则有

两式相减

注意上述的减法运算其实是GF(2) 下多项式减法,等价于加法,等价于异或(所以多项式下的 ,在转为整数后就是 )代码中的注释倒是也说的很明白了

通过本地生成密钥、加解密测试数据,我们会发现,对  做一个解码,能得到 ,不过需要注意题目中  的存储是小端序的,在将  从十六进制转为十进制时,不能直接用 int(x,16)去转。不然解码会报错

解题代码

v1 =&nbsp;''
v2 =&nbsp;''

v1 = int.from_bytes(bytes.fromhex(v1),'little')
v2 = int.from_bytes(bytes.fromhex(v2),'little')

import&nbsp;sys
sys.set_int_max_str_digits(100000000)

from&nbsp;pwn&nbsp;import&nbsp;xor
data = bytes.fromhex('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')

m = xor(concat_decode(v1^v2),data)
shared_key = hashlib.sha512(m).digest()[:32]
print(shared_key.hex())

免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:Van1sh Van1sh Van1sh《第四届熵密杯——初始谜题》

读书笔记0805 网络安全文章

读书笔记0805

文章总结: 这是一篇读书笔记,作者分享了对‘天道’的长期思考,并最终有所领悟,认为其核心可能在于十个字。笔记内容简短,未展开具体论述或提供可操作建议。 综合评分
评论:0   参与:  0