2026软件系统安全赛初赛RSA(赛后复盘)

文章目录

解题

拿到附件,先解压后有一个 level1 目录和一个 level2.zip。

README.md

level2.zip 密码藏在多个message里面

我把多个message通过 Asmuth-Bloom 秘密共享保存

然后把Asmuth-Bloom 秘密共享的内容通过RSA加密保管起来,这下只有我才能知道message是什么了吧

Level 1:RSA 多攻击 + 秘密恢复

encrypt.py加密逻辑

先在encrypt.py中查看加密逻辑:

python 复制代码
def get_rand_bytes(length):
    return bytes([random.randrange(256) for _ in range(length)])

def encrypt(public_key, message):
    if isinstance(message, str):
        message = message.encode('utf-8')
    
    n = public_key.n
    n_bits = n.bit_length()
    
    if n_bits >= 2048:
        key_len = (n.bit_length() + 7) // 8
        symmetric_key = get_rand_bytes(32)
        msg_header = PKCS1_OAEP.new(public_key).encrypt(symmetric_key)
        nonce = get_rand_bytes(12)
        cipher = AES.new(symmetric_key, mode=AES.MODE_GCM, nonce=nonce)
        msg_body, tag = cipher.encrypt_and_digest(message)
        return msg_header + nonce + msg_body + tag
    else:
        symmetric_key = get_rand_bytes(16)
        key_int = bytes_to_long(symmetric_key)
        
        if key_int >= n:
            while key_int >= n:
                symmetric_key = get_rand_bytes(16)
                key_int = bytes_to_long(symmetric_key)
        
        encrypted_key_int = pow(key_int, public_key.e, n)
        
        key_len = (n.bit_length() + 7) // 8
        msg_header = long_to_bytes(encrypted_key_int, key_len)
        
        nonce = get_rand_bytes(12)
        cipher = AES.new(symmetric_key, mode=AES.MODE_GCM, nonce=nonce)
        msg_body, tag = cipher.encrypt_and_digest(message)
        
        return msg_header + nonce + msg_body + tag

笔者这里简要介绍一下脚本的加密逻辑。

  • 随机字节生成函数
python 复制代码
def get_rand_bytes(length):
    return bytes([random.randrange(256) for _ in range(length)])

这里有个问题,random 模块使用 Mersenne Twister 算法,状态可预测。

  • 主加密函数定义
python 复制代码
def encrypt(public_key, message):
    if isinstance(message, str):
        message = message.encode('utf-8')

使用 UTF-8 编码将字符串转为字节串。

  • 获取 RSA 参数
python 复制代码
	n = public_key.n
	n_bits = n.bit_length()

public_key.n是RSA 公钥的模数(modulus),即两个大素数的乘积。用于判断密钥长度、计算加密后数据长度。
bit_length()返回整数 n 的二进制位数,用来判断密钥强度,决定使用哪种加密分支(≥2048 位走安全分支)。

  • 分支一:大密钥(≥2048 位)加密逻辑
python 复制代码
    if n_bits >= 2048:
        key_len = (n.bit_length() + 7) // 8
        symmetric_key = get_rand_bytes(32)
        msg_header = PKCS1_OAEP.new(public_key).encrypt(symmetric_key)
        nonce = get_rand_bytes(12)
        cipher = AES.new(symmetric_key, mode=AES.MODE_GCM, nonce=nonce)
        msg_body, tag = cipher.encrypt_and_digest(message)
        return msg_header + nonce + msg_body + tag

用RSA加密AES的密钥,用AES加密实际数据。
symmetric_key = get_rand_bytes(32) AES-256 是当前推荐强度。用来确认密钥足够安全。
key_len是字节长度。
mode=AES.MODE_GCM是Galois/Counter Mode的意思。提供认证加密。

msg_body是加密后的密文,tag是16 字节认证标签。

  • 分支二:小密钥(<2048 位)加密逻辑
