Ciphertexts - Writeup by AI
一、题目信息
- 题目来源: Bugku CTF
- 题目类别: Crypto / RSA
- 题目文件 :
main.py,output.txt - 考点: RSA 共模攻击、扩展欧几里得算法、数论
二、题目分析
2.1 代码逻辑
python
p = getPrime(512)
q = getPrime(512)
r = getPrime(512)
n1 = p * q # 512+512 = 1024 位
n2 = p * q * r # 512+512+512 = 1536 位
e1 = getPrime(20)
e2 = int(gmpy2.next_prime(e1)) # e2 是 e1 的下一个素数
m = bytes_to_long(flag)
c1 = pow(m, e1, n1) # c1 = m^e1 mod n1
c2 = pow(m, e2, n2) # c2 = m^e2 mod n2
2.2 关键特征
- 共享因子 : n2=n1×rn_2 = n_1 \times rn2=n1×r,即 n1∣n2n_1 | n_2n1∣n2
- 相邻指数 : gcd(e1,e2)=1\gcd(e_1, e_2) = 1gcd(e1,e2)=1(相邻素数互质)
- 同一明文 : 使用相同的 mmm 进行两次加密
三、解题思路
3.1 核心观察
由于 n1∣n2n_1 | n_2n1∣n2,我们有:
- c1≡me1(modn1)c_1 \equiv m^{e_1} \pmod{n_1}c1≡me1(modn1)
- c2≡me2(modn2) ⟹ c2≡me2(modn1)c_2 \equiv m^{e_2} \pmod{n_2} \implies c_2 \equiv m^{e_2} \pmod{n_1}c2≡me2(modn2)⟹c2≡me2(modn1)
因此在模 n1n_1n1 的意义下,我们有两个同余方程:
c1≡me1(modn1)c_1 \equiv m^{e_1} \pmod{n_1}c1≡me1(modn1)
c2≡me2(modn1)c_2 \equiv m^{e_2} \pmod{n_1}c2≡me2(modn1)
3.2 共模攻击
由于 gcd(e1,e2)=1\gcd(e_1, e_2) = 1gcd(e1,e2)=1,根据扩展欧几里得算法,存在整数 a,ba, ba,b 使得:
a⋅e1+b⋅e2=1a \cdot e_1 + b \cdot e_2 = 1a⋅e1+b⋅e2=1
则:
m=ma⋅e1+b⋅e2=(me1)a⋅(me2)b≡c1a⋅c2b(modn1)m = m^{a \cdot e_1 + b \cdot e_2} = (m^{e_1})^a \cdot (m^{e_2})^b \equiv c_1^a \cdot c_2^b \pmod{n_1}m=ma⋅e1+b⋅e2=(me1)a⋅(me2)b≡c1a⋅c2b(modn1)
3.3 为什么可以恢复完整明文?
Flag 通常较短(几十字节),而 n1n_1n1 是 1024 位大整数,所以 m<n1m < n_1m<n1。因此通过上述方法计算出的 m(modn1)m \pmod{n_1}m(modn1) 就是 mmm 本身。
四、详细步骤
步骤 1: 验证 n2=n1×rn_2 = n_1 \times rn2=n1×r
python
r = n2 // n1
assert n1 * r == n2 # 验证成立
计算得:
r = 13153308347214004396487614747060422447697079642955901633441483325707932063710055895033655307701901587407685632388434170260760605433627022109576352718594963
步骤 2: 扩展欧几里得算法
计算 gcd(e1,e2)\gcd(e_1, e_2)gcd(e1,e2) 和系数 a,ba, ba,b:
python
g, a, b = gmpy2.gcdext(e1, e2)
# 结果:g = 1, a = -74571, b = 74570
验证:(−74571)×745699+74570×745709=1(-74571) \times 745699 + 74570 \times 745709 = 1(−74571)×745699+74570×745709=1 ✓
步骤 3: 计算明文 mmm
由于 a=−74571<0a = -74571 < 0a=−74571<0,需要计算 c1c_1c1 的模逆元:
python
c1_inv = gmpy2.invert(c1, n1)
m = (c1_inv^(-a) * c2^b) mod n1
计算得:
m = 110248894196938431401405464716666086561273237812093
步骤 4: 转换为字符串
python
flag = long_to_bytes(m).decode('utf-8')
# 结果:KosenCTF{HALDYN_D0M3}
步骤 5: 验证
python
# 验证 1: m^e1 mod n1 == c1
pow(m, e1, n1) == c1 # ✓
# 验证 2: m^e2 mod n2 == c2
pow(m, e2, n2) == c2 # ✓
五、完整代码
python
from Crypto.Util.number import *
import gmpy2
# 题目数据
n1 = 112027309284322736696115076630869358886830492611271994068413296220031576824816689091198353617581184917157891542298780983841631012944437383240190256425846911754031739579394796766027697768621362079507428010157604918397365947923851153697186775709920404789709337797321337456802732146832010787682176518192133746223
n2 = 1473529742325407185540416487537612465189869383161838138383863033575293817135218553055973325857269118219041602971813973919025686562460789946104526983373925508272707933534592189732683735440805478222783605568274241084963090744480360993656587771778757461612919160894779254758334452854066521288673310419198851991819627662981573667076225459404009857983025927477176966111790347594575351184875653395185719233949213450894170078845932168528522589013379762955294754168074749
e1 = 745699
e2 = 745709
c1 = 23144512980313393199971544624329972186721085732480740903664101556117858633662296801717263237129746648060819811930636439097159566583505473503864453388951643914137969553861677535238877960113785606971825385842502989341317320369632728661117044930921328060672528860828028757389655254527181940980759142590884230818
c2 = 546013011162734662559915184213713993843903501723233626580722400821009012692777901667117697074744918447814864397339744069644165515483680946835825703647523401795417620543127115324648561766122111899196061720746026651004752859257192521244112089034703744265008136670806656381726132870556901919053331051306216646512080226785745719900361548565919274291246327457874683359783654084480603820243148644175296922326518199664119806889995281514238365234514624096689374009704546
# 计算 r
r = n2 // n1
# 扩展欧几里得算法
g, a, b = gmpy2.gcdext(e1, e2)
# 处理负指数,计算明文
if a < 0:
c1_inv = gmpy2.invert(c1, n1)
m = (pow(c1_inv, -a, n1) * pow(c2, b, n1)) % n1
elif b < 0:
c2_inv = gmpy2.invert(c2, n1)
m = (pow(c1, a, n1) * pow(c2_inv, -b, n1)) % n1
else:
m = (pow(c1, a, n1) * pow(c2, b, n1)) % n1
# 转换为字符串
flag = long_to_bytes(m).decode('utf-8')
print(f"Flag: {flag}")
六、运行结果
[*] Calculating r = n2 // n1...
[*] r = 13153308347214004396487614747060422447697079642955901633441483325707932063710055895033655307701901587407685632388434170260760605433627022109576352718594963
[+] Verified: n2 = n1 * r
[*] Computing extended GCD of e1 and e2...
[+] gcd(e1, e2) = 1, a = -74571, b = 74570
[*] Recovering message m...
[+] m mod n1 = 110248894196938431401405464716666086561273237812093
[+] Decoded flag: KosenCTF{XXX}
[+] Verification successful: m^e1 mod n1 == c1
[+] Verification successful: m^e2 mod n2 == c2