SUSCTF_Crypto_large case_复现
k e y w o r d s : keywords:keywords: e与欧拉函数非互质的RSA解密,恢复e,消除padding,除去部分e的素因子
D e s c r i p t i o n DescriptionDescription
from Crypto.Util.number import *
from secret import e,message
def pad(s):
if len(s)<3*L:
s+=bytes(3*L-len(s))
return s
L=128
p=127846753573603084140032502367311687577517286192893830888210505400863747960458410091624928485398237221748639465569360357083610343901195273740653100259873512668015324620239720302434418836556626441491996755736644886234427063508445212117628827393696641594389475794455769831224080974098671804484986257952189021223
q=145855456487495382044171198958191111759614682359121667762539436558951453420409098978730659224765186993202647878416602503196995715156477020462357271957894750950465766809623184979464111968346235929375202282811814079958258215558862385475337911665725569669510022344713444067774094112542265293776098223712339100693
r=165967627827619421909025667485886197280531070386062799707570138462960892786375448755168117226002965841166040777799690060003514218907279202146293715568618421507166624010447447835500614000601643150187327886055136468260391127675012777934049855029499330117864969171026445847229725440665179150874362143944727374907
n=p*q*r
assert isPrime(GCD(e,p-1)) and isPrime(GCD(e,q-1)) and isPrime(GCD(e,r-1)) and e==GCD(e,p-1)*GCD(e,q-1)*GCD(e,r-1)
assert len(message)>L and len(message)<2*L
assert b'SUSCTF' in message
m=bytes_to_long(pad(message))
c=pow(m,e,n)
print(c)
'''
2832775557487418816663494645849097066925967799754895979829784499040437385450603537732862576495758207240632734290947928291961063611897822688909447511260639429367768479378599532712621774918733304857247099714044615691877995534173849302353620399896455615474093581673774297730056975663792651743809514320379189748228186812362112753688073161375690508818356712739795492736743994105438575736577194329751372142329306630950863097761601196849158280502041616545429586870751042908365507050717385205371671658706357669408813112610215766159761927196639404951251535622349916877296956767883165696947955379829079278948514755758174884809479690995427980775293393456403529481055942899970158049070109142310832516606657100119207595631431023336544432679282722485978175459551109374822024850128128796213791820270973849303929674648894135672365776376696816104314090776423931007123128977218361110636927878232444348690591774581974226318856099862175526133892
'''
A n a l y s i s AnalysisAnalysis
可以明显地看到
assert isPrime(GCD(e,p-1)) and isPrime(GCD(e,q-1)) and isPrime(GCD(e,r-1)) and e==GCD(e,p-1)*GCD(e,q-1)*GCD(e,r-1)
e ∣ p − 1 , q − 1 , r − 1 e|p-1,q-1,r-1e∣p−1,q−1,r−1;且e ee是由e ee与三个素数减一的最大公因数的乘积
这就意味着e ee与ϕ ( n ) \phi(n)ϕ(n)不互素,那么即使我们知道了e ee,也不能按照正常的RSA解密方式m ≡ c d ( m o d n ) m\equiv c^d\pmod nm≡cd(modn)求解
所以目前有两个难点,一是e ee是未知大小的,二是如何在e ee与ϕ ( n ) \phi(n)ϕ(n)非互质的情况下求解RSA
G e t e Get~eGet e
p − 1 , q − 1 , r − 1 p-1,q-1,r-1p−1,q−1,r−1都可以先分解程成一些较小的素数和一两个较大的整数(无法继续分解了)
下面以求得e p epep为例
∵ g c d ( e , ϕ ( n ) ) ≠ 1 \because gcd(e,\phi(n))\neq1∵gcd(e,ϕ(n))=1,且 $ t^{p-1}\equiv t^{\frac{ep\cdot(p-1)}{ep}} \equiv (t^{ep}\pmod p)^{\frac{p-1}{ep}}\equiv 1\pmod p $;(费马小定理)
其中t tt是任意一个不是p pp倍数的整数,e p epep是e ee和p − 1 p-1p−1的最大公因数
换而言之,中间的t e p ( m o d p ) t^{ep}\pmod ptep(modp)可以用来替换成与密文c cc相关的式子以达到求得e ee的目的
∵ c ≡ m e ( m o d n ) ≡ m e p ⋅ e q ⋅ e r ( m o d p ⋅ q ⋅ r ) \because c\equiv m^e\pmod n\equiv m^{ep\cdot eq\cdot er}\pmod {p\cdot q\cdot r}∵c≡me(modn)≡mep⋅eq⋅er(modp⋅q⋅r)
∴ c ( m o d p ) ≡ ( m e p ( m o d p ) ) e q ⋅ e r ( m o d p ) \therefore c\pmod p\equiv (m^{ep}\pmod p)^{eq\cdot er}\pmod p∴c(modp)≡(mep(modp))eq⋅er(modp)
这里只要是正确的e p epep,把c cc和e p epep代入费马小定理的式子中会同余于1 11
这就意味着我们只需要爆破出满足c p − 1 e p ≡ 1 ( m o d p ) c^{\frac{p-1}{ep}}\equiv 1\pmod pcepp−1≡1(modp)的e p epep即可
同理e q eqeq,e r erer
实现代码
import primefac
for e_p in primefac.primefac(p-1):
if pow(c%p,(p-1)//e_p,p) == 1 and e_p != 2:
# print(e_p)
break
for e_q in primefac.primefac(q-1):
if pow(c%q,(q-1)//e_q,q) == 1 and e_q != 2:
# print(e_q)
break
for e_r in primefac.primefac(r-1):
if pow(c%r,(r-1)//e_r,r) == 1 and e_r != 2:
# print(e_r)
break
G e t p l a i n t Get~plaintGet plaint
一般的想法是AMM算法,但是根据这道题的条件AMM算法计算时间可能有几个小时,这里根据官方wp介绍的更为快捷的算法来解题
算法来源:https://eprint.iacr.org/2020/1059.pdf
算法的相关证明以及扩展详情可见上述论文
该算法的运行时间与e ee的大小成线性关系(所以之后我们需要缩小e ee)
这里就介绍算法过程

A l g o r i t h m 1 Algorithm~1Algorithm 1
先令ϕ ˙ = ϕ ( n ) e \dot\phi=\frac{\phi(n)}{e}ϕ˙=eϕ(n);g = 1 g=1g=1
循环g = g + 1 g=g+1g=g+1;g E ≡ g ϕ ˙ ( m o d N ) g_E\equiv g^{\dot\phi}\pmod NgE≡gϕ˙(modN)
直到出现g E ≠ 1 g_E\neq 1gE=1的g E g_EgE


A l g o r i t h m 2 Algorithm~2Algorithm 2
依旧令ϕ ˙ = ϕ ( n ) e \dot\phi=\frac{\phi(n)}{e}ϕ˙=eϕ(n)
计算d ≡ e − 1 ( m o d ϕ ˙ ) d\equiv e^{-1}\pmod {\dot\phi}d≡e−1(modϕ˙);a ≡ c d ( m o d N ) a\equiv c^d\pmod Na≡cd(modN)
判断a aa转换为字节/字符类型之后,是否有价值的信息(这里就是判断是否b'SUSCTF'在其中)
若没有,进行e ee次循环,跳出循环的条件也是上述条件
循环体:a ≡ a ⋅ g E ( m o d N ) a\equiv a \cdot g_E \pmod Na≡a⋅gE(modN)
最后满足条件的a aa就是flag
代码实现
# 算法一
phi = phi_n // e
g = 1
g_E = pow(g,phi,n)
while g_E == 1:
g = g + 1
g_E = pow(g,phi,n)
# print(g,g_E)
# 算法二
d = gmpy2.invert(e,phi)
a = pow(c,d,n)
for _ in tqdm(range(now_e)):
flag = a
if b"SUSCTF" in long_to_bytes(flag):
print(long_to_bytes(flag))
break
else:
a = a * g_E % n
N e c e s s a r y M a j o r i z a t i o n Necessary~MajorizationNecessary Majorization
F i r s t FirstFirst
在A l g o r i t h m 2 Algorithm~2Algorithm 2中需要进行e ee次循环,而目前e=757*66553*5156273=259776235785533;肉眼可见的太大了,可能没几年跑不完
那就除去r rr来缩小e ee,显然不能简单地在e ee上除去r rr就完了,还需要对c cc,ϕ ( n ) \phi(n)ϕ(n)作改变
令ϕ ( n ) = ( p − 1 ) ⋅ ( q − 1 ) \phi(n)=(p-1)\cdot (q-1)ϕ(n)=(p−1)⋅(q−1);N = N r N = \frac{N}{r}N=rN
由d r ⋅ e r ≡ 1 ( m o d ϕ ( n ) ) d_{r}\cdot e_r\equiv 1\pmod {\phi(n)}dr⋅er≡1(modϕ(n))求得逆元d r d_{r}dr
∵ c ≡ m e r ⋅ e p ⋅ e q ( m o d p ⋅ q ⋅ r ) \because c\equiv m^{e_r\cdot e_p\cdot e_q}\pmod {p\cdot q\cdot r}∵c≡mer⋅ep⋅eq(modp⋅q⋅r)
∴ c d r ≡ ( m e r ⋅ d r ) e p ⋅ e q ( m o d N ) \therefore c^{d_r}\equiv (m^{e_r\cdot d_r})^{e_p\cdot e_q}\pmod N∴cdr≡(mer⋅dr)ep⋅eq(modN)
所以计算c ≡ c d r ( m o d N ) c\equiv c^{d_{r}}\pmod Nc≡cdr(modN)得到的c cc即是e ee变化后对应的c cc
S e c o n d SecondSecond
在RSA加密过程中程序对明文作了填充,也就是pad()函数
仔细观察也就是简单在明文十六进制位上不断添0 00直到整体的十六进制位达到3 ⋅ L 3\cdot L3⋅L位
而且真实的明文长度处于$ L$到2 ⋅ L 2\cdot L2⋅L之间(此时m mm小于N NN)
assert len(message)>L and len(message)<2*L
也就是说,至少填充了L LL位的十六进制,而总共3 ⋅ L 3\cdot L3⋅L的十六进制位已经大于了N NN
也就意味着这样已经造成了明文丢失,且无法恢复明文
那么就需要把填充的十六进制位数消除,至少消去L LL位的十六进制(其余的填充位因为未知大小所以就不管了),因为2 ⋅ L 2\cdot L2⋅L的十六进制位是小于N NN的,可以就此恢复明文
仔细观察pad()函数
def pad(s):
if len(s)<3*L:
s+=bytes(3*L-len(s))
return s
实际上我们使用二进制更多一些,这里1 byte是8 88个二进制位,那么实际有2 8 ⋅ L 2^{8\cdot L}28⋅L多余的数
原来的pad()函数相当于m = m ⋅ 2 8 ⋅ L m=m\cdot 2^{8\cdot L}m=m⋅28⋅L;代入RSA加密之后,可以这样看
c ≡ c m ⋅ c p a d ≡ c m ⋅ 2 8 ⋅ L ⋅ e ( m o d N ) c\equiv c_m\cdot c_{pad}\equiv c_m\cdot 2^{8\cdot L\cdot e}\pmod Nc≡cm⋅cpad≡cm⋅28⋅L⋅e(modN)
那么为了消去pad的影响,我们直接乘上padding的逆元d=gmpy2.invert(pow(2,8*L*e,N),N)
其中,这里的e ee和N NN可以是除去r rr之后的,也可以是除去r rr之前的
实现代码
c = c * gmpy2.invert(pow(2,8 * L * e,n),n) % n
S o l v i n g c o d e Solving~codeSolving code
from Crypto.Util.number import *
import gmpy2,primefac
from tqdm import tqdm
L = 128
p = 127846753573603084140032502367311687577517286192893830888210505400863747960458410091624928485398237221748639465569360357083610343901195273740653100259873512668015324620239720302434418836556626441491996755736644886234427063508445212117628827393696641594389475794455769831224080974098671804484986257952189021223
q = 145855456487495382044171198958191111759614682359121667762539436558951453420409098978730659224765186993202647878416602503196995715156477020462357271957894750950465766809623184979464111968346235929375202282811814079958258215558862385475337911665725569669510022344713444067774094112542265293776098223712339100693
r = 165967627827619421909025667485886197280531070386062799707570138462960892786375448755168117226002965841166040777799690060003514218907279202146293715568618421507166624010447447835500614000601643150187327886055136468260391127675012777934049855029499330117864969171026445847229725440665179150874362143944727374907
c = 2832775557487418816663494645849097066925967799754895979829784499040437385450603537732862576495758207240632734290947928291961063611897822688909447511260639429367768479378599532712621774918733304857247099714044615691877995534173849302353620399896455615474093581673774297730056975663792651743809514320379189748228186812362112753688073161375690508818356712739795492736743994105438575736577194329751372142329306630950863097761601196849158280502041616545429586870751042908365507050717385205371671658706357669408813112610215766159761927196639404951251535622349916877296956767883165696947955379829079278948514755758174884809479690995427980775293393456403529481055942899970158049070109142310832516606657100119207595631431023336544432679282722485978175459551109374822024850128128796213791820270973849303929674648894135672365776376696816104314090776423931007123128977218361110636927878232444348690591774581974226318856099862175526133892
n = p * q * r
# 恢复e
for e_p in primefac.primefac(p-1):
if pow(c%p,(p-1)//e_p,p) == 1 and e_p != 2:
# print(e_p)
break
for e_q in primefac.primefac(q-1):
if pow(c%q,(q-1)//e_q,q) == 1 and e_q != 2:
# print(e_q)
break
for e_r in primefac.primefac(r-1):
if pow(c%r,(r-1)//e_r,r) == 1 and e_r != 2:
# print(e_r)
break
e = e_p * e_q * e_r
# print(e_p,e_q,e_r)
phi_n = (p - 1) * (q - 1) * (r - 1)
# print(e)
# 消除L位的十六进制的padding(在删除r之前)
c = c * gmpy2.invert(pow(2,8 * L * e,n),n) % n
# 缩小e:删除r以及更改各个指标的受r的影响
e = e // e_r
phi_n_without_r = (p - 1) * (q - 1)
n = n // r
d1 = gmpy2.invert(e_r,phi_n_without_r)
c = pow(c,d1,n)
# 在删除r后消除padding
# c = c * gmpy2.invert(pow(2,8 * L * now_e,n),n) % n
# 算法一
phi = phi_n_without_r // e
g = 1
g_E = pow(g,phi,n)
while g_E == 1:
g = g + 1
g_E = pow(g,phi,n)
# print(g,g_E)
# 算法二
d = gmpy2.invert(e,phi)
a = pow(c,d,n)
for _ in tqdm(range(e)):
flag = a
if b"SUSCTF" in long_to_bytes(flag):
print()
print(long_to_bytes(flag))
break
else:
a = a * g_E % n
最后得到flag
