2025熵密杯 -- 初始谜题 -- Reproducibility

2025熵密杯 -- 初始谜题 -- Reproducibility

前言

本文记录2025熵密杯初始谜题赛题复现过程,参考languag3师傅的熵密杯题解博客。膜拜大佬~

https 复制代码
https://languag3.github.io/

初始谜题1

sm4_encrypt.py

python 复制代码
import 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())
复制代码
(m, c) = (6362646264646264646361636463646261636164626362646264616161636162636261616461646263626362626264636163616264626164616161616361616161636163636361626463616361626263626263646462626261616462646362626162646162646461646264626361646264636164626262626264646464626362626462626364646463636362636264616161636461626363626362616263636264636164636261646262626161636161616461616163616461626461646464616363646462636464646464636462636264636464636162616461646263646264636363616263616164646263646362646161616361646261616161626261626462636261646362626362626462636463616164636263646164636461646364646464616461626463616463636362626464646463636461626264636363636263646463626163636362646264616261636464626164646363626164626264616462626264646463646163646162626364616264646163636362646164616263616461626164636261646361636162616162646161646162636364646162626361646362646361626463616262646362636261616464626162636161616362616464616261626363616464626164616362616262616463626364646461636461636164636261626364646362626263636164626161636264646261636264626164626264636463616263646462646464636461626264626262626263626362636463636362636264646262626162626364616363636264626462636163646361636163616464626361616262646361626261646364646461636164626164646361626264616364626361636163626163636363626464626462646364646364616461616161616361646264626364636364626261636363636361616264636262646262616263636361616361616261616162636163646363646264636364616362626362626463616264646261626164646263646164646264616463646462626261616461636461626262636462646163636461646362616363616163616361626162636462616362636361646363626161636362636361616261636362636463, 308203ec30790220467b2389364b2ebd2eadfa624d668c9b0d530b89edbaf9676c1d7db18c7da40502210099f160a4fc540baa3c316e0b28db789f366fc4c84ba1a98e3aaf0806667a82d804204452c9104b87f44ac026d449cb5b5b5f306b2deab5187db7b3cb845659b1714b0410443c9a3f4f7b0083ddd323d6a1f576503082034006082a811ccf550168020410f414bdfbca9d9e902114536a3b9c443204820320d76516cc03294caaa08df866f53ac3ddc8331f5aa82c09d1ae49c41746165293d70f8d2e7578e4a6c59c9a2adb7b446fd7139e6e1d9f4fef7dca09425b574dfd30c24d773c59bcd86384f013439e7b0f61439192f9b7889a72d4fb59d20eaa7dc7191594aa5cdb855410f0b6be69cb5eb5f303a605300d48c8ca0b86549ba2c586009fa7a24e4b71a2c304aa34f9a6cb45d1e5d97e6d768dd63a1bfbe7a975be13585742aed7e3e606450530c05c0cd1c41dea44603f628bc2398cdd706abb66ca964178122b99c879bf1ded48268f662cbb1d06798e19ecb61c5719fd85b4bf72b25226778632e4510009f101a50fc66ef8f12088357ccac4749262f2eb07b6ea6d800ed3897a02d530b0e6f22b378354edddd8bf4a8df07d020986c1f49570853f4875e00fac96b2f8054288ebebb4d86ebfb644f53b1195fc2ff04effe18d419da605ba949ff7a3cc3624fdb71af173a8b8e0c721ff0095f1cfb08d1f236835013811502da499408de1ed0d329027d65b62790043f0b2f67a71f2056ca1c2e431147b145d8817b86a49d08e82c5d710e999078fa8bc1144bb77654a53d91591522540f3ac835ad1df444f94db2c07f223be6db215034ef39c2123690d0457877c40ea91f5662f2efb57cefa4d8b3bb6ce18bed2de4de3c8d53c9bd2f0be84471a88719f74a3e2193915db3c455c597e897ada24e6c3e628e03416a759ed1a7886525569a2b7e7431311504e27add6b43d85e704d364fe6446c29c314e95c498fea6b8123d811480a1add915a34d4ffeda304e0ee8456f842786a385eaac42c0beec3b37d30906a982340a1a9a0151eca11040fd2c467a8a014e2db9ccec96f0d927208de545fddd0cae8b7da3ce113d0ac95fb84ec74ed266ae8676ac662f21bb93034064a043e5969c378e825b3751d6c54e9d8d79a905c942857103564ce7ed5c61c899420d02170f42e41e3193cd5de116701c59043fc42ec596fc8cd05c75ed9fd514c9323ce01143b84080cc2d81ac477c7c2a9ef97ea0b76159fc2ea24e18c8511046f4e500d1653b16b71f0a1028155173a11c55e777dd13c23e67f9fa5e6a8c32162dcf01b09a3c50c106c63d13ce2a9e2fff014665ec490f947706092a811ccf5501831101042024d458eef08943e27b496fd180de68c18f467bee9d3c802bb56d607a364ddc70)
c = 3081e830790220467b2389364b2ebd2eadfa624d668c9b0d530b89edbaf9676c1d7db18c7da40502210099f160a4fc540baa3c316e0b28db789f366fc4c84ba1a98e3aaf0806667a82d804204452c9104b87f44ac026d449cb5b5b5f306b2deab5187db7b3cb845659b1714b0410443c9a3f4f7b0083ddd323d6a1f57650303e06082a811ccf550168020410b63a85e103d362fb6247c19e324e97c4042098fea6b8123d811480a1add915a34d4fdbbfd86515a457e1b20bf4751ea0010706092a811ccf55018311010420aab05fca300811223b3b957bfe33130770fb7a6b55b030a5809c559344f66f79