python 复制代码
    else:
        symmetric_key = get_rand_bytes(16)
        key_int = bytes_to_long(symmetric_key)
        
        if key_int >= n:
            while key_int >= n:
                symmetric_key = get_rand_bytes(16)
                key_int = bytes_to_long(symmetric_key)
        
        encrypted_key_int = pow(key_int, public_key.e, n)
        
        key_len = (n.bit_length() + 7) // 8
        msg_header = long_to_bytes(encrypted_key_int, key_len)
        
        nonce = get_rand_bytes(12)
        cipher = AES.new(symmetric_key, mode=AES.MODE_GCM, nonce=nonce)
        msg_body, tag = cipher.encrypt_and_digest(message)
        
        return msg_header + nonce + msg_body + tag

为什么有这个分支?当RSA密钥强度不足时(<2048位),不能使用PKCS1_OAEP(需要至少2048位才能安全),只能用更基础的方法。

使用AES-128,RSA加密要求:明文 < 模数n。但这个检查实际意义不大,密钥总是小于n(比如1024位≈128字节)。

python 复制代码
pow(base, exponent, modulus) = base^exponent mod modulus

原始RSA:直接对整数进行模幂运算。

其实就是:

python 复制代码
OAEP 路径:PKCS1_OAEP 加密 32 字节对称密钥,再用 AES-GCM 加密明文
RAW 路径:直接用 pow(key_int, e, n) 加密 16 字节对称密钥
generate-plaintexts.py明文生成逻辑
python 复制代码
class AsmuthBloomSecretSharer:
    @staticmethod
    def split_secret(secret, k, n):

        if isinstance(secret, str):
            secret_bytes = secret.encode('utf-8')
        else:
            secret_bytes = secret
        S = int.from_bytes(secret_bytes, 'big')
        secret_bits = S.bit_length()
        original_bits = len(secret_bytes) * 8
        
        d = []
        base_bits = max(64, (secret_bits * 2) // k)  
        
        for i in range(n):
            bits = base_bits + i * 10
            attempts = 0
            while attempts < 100:
                p = getPrime(bits)
                if not d or p > d[-1]:
                    d.append(p)
                    break
                attempts += 1
            if attempts >= 100:
                bits += 50
                p = getPrime(bits)
                d.append(p)
        
        shares = []
        for i, di in enumerate(d):
            ki = S % di
            share_str = f"{di:x}:{ki:x}:{original_bits:x}"
            shares.append(share_str)
        
        return shares

def main():
    with open('message1.txt', 'r') as f:
        PLAINTEXTS = [f.read()] * 10

    for i in range(2, 11):
        with open(f'message{i}.txt', 'r') as f:
            msg = f.read().strip()
        
        k = i 
        
        shares = AsmuthBloomSecretSharer.split_secret(msg, k, 10)
        for j in range(10):
            PLAINTEXTS[j] += shares[j] + '\n'

    for j in range(10):
        with open(f'plaintext-{j+1}.txt', 'w') as f:
            f.write(PLAINTEXTS[j])

这段代码 把 9 条秘密消息(message2~message10)分别用 Asmuth-Bloom 门限秘密共享拆成 10 份分片,然后把所有分片塞进 10 个明文文件(plaintext-1.txt ~ plaintext-10.txt) 里。

题目里的分片核心:

python 复制代码
di:ki:original_bits

di = 模数

ki = S(秘密) mod di

original_bits = 原始秘密比特长度

攻击

直接写脚本开始分析:

先做参数分析:

python 复制代码
import os
import math
from Crypto.PublicKey import RSA

BASE = "./"

print("分析公钥")
keys = {}
for fname in os.listdir(BASE):
    if fname.startswith("key-") and fname.endswith(".pem"):
        idx = int(fname.split("-")[1].split(".")[0])
        path = os.path.join(BASE,fname)

        with open(path,"rb") as f:
            key = RSA.import_key(f.read())

        n = key.n
        e = key.e
        bits = n.bit_length()

        keys[idx] = (n,e,bits)
        print(f"key-{idx} | bits={bits} | e={e}")

得到:

python 复制代码
分析公钥
key-0 | bits=4096 | e=65537
key-1 | bits=2048 | e=65537
key-10 | bits=4095 | e=65537
key-11 | bits=4096 | e=65537
key-12 | bits=2047 | e=10795669808500986839867978007354136033507383308060972092210982356223464282434483873916345149633289094224982075054975506961595118494936774031612163932067464724146385088222048297291291838370153653739834018052640636234900232987834564911302987519049227599490825034158719419542846516048278161120251550215203309987369940813168575597738178213209337773065080822320130737915747222394462574964777484903041979462204703443283401793237450938707033792308549290798269212824655826300120509929022910960111904354845086275135223400761734163645243648958437716056769886766476544087693535237383722169785378759046439621215416219137098226401
key-13 | bits=2048 | e=65537
key-14 | bits=4095 | e=65537
key-15 | bits=2565 | e=65537
key-16 | bits=2048 | e=65537
key-17 | bits=2047 | e=7045617919508503170913299569224871439909404012613583574625901164191991675287796266273854166734612476746839074988423369131303912465169569996213125401452204398207583552623411893615911561297340154694892150867912144472312418456278578885641614228872026814049608407543779079104736417611808354945902908385808197772549991010971137879535391118955616187540872793929604490558314574976087311433696649912152014920759633908135493994218657596313100339159376716099133909454527659600279677892664553325955149256122036346197830516098862994316618871518132525756016462393107582116150670162194922298893374675588103906585714353196651454809
key-18 | bits=2048 | e=65537
key-19 | bits=4096 | e=65537
key-2 | bits=2047 | e=65537
key-3 | bits=2047 | e=65537
key-4 | bits=2566 | e=65537
key-5 | bits=198 | e=3
key-6 | bits=2047 | e=2607110783380146140775808743336565927137709488187554446282139965019365979854323549373285865339229187318583333223090907201019645685597438130562576325073282626054328187320861773442259120660133247368731272591384198837766109000234459551122661027003249828284336275155300357461650613460368038036603123137963524563641328181905359218505337225310513029253193946377051675307125514178979858274411493746042329604325294673942979542592215241185358980747617726951553875754374844790186024251905912877495475003795787350176930606949634589871021747898463278985835221430755306575680124745409948264933489347043556454636724961161574939033
key-7 | bits=2047 | e=65537
key-8 | bits=2048 | e=65537
key-9 | bits=4095 | e=65537

可以发现key-6, key-12, key-17,e 极大。适合 Wiener 攻击。

接着,自动寻找 GCD 共因子,暴力检查所有密钥对,看是否共享同一个素因子 p。

python 复制代码
gcd_pairs = []
for i in keys:
    for j in keys:
        if i<j:
            n1 = keys[i][0]
            n2 = keys[j][0]
            g = math.gcd(n1,n2)
            if g>1:
                print(f"发现共因子:key-{i} <-> key-{j}")

得到:

可以直接破解这些密钥。

接下来是Fermat 攻击可能性。但是笔者写的脚本识别所有公钥都可能适合Fermat 攻击,就不在这里放代码了。

最后分析密文结构。

python 复制代码
for fname in os.listdir(BASE):
    if fname.startswith("ciphertext-") and fname.endswith(".bin"):
        size = os.path.getsize(os.path.join(BASE, fname))
        print(f"{fname} | 大小 = {size}字节")

这里过了一遍,就是正常的攻击了。上面的内容主要介绍一下原理和大致的思路。

正常攻击方法:

GCD 攻击

python 复制代码
import math
p = math.gcd(n1, n2)  
q1 = n1 // p        
q2 = n2 // p         
d1 = pow(e, -1, (p-1)*(q1-1))

Fermat 分解

python 复制代码
a = math.isqrt(n) + 1
b2 = a*a - n
while math.isqrt(b2)**2 != b2:
    b2 += 2*a + 1
    a += 1
p, q = a - math.isqrt(b2), a + math.isqrt(b2)

Wiener 攻击

python 复制代码
for (k, d) in convergents(continued_fraction(e, n)):
    if (e*d - 1) % k != 0: continue
    phi = (e*d - 1) // k
    disc = (n - phi + 1)**2 - 4*n
    if is_perfect_square(disc):
        p, q = ...   # 成功分解

获得私钥后,根据密钥位数选择对应的解密路径:

  • OAEP 路径(n_bits >= 2048): header = PKCS1_OAEP.decrypt(...)
  • RAW 路径(n_bits < 2048): key_int = pow(enc_int, d, n); sym = key_int.to_bytes(16,'big')
    最终对应关系(密文编号 → 密钥编号 → 解密路径):
密文 密钥 攻击方式 路径
ciphertext-1 key-3 Fermat 分解 RAW
ciphertext-2 key-6 Wiener 攻击 RAW
ciphertext-3 key-4 GCD 攻击 OAEP
ciphertext-4 key-7 Fermat 分解 RAW
ciphertext-5 key-17 Wiener 攻击 RAW
ciphertext-8 key-1 GCD 攻击 OAEP
ciphertext-9 key-18 Fermat 分解 OAEP

Asmuth-Bloom 秘密恢复(CRT)

每条明文包含各消息的一个 share,格式为 di:ki:original_bits。成功解密 7 条明文后,利用中国剩余定理(CRT)恢复各 message:

python 复制代码
from functools import reduce

def crt_recover(shares):
    # shares = [(di, ki), ...]
    M = reduce(lambda a, b: a*b, [d for d, _ in shares])
    S = 0
    for di, ki in shares:
        Mi = M // di
        yi = pow(Mi, -1, di)
        S += ki * Mi * yi
    return S % M

我用自制的一个小工具解题。

这里提供给读者参考:
通用RSA密码学攻击工具箱

python 复制代码
gitcode链接:https://gitcode.com/2401_88644935/rsa_general_toolbox

直接跑出来了。

得到:9Zr4M1ThwVCHe4nHnmOcilJ8

用这个密码解压 level2.zip:

Level 2:已知明文 + 小私钥 d 恢复

打开py文件。

python 复制代码
import gmpy2
import hashlib
from Crypto.Util.number import *

def generate_polynomial_coefficients(degree, bound=100):
    coefficients = []
    for i in range(degree + 1):
        coefficients.append(getRandomRange(-bound, bound))
    while coefficients[-1] == 0:
        coefficients[-1] = getRandomRange(-bound, bound)
    return coefficients

def evaluate_polynomial(coeffs, x, n):
    result = 0
    power = 1
    for i, coeff in enumerate(coeffs):
        result = (result + coeff * power) % n
        power = (power * x) % n
    return result

p = getPrime(512)
q = getPrime(512)
d = getPrime(180)  

lam = (p-1) * (q-1) // gmpy2.gcd(p-1, q-1)

e = gmpy2.invert(d, lam)

n = p * q
m1 = bytes_to_long(b"Secret message: " + b"A" * 16)

poly2_coeffs = generate_polynomial_coefficients(2)
poly3_coeffs = generate_polynomial_coefficients(3)

m2 = evaluate_polynomial(poly2_coeffs, m1, n)
m3 = evaluate_polynomial(poly3_coeffs, m1, n)

c1 = pow(m1, e, n)
c2 = pow(m2, e, n)
c3 = pow(m3, e, n)

flag_hash = hashlib.sha256(str(p + q).encode()).hexdigest()
flag = f"next pass is {flag_hash}"

poly2_coeffs_display = poly2_coeffs.copy()
poly2_coeffs_display[0] = "?"  
poly3_coeffs_display = poly3_coeffs.copy()
poly3_coeffs_display[0] = "?"  

print("n =", n)
print("e =", e)
print("c1 =", c1)
print("c2 =", c2)
print("c3 =", c3)
print("poly2_coeffs =", poly2_coeffs_display)  
print("poly3_coeffs =", poly3_coeffs_display)  

# n = 99573363048275234764231402769464116416087010014992319221201093905687439933632430466067992037046120712199565250482197004301343341960655357944577330885470918466007730570718648025143561656395751518428630742587023267450633824636936953524868735263666089452348466018195099471535823969365007120680546592999022195781
# e = 12076830539295193533033212232487568888200963123024189287629493480058638222146972496110814372883829765692623107191129306190788976704250502316265439996891764101447017190377014980293589797403095249538391534986638973035285900867548420192211241163778919028921502305790979880346050428839102874086046622833211913299
# c1 = 88537483899519116785221065592618063396859368769048931371104532271282451393564912999388648867349770059882231896252136530442609316120059139869000411598215669228402275014417736389191093818032356471508269901358077592526362193180661405990147957408129845474938259771860341576649904811782733150222504695142224907008
# c2 = 94664119872856479106437852390497826718494891787231788048637569911820802241271385500237521849783257742020074596801243551199558780516078698265978603364906102405513805258523893161481880920117078266554039648951735640682344760175446065823258414274759467738667667682764189886842676476993302026419523097311062869020
# c3 = 17452842791128500883484238194649202112170709841838938382334668085116735124742824096780522905501631340623564870573855325492444043679562915249332054460794149403631363005265204604824764244033832504758820981926933304283896441280291037649477532990161765794023799333588888734136298348520144818810380374971149851767
# poly2_coeffs = ['?', -36, -94]
# poly3_coeffs = ['?', -11, -20, 11]

task.py 生成一个 1024-bit RSA 密钥对,其中 d = getPrime(180)(180 bit 的小私钥),然后加密三条消息。

可知题目下一层密码是:

python 复制代码
flag_hash = hashlib.sha256(str(p + q).encode()).hexdigest()
flag = f"next pass is {flag_hash}"

密文:

python 复制代码
c1 = pow(m1, e, n)
c2 = pow(m2, e, n)
c3 = pow(m3, e, n)

关键点就是 d 很小。

python 复制代码
p = getPrime(512)
q = getPrime(512)
d = getPrime(180)  

n是1024位,RSA模数。

d是180位,远小于 n 0.25 n^{0.25} n0.25约等于256位。

在这个指数前,1/3不是很影响位数。

满足 Wiener 攻击条件。

注意:题目使用
λ ( n ) = l c m ( p − 1 , q − 1 ) λ(n) = lcm(p-1,q-1) λ(n)=lcm(p−1,q−1)

而非
φ ( n ) φ(n) φ(n)

但 Wiener 攻击仍然有效,因为:
λ ( n ) ∣ φ ( n ) λ(n) | φ(n) λ(n)∣φ(n)

连分数展开的数学原理不变。

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
RSA Challenge Solver
- Attack: Known-plaintext assisted Wiener attack + generic factorization
- Target: Recover d → factor n → compute sha256(p+q)
"""

import hashlib
from math import gcd
from Crypto.Util.number import bytes_to_long

# ===== Challenge Parameters =====
n = 99573363048275234764231402769464116416087010014992319221201093905687439933632430466067992037046120712199565250482197004301343341960655357944577330885470918466007730570718648025143561656395751518428630742587023267450633824636936953524868735263666089452348466018195099471535823969365007120680546592999022195781
e = 12076830539295193533033212232487568888200963123024189287629493480058638222146972496110814372883829765692623107191129306190788976704250502316265439996891764101447017190377014980293589797403095249538391534986638973035285900867548420192211241163778919028921502305790979880346050428839102874086046622833211913299
c1 = 88537483899519116785221065592618063396859368769048931371104532271282451393564912999388648867349770059882231896252136530442609316120059139869000411598215669228402275014417736389191093818032356471508269901358077592526362193180661405990147957408129845474938259771860341576649904811782733150222504695142224907008

# Known plaintext (from challenge source code)
M1_KNOWN = bytes_to_long(b"Secret message: " + b"A" * 16)


def continued_fraction(a: int, b: int):
    """Generate continued fraction terms of a/b"""
    while b:
        q = a // b
        yield q
        a, b = b, a - q * b


def convergents(cf_terms):
    """Generate convergents (h, k) from continued fraction terms"""
    h0, h1 = 1, cf_terms[0]
    k0, k1 = 0, 1
    yield h1, k1
    for a in cf_terms[1:]:
        h0, h1 = h1, a * h1 + h0
        k0, k1 = k1, a * k1 + k0
        yield h1, k1


def recover_d(e, n, c_known, m_known):
    """
    Recover private key d using known-plaintext assisted Wiener attack.
    Handles both φ(n) and λ(n) cases via small-factor enumeration.
    """
    cf = list(continued_fraction(e, n))
    for _, den in convergents(cf):
        # Candidate set: den itself + den divided by small factors
        candidates = {den}
        for g in range(2, 2000):
            if den % g == 0:
                candidates.add(den // g)
        
        for d in candidates:
            if d > 1 and pow(c_known, d, n) == m_known:
                return d
    raise ValueError("Failed to recover private key d")


def factor_from_d(e, d, n):
    """
    Factor n using private key d via generic algorithm.
    Works for both φ(n) and λ(n) by finding non-trivial square root of 1.
    """
    # Write ed-1 = 2^s * t (t odd)
    k = e * d - 1
    t, s = k, 0
    while t % 2 == 0:
        t //= 2
        s += 1
    
    # Search for non-trivial square root of 1 mod n
    for a in range(2, 128):
        x = pow(a, t, n)
        if x in (1, n - 1):
            continue
        for _ in range(s - 1):
            y = pow(x, 2, n)
            if y == 1:
                p = gcd(x - 1, n)
                if 1 < p < n and n % p == 0:
                    return p, n // p
            if y == n - 1:
                break
            x = y
    raise ValueError("Failed to factor n")


def main():
    print("[*] Starting RSA challenge solver...")
    
    # Step 1: Recover private key d
    print("[*] Recovering private key d...")
    d = recover_d(e, n, c1, M1_KNOWN)
    print(f"[+] Found d ({d.bit_length()} bits)")
    
    # Step 2: Factor n using d
    print("[*] Factoring n...")
    p, q = factor_from_d(e, d, n)
    print(f"[+] p = {p}")
    print(f"[+] q = {q}")
    
    # Step 3: Compute flag
    password = hashlib.sha256(str(p + q).encode()).hexdigest()
    print(f"\n🚩 level3 password: {password}")
    
    # Optional: Verify decryption
    m1_dec = pow(c1, d, n)
    assert m1_dec == M1_KNOWN, "Decryption verification failed!"
    print("[✓] Decryption verified successfully")


if __name__ == "__main__":
    main()

不用AI这题还是真难做。

python 复制代码
最终目标:sha256(p + q)
    ↓ 需要
分解 n → 得到 p, q
    ↓ 需要
恢复私钥 d(标准分解需要 p,q,但已知 d 可通用分解 n)
    ↓ 需要
利用漏洞:d 仅 180 位(远小于 n^0.25),且题目给了已知明文对 (m1, c1)

Level 3:Leak 逐 bit 恢复 p, q

python 复制代码
from Crypto.Util.number import getPrime, bytes_to_long

p = getPrime(1536)
q = getPrime(1536)
n = p * q
e = 65537

flag = b"dart{**************}"
c = pow(bytes_to_long(flag), e, n)

CONST1 = 0xDEADBEEFCAFEBABE123456789ABCDEFFEDCBA9876543210
CONST2 = 0xCAFEBABEDEADBEEF123456789ABCDEF0123456789ABCDEF
CONST3 = 0x123456789ABCDEFFEDCBA9876543210FEDCBA987654321
MOD128 = 2 ** 128  
MASK64 = (1 << 64) - 1  
MASK128 = (1 << 128) - 1  

leak = ( (p * CONST1) ^ (q * CONST2) ^ ((p & q) << 64) ^ ((p | q) << 48) ^ ((p ^ q) * CONST3) ) + ((p + q) % MOD128) ^ ((p * q) & MASK64)

print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
print(f"leak = {leak}")

# n = 3656543170780671302102369785821318948521533232259598029746397061108006818468053676291634112787611176554924353628972482471754519193717232313848847744522215592281921147297898892307445674335249953174498025904493855530892785669281622228067328855550222457290704991186404511294392428626901071668540517391132556632888864694653334853557764027749481199416901881332307660966462957016488884047047046202519520508102461663246328437930895234074776654459967857843207320530170144023056782205928948050519919825477562514594449069964098794322005156920839848615481717184615581471471105167310877784107653826948801838083937060929103306952084786982834242119877046219260840966142997264676014575104231122349770882974818427591538551719990220347345614399639643257685591321500648437402084919467346049683842042993975696447711080289559063959271045082506968532103445241637971734173037224394103944153692310048043693502870706225319787902231218954548412018259
# e = 65537
# c = 1757914668604154089701710446907445787512346500378259224658947923217272944211214757488735053484213917067698715050010452193463598710989123020815295814709518742755820383364097695929549366414223421242599840755441311771835982431439073932340356341636346882464058493459455091691653077847776771631560498930589569988646613218910231153610031749287171649152922929066828605655570431656426074237261255561129432889318700234884857353891402733791836155496084825067878059001723617690872912359471109888664801793079193144489323455596341708697911158942505611709946252101670450796550313079139560281843612045681545992626944803230832776794454353639122595107671267859292222861367326121435154862607517890329925621367992667728899878422037182817860641530146234730196633237339901726508906733897556146751503097127672718192958642776389691940671356367304182825433592577899881444815062581163386947075887218537802483045756886019426749855723715192981635971943
# leak = 153338022210585970687495444409227961261783749570114993931231317427634321118309600575903662678286698071962304436931371977179197266063447616304477462206528342008151264611040982873859583628234755013757003082382562012219175070957822154944231126228403341047477686652371523951028071221719503095646413530842908952071610518530005967880068526701564472237686095043481296201543161701644160151712649014052002012116829110394811586873559266763339069172495704922906651491247001057095314718709634937187619890550086009706737712515532076

n = p * q(p、q 均为 1536 位素数,n ≈ 3072 位)

公钥 e = 65537(标准)

密文 c = flag^e mod n

关键泄露 leak:一个巨大的数,里面混合了 p、q 的各种位运算和常量

Leak 表达式解析

python 复制代码
Pythonleak = ( (p * CONST1) ^ (q * CONST2) ^ ((p & q) << 64) ^ ((p | q) << 48) ^ ((p ^ q) * CONST3) ) 
       + ((p + q) % 2**128) ^ ((p * q) & MASK64)

其中:

python 复制代码
CONST1、CONST2、CONST3 是已知的大常数(题目给出)
MOD128 = 2**128
MASK64 = 2**64 - 1
MASK128 = 2**128 - 1

这个 leak 把 p 和 q 的加法、乘法、与、或、异或等操作混在一起,并通过低 128 位 + 高位异或的方式泄露信息。

由于 p 和 q 都是 1536 位,我们无法直接暴力,但 leak 提供了大量关于 p+q、pq(已知 n)、p&q、p|q、p^q 的信息。
最有效的实际解法是:
提取 leak 的低 128 位(包含 (p+q) % 2**128 的信息)
利用已知 n = p
q,尝试恢复 s = p + q 的低位

结合了两个强约束:

  • 乘法约束:(pp * qq) ≡ n mod 2^{k+1}
  • leak 约束:leak_expr_mod(pp, qq, mod) == leak % mod
python 复制代码
from Crypto.Util.number import long_to_bytes

# ==================== 题目数据 ====================
n = 3656543170780671302102369785821318948521533232259598029746397061108006818468053676291634112787611176554924353628972482471754519193717232313848847744522215592281921147297898892307445674335249953174498025904493855530892785669281622228067328855550222457290704991186404511294392428626901071668540517391132556632888864694653334853557764027749481199416901881332307660966462957016488884047047046202519520508102461663246328437930895234074776654459967857843207320530170144023056782205928948050519919825477562514594449069964098794322005156920839848615481717184615581471471105167310877784107653826948801838083937060929103306952084786982834242119877046219260840966142997264676014575104231122349770882974818427591538551719990220347345614399639643257685591321500648437402084919467346049683842042993975696447711080289559063959271045082506968532103445241637971734173037224394103944153692310048043693502870706225319787902231218954548412018259
e = 65537
c = 1757914668604154089701710446907445787512346500378259224658947923217272944211214757488735053484213917067698715050010452193463598710989123020815295814709518742755820383364097695929549366414223421242599840755441311771835982431439073932340356341636346882464058493459455091691653077847776771631560498930589569988646613218910231153610031749287171649152922929066828605655570431656426074237261255561129432889318700234884857353891402733791836155496084825067878059001723617690872912359471109888664801793079193144489323455596341708697911158942505611709946252101670450796550313079139560281843612045681545992626944803230832776794454353639122595107671267859292222861367326121435154862607517890329925621367992667728899878422037182817860641530146234730196633237339901726508906733897556146751503097127672718192958642776389691940671356367304182825433592577899881444815062581163386947075887218537802483045756886019426749855723715192981635971943
leak = 153338022210585970687495444409227961261783749570114993931231317427634321118309600575903662678286698071962304436931371977179197266063447616304477462206528342008151264611040982873859583628234755013757003082382562012219175070957822154944231126228403341047477686652371523951028071221719503095646413530842908952071610518530005967880068526701564472237686095043481296201543161701644160151712649014052002012116829110394811586873559266763339069172495704922906651491247001057095314718709634937187619890550086009706737712515532076

CONST1 = 0xDEADBEEFCAFEBABE123456789ABCDEFFEDCBA9876543210
CONST2 = 0xCAFEBABEDEADBEEF123456789ABCDEF0123456789ABCDEF
CONST3 = 0x123456789ABCDEFFEDCBA9876543210FEDCBA987654321
MOD128 = 2 ** 128
MASK64 = (1 << 64) - 1

# ==================== leak 表达式 ====================
def leak_expr_mod(p, q, mod):
    A = (
        (p * CONST1) ^
        (q * CONST2) ^
        ((p & q) << 64) ^
        ((p | q) << 48) ^
        ((p ^ q) * CONST3)
    )
    B = (p + q) % MOD128
    C = (p * q) & MASK64
    return ((A + B) ^ C) % mod

# ==================== 逐比特恢复 p、q ====================
def recover_pq():
    states = [(1, 1)]                     # (p_low, q_low),从最低位开始(都是奇数)
    print("[+] 开始逐比特恢复 p 和 q ...")

    for k in range(1, 1536):
        mod = 1 << (k + 1)
        target = leak % mod

        new_states = []
        for p_low, q_low in states:
            for a in (0, 1):
                pp = p_low | (a << k)
                for b in (0, 1):
                    qq = q_low | (b << k)
                    # 约束1:低位乘积正确
                    if (pp * qq - n) % mod != 0:
                        continue
                    # 约束2:leak 在当前模数下匹配
                    if leak_expr_mod(pp, qq, mod) != target:
                        continue
                    new_states.append((pp, qq))

        # 去重
        states = []
        seen = set()
        for item in new_states:
            if item not in seen:
                seen.add(item)
                states.append(item)

        if len(states) == 0:
            print(f"[-] 失败:在第 {k+1} 位没有候选")
            return None, None

        # 打印进度
        if (k + 1) % 64 == 0 or (k + 1) in [128, 256, 512, 1024]:
            print(f"[+] 已恢复 {k+1:4d} 位,低位候选数量: {len(states)}")

        if len(states) > 16:
            print(f"[!] 警告:第 {k+1} 位候选过多 ({len(states)}),继续运行...")

    p, q = states[0]
    if p > q:
        p, q = q, p
    return p, q

# ==================== 主函数 ====================
def main():
    p, q = recover_pq()
    if p is None or q is None:
        print("[-] 恢复失败")
        return

    print("\n" + "="*60)
    print("[+] 恢复成功!")
    print(f"p = {p}")
    print(f"q = {q}")
    print(f"p * q == n ? {p * q == n}")
    print(f"p 位长 = {p.bit_length()}, q 位长 = {q.bit_length()}")

    # 计算私钥并解密 flag
    phi = (p - 1) * (q - 1)
    d = pow(e, -1, phi)
    m = pow(c, d, n)
    flag = long_to_bytes(m).decode(errors='ignore')

    print(f"[+] flag = {flag}")
    print("="*60)

if __name__ == "__main__":
    main()
相关推荐
xuansec2 小时前
ThinkPHP 6.0.X 反序列化漏洞利用指南(PHPGGC 工具版)
安全·php
智擎软件测评小祺2 小时前
从报告看懂安全隐患,提升防护能力
安全·web安全·渗透测试·测试·检测·cma·cnas
路溪非溪2 小时前
Linux中Netlink简介和使用总结
linux·网络·arm开发·驱动开发
Digitally2 小时前
如何轻松地使用隔空投送将iPhone内容传输到Android
android·ios·iphone
Mr_Xuhhh2 小时前
[特殊字符] 《网络知识和Servlet重点知识整理》
网络·servlet
lishutong10062 小时前
Android 性能诊断 V2:基于 Agent Skill 的原生 IDE 融合架构
android·ide·架构
952362 小时前
网络原理 - HTTP / HTTPS
网络·http·https
恋猫de小郭2 小时前
AGP 9.2 开始,Android 上协程启动和取消速度提升两倍
android·前端·flutter
克莱因3582 小时前
思科 单区域OSPF(1
网络·路由·思科