文章目录
-
-
- 解题
- [Level 1:RSA 多攻击 + 秘密恢复](#Level 1:RSA 多攻击 + 秘密恢复)
- [Level 2:已知明文 + 小私钥 d 恢复](#Level 2:已知明文 + 小私钥 d 恢复)
- [Level 3:Leak 逐 bit 恢复 p, q](#Level 3:Leak 逐 bit 恢复 p, q)
-
解题
拿到附件,先解压后有一个 level1 目录和一个 level2.zip。

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