analysis

  • 基于当时现场的分析,该题目实现了当前通信中常用的数字信封,使用对称密码加密明文信息,使用非对称密码加密对称密码所使用的密钥,同时利用杂凑函数计算相应的信息摘要。体现在这道题中就是SM4-CBC加密明文信息,SM2加密SM4-CBC密钥,SM3计算消息杂凑值。

  • 本题目中将以下参数加入到asn1证书中,同时明文信息为abcd的组合。

    1. SM2的公钥以及SM2针对于SM4-CBC密钥加密后的密文
    2. SM4-CBC使用的iv以及对明文加密之后的密文
    3. SM3针对于明文进行的杂凑值
    4. 交互得到的一对(m, c)和待解密密文
  • asn1解析结果如下:

    python 复制代码
    from sm4_encrypt import asn1_parse, EnvelopedData
    
    print("#########################c1 asn_parse")
    c1 = ...
    asn1_parse(c1, EnvelopedData())
    print("#########################c asn_parse")
    c = ...
    asn1_parse(c, EnvelopedData())
    
    """
    #########################c1 asn_parse
    asn1格式的16进制字符串:
      asn1: 308203ec30790220467b2389364b2ebd2eadfa624d668c9b0d530b89edbaf9676c1d7db18c7da40502210099f160a4fc540baa3c316e0b28db789f366fc4c84ba1a98e3aaf0806667a82d804204452c9104b87f44ac026d449cb5b5b5f306b2deab5187db7b3cb845659b1714b0410443c9a3f4f7b0083ddd323d6a1f576503082034006082a811ccf550168020410f414bdfbca9d9e902114536a3b9c443204820320d76516cc03294caaa08df866f53ac3ddc8331f5aa82c09d1ae49c41746165293d70f8d2e7578e4a6c59c9a2adb7b446fd7139e6e1d9f4fef7dca09425b574dfd30c24d773c59bcd86384f013439e7b0f61439192f9b7889a72d4fb59d20eaa7dc7191594aa5cdb855410f0b6be69cb5eb5f303a605300d48c8ca0b86549ba2c586009fa7a24e4b71a2c304aa34f9a6cb45d1e5d97e6d768dd63a1bfbe7a975be13585742aed7e3e606450530c05c0cd1c41dea44603f628bc2398cdd706abb66ca964178122b99c879bf1ded48268f662cbb1d06798e19ecb61c5719fd85b4bf72b25226778632e4510009f101a50fc66ef8f12088357ccac4749262f2eb07b6ea6d800ed3897a02d530b0e6f22b378354edddd8bf4a8df07d020986c1f49570853f4875e00fac96b2f8054288ebebb4d86ebfb644f53b1195fc2ff04effe18d419da605ba949ff7a3cc3624fdb71af173a8b8e0c721ff0095f1cfb08d1f236835013811502da499408de1ed0d329027d65b62790043f0b2f67a71f2056ca1c2e431147b145d8817b86a49d08e82c5d710e999078fa8bc1144bb77654a53d91591522540f3ac835ad1df444f94db2c07f223be6db215034ef39c2123690d0457877c40ea91f5662f2efb57cefa4d8b3bb6ce18bed2de4de3c8d53c9bd2f0be84471a88719f74a3e2193915db3c455c597e897ada24e6c3e628e03416a759ed1a7886525569a2b7e7431311504e27add6b43d85e704d364fe6446c29c314e95c498fea6b8123d811480a1add915a34d4ffeda304e0ee8456f842786a385eaac42c0beec3b37d30906a982340a1a9a0151eca11040fd2c467a8a014e2db9ccec96f0d927208de545fddd0cae8b7da3ce113d0ac95fb84ec74ed266ae8676ac662f21bb93034064a043e5969c378e825b3751d6c54e9d8d79a905c942857103564ce7ed5c61c899420d02170f42e41e3193cd5de116701c59043fc42ec596fc8cd05c75ed9fd514c9323ce01143b84080cc2d81ac477c7c2a9ef97ea0b76159fc2ea24e18c8511046f4e500d1653b16b71f0a1028155173a11c55e777dd13c23e67f9fa5e6a8c32162dcf01b09a3c50c106c63d13ce2a9e2fff014665ec490f947706092a811ccf5501831101042024d458eef08943e27b496fd180de68c18f467bee9d3c802bb56d607a364ddc70
    SM2参数:
      xCoordinate: 467b2389364b2ebd2eadfa624d668c9b0d530b89edbaf9676c1d7db18c7da405
      yCoordinate: 99f160a4fc540baa3c316e0b28db789f366fc4c84ba1a98e3aaf0806667a82d8
      hash: 4452c9104b87f44ac026d449cb5b5b5f306b2deab5187db7b3cb845659b1714b
      cipherText: 443c9a3f4f7b0083ddd323d6a1f57650
    SM4参数:
      algorithm: 1.2.156.10197.1.104.2
      iv: f414bdfbca9d9e902114536a3b9c4432
      cipherText: d76516cc03294caaa08df866f53ac3ddc8331f5aa82c09d1ae49c41746165293d70f8d2e7578e4a6c59c9a2adb7b446fd7139e6e1d9f4fef7dca09425b574dfd30c24d773c59bcd86384f013439e7b0f61439192f9b7889a72d4fb59d20eaa7dc7191594aa5cdb855410f0b6be69cb5eb5f303a605300d48c8ca0b86549ba2c586009fa7a24e4b71a2c304aa34f9a6cb45d1e5d97e6d768dd63a1bfbe7a975be13585742aed7e3e606450530c05c0cd1c41dea44603f628bc2398cdd706abb66ca964178122b99c879bf1ded48268f662cbb1d06798e19ecb61c5719fd85b4bf72b25226778632e4510009f101a50fc66ef8f12088357ccac4749262f2eb07b6ea6d800ed3897a02d530b0e6f22b378354edddd8bf4a8df07d020986c1f49570853f4875e00fac96b2f8054288ebebb4d86ebfb644f53b1195fc2ff04effe18d419da605ba949ff7a3cc3624fdb71af173a8b8e0c721ff0095f1cfb08d1f236835013811502da499408de1ed0d329027d65b62790043f0b2f67a71f2056ca1c2e431147b145d8817b86a49d08e82c5d710e999078fa8bc1144bb77654a53d91591522540f3ac835ad1df444f94db2c07f223be6db215034ef39c2123690d0457877c40ea91f5662f2efb57cefa4d8b3bb6ce18bed2de4de3c8d53c9bd2f0be84471a88719f74a3e2193915db3c455c597e897ada24e6c3e628e03416a759ed1a7886525569a2b7e7431311504e27add6b43d85e704d364fe6446c29c314e95c498fea6b8123d811480a1add915a34d4ffeda304e0ee8456f842786a385eaac42c0beec3b37d30906a982340a1a9a0151eca11040fd2c467a8a014e2db9ccec96f0d927208de545fddd0cae8b7da3ce113d0ac95fb84ec74ed266ae8676ac662f21bb93034064a043e5969c378e825b3751d6c54e9d8d79a905c942857103564ce7ed5c61c899420d02170f42e41e3193cd5de116701c59043fc42ec596fc8cd05c75ed9fd514c9323ce01143b84080cc2d81ac477c7c2a9ef97ea0b76159fc2ea24e18c8511046f4e500d1653b16b71f0a1028155173a11c55e777dd13c23e67f9fa5e6a8c32162dcf01b09a3c50c106c63d13ce2a9e2fff014665ec490f9477
    SM3参数:
      digestAlgorithm: 1.2.156.10197.1.401.1
      digest: 24d458eef08943e27b496fd180de68c18f467bee9d3c802bb56d607a364ddc70
    #########################c asn_parse
    asn1格式的16进制字符串:
      asn1: 3081e830790220467b2389364b2ebd2eadfa624d668c9b0d530b89edbaf9676c1d7db18c7da40502210099f160a4fc540baa3c316e0b28db789f366fc4c84ba1a98e3aaf0806667a82d804204452c9104b87f44ac026d449cb5b5b5f306b2deab5187db7b3cb845659b1714b0410443c9a3f4f7b0083ddd323d6a1f57650303e06082a811ccf550168020410b63a85e103d362fb6247c19e324e97c4042098fea6b8123d811480a1add915a34d4fdbbfd86515a457e1b20bf4751ea0010706092a811ccf55018311010420aab05fca300811223b3b957bfe33130770fb7a6b55b030a5809c559344f66f79
    SM2参数:
      xCoordinate: 467b2389364b2ebd2eadfa624d668c9b0d530b89edbaf9676c1d7db18c7da405
      yCoordinate: 99f160a4fc540baa3c316e0b28db789f366fc4c84ba1a98e3aaf0806667a82d8
      hash: 4452c9104b87f44ac026d449cb5b5b5f306b2deab5187db7b3cb845659b1714b
      cipherText: 443c9a3f4f7b0083ddd323d6a1f57650
    SM4参数:
      algorithm: 1.2.156.10197.1.104.2
      iv: b63a85e103d362fb6247c19e324e97c4
      cipherText: 98fea6b8123d811480a1add915a34d4fdbbfd86515a457e1b20bf4751ea00107
    SM3参数:
      digestAlgorithm: 1.2.156.10197.1.401.1
      digest: aab05fca300811223b3b957bfe33130770fb7a6b55b030a5809c559344f66f79
    """
  • 针对于此等加密,刚学完课程《密码学概论》的我,当前安全且高效的通信加密技术,赛场上看着这道题非常怅惘。后续看到大佬的解题思路如下:针对于国密算法以及数字信封的实现上而言,我们很难找到其漏洞点,但是出了穷举攻击以外,可能出现的漏洞点就在于分组密码的CBC工作模式。由于给出的明密文对的长度很长,而我们要恢复的密文却很短。cipherText = "98fea6b8123d811480a1add915a34d4fdbbfd86515a457e1b20bf4751ea00107"

  • 看到languag3师傅的博客,同时验证待解密密文的第一组密文在明密文对的密文中存在,那么可以判断此处在黑盒处理之前,两者对应的分组的keyinput是相同的,则通过异或处理可以恢复明文的第一块。但是并不能推出该组黑盒使用的key以及其他分组的keyinput的关系。

  • 此后怎对于后续的16字节进行爆破,即为32bit的abcd爆破。

