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 Description Description

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 Analysis Analysis

可以明显地看到

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-1 ep1,q1,r1;且 e e e是由 e e e与三个素数减一的最大公因数的乘积

这就意味着 e e e ϕ ( n ) \phi(n) ϕ(n)不互素,那么即使我们知道了 e e e,也不能按照正常的RSA解密方式 m ≡ c d ( m o d n ) m\equiv c^d\pmod n mcd(modn)求解

所以目前有两个难点,一是 e e e是未知大小的,二是如何在 e e e ϕ ( n ) \phi(n) ϕ(n)非互质的情况下求解RSA


G e t   e Get~e Get e

p − 1 , q − 1 , r − 1 p-1,q-1,r-1 p1,q1,r1都可以先分解程成一些较小的素数和一两个较大的整数(无法继续分解了)

下面以求得 e p ep ep为例

∵ 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 t t是任意一个不是 p p p倍数的整数, e p ep ep e e e p − 1 p-1 p1的最大公因数

换而言之,中间的 t e p ( m o d p ) t^{ep}\pmod p tep(modp)可以用来替换成与密文 c c c相关的式子以达到求得 e e e的目的

∵ 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} cme(modn)mepeqer(modpqr)

∴ 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))eqer(modp)

这里只要是正确的 e p ep ep,把 c c c e p ep ep代入费马小定理的式子中会同余于 1 1 1

这就意味着我们只需要爆破出满足 c p − 1 e p ≡ 1 ( m o d p ) c^{\frac{p-1}{ep}}\equiv 1\pmod p cepp11(modp) e p ep ep即可

同理 e q eq eq e r er er

实现代码

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~plaint Get plaint

一般的想法是AMM算法,但是根据这道题的条件AMM算法计算时间可能有几个小时,这里根据官方wp介绍的更为快捷的算法来解题

算法来源:https://eprint.iacr.org/2020/1059.pdf

算法的相关证明以及扩展详情可见上述论文

该算法的运行时间与 e e e的大小成线性关系(所以之后我们需要缩小 e e e

这里就介绍算法过程

请添加图片描述

A l g o r i t h m   1 Algorithm~1 Algorithm 1

先令 ϕ ˙ = ϕ ( n ) e \dot\phi=\frac{\phi(n)}{e} ϕ˙=eϕ(n) g = 1 g=1 g=1

循环 g = g + 1 g=g+1 g=g+1 g E ≡ g ϕ ˙ ( m o d N ) g_E\equiv g^{\dot\phi}\pmod N gEgϕ˙(modN)

直到出现 g E ≠ 1 g_E\neq 1 gE=1 g E g_E gE

请添加图片描述

请添加图片描述

A l g o r i t h m   2 Algorithm~2 Algorithm 2

依旧令 ϕ ˙ = ϕ ( n ) e \dot\phi=\frac{\phi(n)}{e} ϕ˙=eϕ(n)

计算 d ≡ e − 1 ( m o d ϕ ˙ ) d\equiv e^{-1}\pmod {\dot\phi} de1(modϕ˙) a ≡ c d ( m o d N ) a\equiv c^d\pmod N acd(modN)

判断 a a a转换为字节/字符类型之后,是否有价值的信息(这里就是判断是否b'SUSCTF'在其中)

若没有,进行 e e e次循环,跳出循环的条件也是上述条件

循环体: a ≡ a ⋅ g E ( m o d N ) a\equiv a \cdot g_E \pmod N aagE(modN)

最后满足条件的 a a a就是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~Majorization Necessary Majorization

F i r s t First First

A l g o r i t h m   2 Algorithm~2 Algorithm 2中需要进行 e e e次循环,而目前e=757*66553*5156273=259776235785533;肉眼可见的太大了,可能没几年跑不完

那就除去 r r r来缩小 e e e,显然不能简单地在 e e e上除去 r r r就完了,还需要对 c c c ϕ ( n ) \phi(n) ϕ(n)作改变

ϕ ( n ) = ( p − 1 ) ⋅ ( q − 1 ) \phi(n)=(p-1)\cdot (q-1) ϕ(n)=(p1)(q1) 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)} drer1(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} cmerepeq(modpqr)

∴ 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(merdr)epeq(modN)

所以计算 c ≡ c d r ( m o d N ) c\equiv c^{d_{r}}\pmod N ccdr(modN)得到的 c c c即是 e e e变化后对应的 c c c

S e c o n d Second Second

在RSA加密过程中程序对明文作了填充,也就是pad()函数

仔细观察也就是简单在明文十六进制位上不断添 0 0 0直到整体的十六进制位达到 3 ⋅ L 3\cdot L 3L

而且真实的明文长度处于$ L$到 2 ⋅ L 2\cdot L 2L之间(此时 m m m小于 N N N

assert len(message)>L and len(message)<2*L

也就是说,至少填充了 L L L位的十六进制,而总共 3 ⋅ L 3\cdot L 3L的十六进制位已经大于了 N N N

也就意味着这样已经造成了明文丢失,且无法恢复明文

那么就需要把填充的十六进制位数消除,至少消去 L L L位的十六进制(其余的填充位因为未知大小所以就不管了),因为 2 ⋅ L 2\cdot L 2L的十六进制位是小于 N N N的,可以就此恢复明文

仔细观察pad()函数

def pad(s):
    if len(s)<3*L:
        s+=bytes(3*L-len(s))
    return s

实际上我们使用二进制更多一些,这里1 byte 8 8 8个二进制位,那么实际有 2 8 ⋅ L 2^{8\cdot L} 28L多余的数

原来的pad()函数相当于 m = m ⋅ 2 8 ⋅ L m=m\cdot 2^{8\cdot L} m=m28L;代入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 N ccmcpadcm28Le(modN)

那么为了消去pad的影响,我们直接乘上padding的逆元d=gmpy2.invert(pow(2,8*L*e,N),N)

其中,这里的 e e e N N N可以是除去 r r r之后的,也可以是除去 r r r之前的

实现代码

c = c * gmpy2.invert(pow(2,8 * L * e,n),n) % n

S o l v i n g   c o d e Solving~code Solving 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

请添加图片描述

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

M3ng@L

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值