exp

python 复制代码
# part1 Solve
from pwn import xor
m1 = "6362646264646264646361636463646261636164626362646264616161636162636261616461646263626362626264636163616264626164616161616361616161636163636361626463616361626263626263646462626261616462646362626162646162646461646264626361646264636164626262626264646464626362626462626364646463636362636264616161636461626363626362616263636264636164636261646262626161636161616461616163616461626461646464616363646462636464646464636462636264636464636162616461646263646264636363616263616164646263646362646161616361646261616161626261626462636261646362626362626462636463616164636263646164636461646364646464616461626463616463636362626464646463636461626264636363636263646463626163636362646264616261636464626164646363626164626264616462626264646463646163646162626364616264646163636362646164616263616461626164636261646361636162616162646161646162636364646162626361646362646361626463616262646362636261616464626162636161616362616464616261626363616464626164616362616262616463626364646461636461636164636261626364646362626263636164626161636264646261636264626164626264636463616263646462646464636461626264626262626263626362636463636362636264646262626162626364616363636264626462636163646361636163616464626361616262646361626261646364646461636164626164646361626264616364626361636163626163636363626464626462646364646364616461616161616361646264626364636364626261636363636361616264636262646262616263636361616361616261616162636163646363646264636364616362626362626463616264646261626164646263646164646264616463646462626261616461636461626262636462646163636461646362616363616163616361626162636462616362636361646363626161636362636361616261636362636463"
c1 = "d76516cc03294caaa08df866f53ac3ddc8331f5aa82c09d1ae49c41746165293d70f8d2e7578e4a6c59c9a2adb7b446fd7139e6e1d9f4fef7dca09425b574dfd30c24d773c59bcd86384f013439e7b0f61439192f9b7889a72d4fb59d20eaa7dc7191594aa5cdb855410f0b6be69cb5eb5f303a605300d48c8ca0b86549ba2c586009fa7a24e4b71a2c304aa34f9a6cb45d1e5d97e6d768dd63a1bfbe7a975be13585742aed7e3e606450530c05c0cd1c41dea44603f628bc2398cdd706abb66ca964178122b99c879bf1ded48268f662cbb1d06798e19ecb61c5719fd85b4bf72b25226778632e4510009f101a50fc66ef8f12088357ccac4749262f2eb07b6ea6d800ed3897a02d530b0e6f22b378354edddd8bf4a8df07d020986c1f49570853f4875e00fac96b2f8054288ebebb4d86ebfb644f53b1195fc2ff04effe18d419da605ba949ff7a3cc3624fdb71af173a8b8e0c721ff0095f1cfb08d1f236835013811502da499408de1ed0d329027d65b62790043f0b2f67a71f2056ca1c2e431147b145d8817b86a49d08e82c5d710e999078fa8bc1144bb77654a53d91591522540f3ac835ad1df444f94db2c07f223be6db215034ef39c2123690d0457877c40ea91f5662f2efb57cefa4d8b3bb6ce18bed2de4de3c8d53c9bd2f0be84471a88719f74a3e2193915db3c455c597e897ada24e6c3e628e03416a759ed1a7886525569a2b7e7431311504e27add6b43d85e704d364fe6446c29c314e95c498fea6b8123d811480a1add915a34d4ffeda304e0ee8456f842786a385eaac42c0beec3b37d30906a982340a1a9a0151eca11040fd2c467a8a014e2db9ccec96f0d927208de545fddd0cae8b7da3ce113d0ac95fb84ec74ed266ae8676ac662f21bb93034064a043e5969c378e825b3751d6c54e9d8d79a905c942857103564ce7ed5c61c899420d02170f42e41e3193cd5de116701c59043fc42ec596fc8cd05c75ed9fd514c9323ce01143b84080cc2d81ac477c7c2a9ef97ea0b76159fc2ea24e18c8511046f4e500d1653b16b71f0a1028155173a11c55e777dd13c23e67f9fa5e6a8c32162dcf01b09a3c50c106c63d13ce2a9e2fff014665ec490f9477"
c = "98fea6b8123d811480a1add915a34d4fdbbfd86515a457e1b20bf4751ea00107"
iv1 = "f414bdfbca9d9e902114536a3b9c4432"
iv = "b63a85e103d362fb6247c19e324e97c4"
# print(len(c)) 64 -- 分组后为2组
c_list = [c[32 * i:32 * (i + 1)] for i in range(2)]
# print(c_list)
# print(c1.index(c_list[0])); print(c1.index(c_list[1])) 1088 Error
# Black Cipher Encrypt之前的结果相同
temp = bytes.fromhex(m1[1088:1088 + 32])
c1_block = bytes.fromhex(c1[1088 - 32:1088])
iv = bytes.fromhex(iv)
m_part1 = xor(xor(c1_block, temp), iv)
# print(m_part1) adcddbbadcacabad
c++ 复制代码
// part2 Solve
#include <iostream>
#include <vector>
#include <string>
#include <thread>
#include <chrono>
#include <cstring>
#include <openssl/evp.h>
#include <mutex>
#include <condition_variable>

using namespace std;

// 线程通信全局变量
bool found = false;
mutex mtx; // 信号量
condition_variable cv;

// 字符集以及前16个字节内容固定
const unsigned char char_set[] = "adcddbbadcacabad";
const unsigned char char_map[] = "abcd";
const string tar_hex = "aab05fca300811223b3b957bfe33130770fb7a6b55b030a5809c559344f66f79";

// 生成索引对应的16字节字符
void index_to_char(uint64_t index, unsigned char *result)
{
    int len = 0;
    while (len < 15)
    {
        result[len++] = char_map[index % 4];
        index /= 4;
    }
    result[len] = char_map[index];
}

// 有结果后终止线程
bool should_terminate()
{
    lock_guard<mutex> lock(mtx);
    return found;
}

// 线程
void thread_worker(uint64_t j)
{
    const size_t prefix_len = strlen(reinterpret_cast<const char *>(char_set));
    vector<unsigned char> data(prefix_len + 16);
    memcpy(data.data(), char_set, prefix_len);

    unsigned char hash[32];
    char hash_hex[65];
    hash_hex[64] = '\0';

    for (uint64_t i = 0; i <= 0xffffff; ++i)
    {
        if (should_terminate())
        {
            return;
        }

        const uint64_t index = (j << 24) + i;
        unsigned char suffix[16];

        index_to_char(index, suffix);
        memcpy(data.data() + prefix_len, suffix, 16);

        // 计算SM3哈希
        EVP_MD_CTX *ctx = EVP_MD_CTX_new();
        EVP_DigestInit_ex(ctx, EVP_sm3(), nullptr);
        EVP_DigestUpdate(ctx, data.data(), data.size());
        EVP_DigestFinal_ex(ctx, hash, nullptr);
        EVP_MD_CTX_free(ctx);

        // 转换哈希结果为十六进制字符串
        for (int k = 0; k < 32; ++k)
        {
            snprintf(hash_hex + 2 * k, 3, "%02x", hash[k]);
        }

        // 检查是否匹配目标哈希
        if (tar_hex == hash_hex)
        {
            // 找到匹配项,设置全局标志并通知主线程
            {
                lock_guard<mutex> lock(mtx);
                found = true;
                cout << "Found: " << string(reinterpret_cast<char *>(data.data()), data.size()) << endl;
            }
            cv.notify_one(); // 通知主线程
            return;
        }
    }
}

int main()
{
    auto start_time = chrono::high_resolution_clock::now();

    vector<thread> threads;
    threads.reserve(256);

    // 创建线程
    for (uint64_t j = 0; j <= 0xff; ++j)
    {
        threads.emplace_back(thread_worker, j);
    }

    // 等待找到结果或所有线程完成
    unique_lock<mutex> lock(mtx);
    cv.wait(lock, []
            { return found; });

    // 找到结果后终止线程
    for (auto &t : threads)
    {
        if (t.joinable())
        {
            t.join();
        }
    }

    // 计算并输出耗时
    auto end_time = chrono::high_resolution_clock::now();
    chrono::duration<double> elapsed = end_time - start_time;
    cout << "Time taken: " << elapsed.count() << " seconds" << endl;

    return 0;
}
// flag{adcddbbadcacabadcbbbbaaaabcbcabd}

初始谜题2

sm2_verify

python 复制代码
import 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))

    @staticmethod
    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())

    @staticmethod
    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

    @staticmethod
    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)}")

    @staticmethod
    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

复制代码
308202CD02010106092A811CCF5501831101304502206F8EAFF551D0F3FA6DE74B75B33E1E58F9FDB4DC58E61C82E11E717FFCF168C4022100DB3D5A90FF3625D12B8B658F8DBAB34340C278B412B3AFF25489E7FEB1C755983081E0020101302C06072A8648CE3D0101022100FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF30440420FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC042028E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E9304410432C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0022100FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123020101048201803082017C30820122A003020102020D00947C8427D3E849B48A7E5136300A06082A811CCF550183753036310B300906035504061302434E31133011060355040A130A5368616E674D6942656931123010060355040313095368616E674D694341301E170D3235303532303035353330365A170D3330303531393035353330365A304D310B300906035504061302434E3110300E060355040A1307496E746572434131173015060355040B130E5368616E674D6942656932303235311330110603550403130A7368616E676D696265693059301306072A8648CE3D020106082A811CCF5501822D03420004CECC0005AED684A1E7E39C316E7F3F39BDD0490936BC0E1AFDDC1B9627A05B4418809E5327746EE1977913F036EF0A9A255C27D73C00E45D0BB205B34D2C80D4300A06082A811CCF5501837503480030450220360779CBF5AA6E5E9CC073D95E22C52C09E81CFC06A3916559063A3C8C1DFDE6022100ED0E5E5E51F3894A3EAC11F247739D9F6A88C961D89F68337972BC3CC6BB6706180F32303235303532303130313030305A

analysis

  • 题目中实现了一个SM2的签名系统,而我们获取得到flag的关键就在于对指定信息进行签名通过交互验签程序的比对。因此task就转移到我们怎么写一个能通过验签函数的签名函数。注:证书为固定内容,也就意味着公钥固定不能进行修改。

  • 针对于签名函数而言,我们查看gmssl.sm2源码之后发现,签名需要找到私钥,但是目前我们只知道公钥以及其签名时使用的基点G以及经过私钥计算之后的公钥,根据sm2的签名过程中的密钥生成方式:

    \[确定a,b,p后选择点P作为基点,选择d且计算Q=d*P,其中d为私钥,P为公钥。 \]

  • 因此我们自己伪造私钥即可,但是需要满足私钥和公钥的关系,那么我们在曲线上找到我们所选择的私钥d的逆元点即可进行签名验证。

exp

python 复制代码
# 伪造私钥,确定相应私钥的基点
from sage.all import *

curve_params = {
    "n": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',
    "p": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
    "a": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
    "b": '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
}

pk = "8E1860588D9900C16BD19A0FE0A5ACC600224DBD794FFD34179E03698D52421F46E6D8C6E8AADE512C7B543395AC39C76384726C7F8BA537ABCA0C129ECD9882"

p = int(curve_params['p'], 16)
a = int(curve_params['a'], 16)
b = int(curve_params['b'], 16)
n = int(curve_params['n'], 16)
E = EllipticCurve(GF(p), [a, b])

Q = E(int(pk[:64], 16), int(pk[64:], 16))

sk = 2
G = Q * inverse_mod(sk, n)

print(G)
# (69820663585833773923605819432869967907739933733211104912986148573209369246123 : 76928629769251499992251494960564507419350106546363535365017317636758007136702 : 1)
python 复制代码
# 利用伪造私钥和基点仿造签名
from gmssl.sm2 import CryptSM2

curve_params = {
    "n": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123',
    "p": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
    "g": "9a5d185c2d305a22641a68c2c637924ae27921e02019213d08788bffa8bff9abaa140fbb0a66d0166463e801113b9ef38198f2918e053902bb67b018476341be",
    "a": 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
    "b": '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
}

sm2_crypt = CryptSM2(private_key = "2", public_key = "cecc0005aed684a1e7e39c316e7f3f39bdd0490936bc0e1afddc1b9627a05b4418809e5327746ee1977913f036ef0a9a255c27d73c00e45d0bb205b34d2c80d4", ecc_table=curve_params)

data = b'EUWJSFTFHGQEQVRXZHJYKPUXDPUMRDQQ'
signature = sm2_crypt.sign_with_sm3(data)
print(f"signature: {signature}")
# signature: c0f36fe86a81a061139252f518b51651397d517799f30e3dece0acb762f497c3693297c721cbb0d778856e2c2b4cea9cfe0af6a4548f4c29be03688f071fe4f6

初始谜题3

xmss_verify.py

python 复制代码
from 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

    @staticmethod
    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)))

        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)

analysis

  • 针对于这道题而言,从wots.verify一步步跟进发现这道题,给出公钥,输入信息摘要以及其针对于该信息的签名值。
  • 跟进get_pubkey_from_signature发现该签名的流程是对sig_part进行x次的哈希,返回的结果作为签名值。这里考虑到哈希函数的不可逆性,所以我们就要把思路转换到恶意伪造签名上而非进行由公钥进行求解私钥再进行计算。
  • 随之发现其中_chain操作的次数与所传入的参数信息摘要的每一位有关,可以输出其中的参数msg_to_verify进行验证。
  • 因此,我们可以恶意伪造信息摘要为ffffffff,使其_chain操作次数均为0,那么签名值就是未经操作的公钥,伪造成功交互获取flag即可。
  • 看到languag3师傅的wp才知晓该签名验签程序为OTS签名,更能明白些许原理以及这样考虑的思路,更具指导性。

写在最后

  • 第一次参加熵密杯,题目质量很高,比起以往的CTF中的密码学方向的题目而言,课本上的知识得到了时间并且针对于加密算法的漏洞攻击而言,更加使用的签名和实用化的场景下进行的题目更加具有实际意义。
  • 本次比赛只解出了初始谜题3,第一次使用gmsll库,发现了该库简直是密码学应用的天然宝库。但是由于自己太菜了,没有享受到密码学的快乐,坐牢ed.
  • 因为比赛时全程都在看初始谜题,flag也因为实力太弱没能求解,同时对于Go的审计能力几乎为0,后续题解就不再记录,留个坑。还得xue...