总述
g
c
d
(
e
,
φ
(
n
)
)
gcd(e,\varphi(n))
gcd(e,φ(n))比较小时可以考虑iroot
直接开根,当直接开根跑不出来时,考虑有限域内开方
g
c
d
(
e
,
φ
(
n
)
)
gcd(e,\varphi(n))
gcd(e,φ(n))很大时,考虑AMM
算法
类型一
e e e和 φ ( n ) \varphi(n) φ(n)不互素,但是 e e e和 p − 1 p-1 p−1或者 q − 1 q-1 q−1互素,转化到模 p p p或者模 q q q下求解
例题 (2023MoeCTF bad_E)
from Crypto.Util.number import *
p = getPrime(512)
q = getPrime(512)
e = 65537
print(p) # 6853495238262155391975011057929314523706159020478084061020122347902601182448091015650787022962180599741651597328364289413042032923330906135304995252477571
print(q) # 11727544912613560398705401423145382428897876620077115390278679983274961030035884083100580422155496261311510530671232666801444557695190734596546855494472819
with open("flag.txt","r") as fs:
flag = fs.read().strip()
m = bytes_to_long(flag.encode())
c = pow(m,e,p*q)
print(c) # 63388263723813143290256836284084914544524440253054612802424934400854921660916379284754467427040180660945667733359330988361620691457570947823206385692232584893511398038141442606303536260023122774682805630913037113541880875125504376791939861734613177272270414287306054553288162010873808058776206524782351475805
exp
from Crypto.Util.number import *
p=6853495238262155391975011057929314523706159020478084061020122347902601182448091015650787022962180599741651597328364289413042032923330906135304995252477571
q=11727544912613560398705401423145382428897876620077115390278679983274961030035884083100580422155496261311510530671232666801444557695190734596546855494472819
e=65537
c = 63388263723813143290256836284084914544524440253054612802424934400854921660916379284754467427040180660945667733359330988361620691457570947823206385692232584893511398038141442606303536260023122774682805630913037113541880875125504376791939861734613177272270414287306054553288162010873808058776206524782351475805
n = p*q
# _gcd = gcd(e,(p-1)*(q-1)) # 65537
gcd_q = gcd(e,q-1) # 1
d = inverse(e,q-1)
m = pow(c,d,q)
print(long_to_bytes(int(m)))
# moectf{N0w_Y0U_hAve_kN0w_h0w_rsA_w0rks!_f!lP0iYlJf!M3ru}
因为gcd(e,phi)=e=665537
很大,可以用AMM
算法求解
exp2
# sagemanth
import random
import math
import time
from Crypto.Util.number import bytes_to_long,long_to_bytes
p = 0
#设置模数
def GF(a):
global p
p = a
#乘法取模
def g(a,b):
global p
return pow(a,b,p)
def AMM(x,e,p):
GF(p)
y = random.randint(1, p-1)
while g(y, (p-1)//e) == 1:
y = random.randint(1, p-1)
print(y)
print("find")
#p-1 = e^t*s
t = 1
s = 0
while p % e == 0:
t += 1
print(t)
s = p // (e**t)
print('e =',e)
print('p =',p)
print('s =',s)
print('t =',t)
# s|ralpha-1
k = 1
while((s * k + 1) % e != 0):
k += 1
alpha = (s * k + 1) // e
#计算a = y^s b = x^s h =1
#h为e次非剩余部分的积
a = g(y, (e ** (t - 1) ) * s)
b = g(x, e * alpha - 1)
c = g(y, s)
h = 1
#
for i in range(1, t-1):
d = g(b,e**(t-1-i))
if d == 1:
j = 0
else:
j = -math.log(d,a)
b = b * (g(g(c, e), j))
h = h * g(c, j)
c = g(c, e)
#return (g(x, alpha * h)) % p
root = (g(x, alpha * h)) % p
roots = set()
for i in range(e):
mp2 = root * g(a,i) %p
assert(g(mp2, e) == x)
roots.add(mp2)
return roots
# def check(m):
# if 'flag' in m:
# print(m)
# return True
# else:
# return False
p=6853495238262155391975011057929314523706159020478084061020122347902601182448091015650787022962180599741651597328364289413042032923330906135304995252477571
q=11727544912613560398705401423145382428897876620077115390278679983274961030035884083100580422155496261311510530671232666801444557695190734596546855494472819
e=65537
c = 63388263723813143290256836284084914544524440253054612802424934400854921660916379284754467427040180660945667733359330988361620691457570947823206385692232584893511398038141442606303536260023122774682805630913037113541880875125504376791939861734613177272270414287306054553288162010873808058776206524782351475805
n = p*q
mps = AMM(c,e,p)
for mpp in mps:
solution = long_to_bytes(int(mpp))
if b'moectf' in solution:
#solution = int(mpp)
print(solution)
类型二
_gcd=gcd(e,phi)=2
比较小,直接iroot
开e//_gcd
次根
示例
from Crypto.Util.number import *
flag = b'NSSCTF{******}'
p = getPrime(512)
q = getPrime(512)
e = 65537*2
n = p*q
m = bytes_to_long(flag)
c = pow(m, e, n)
print(f'p = {p}')
print(f'q = {q}')
print(f'e = {e}')
print(f'c = {c}')
'''
p = 9927950299160071928293508814174740578824022211226572614475267385787727188317224760986347883270504573953862618573051241506246884352854313099453586586022059
q = 9606476151905841036013578452822151891782938033700390347379468858357928877640534612459734825681004415976431665670102068256547092636766287603818164456689343
e = 131074
c = 68145285629092005589126591120307889109483909395989426479108244531402455690717006058397784318664114589567149811644664654952286387794458474073250495807456996723468838094551501146672038892183058042546944692051403972876692350946611736455784779361761930869993818138259781995078436790236277196516800834433299672560
'''
exp
import gmpy2
import libnum
p = 9927950299160071928293508814174740578824022211226572614475267385787727188317224760986347883270504573953862618573051241506246884352854313099453586586022059
q = 9606476151905841036013578452822151891782938033700390347379468858357928877640534612459734825681004415976431665670102068256547092636766287603818164456689343
e = 131074
c = 68145285629092005589126591120307889109483909395989426479108244531402455690717006058397784318664114589567149811644664654952286387794458474073250495807456996723468838094551501146672038892183058042546944692051403972876692350946611736455784779361761930869993818138259781995078436790236277196516800834433299672560
n = p*q
phi = (p-1)*(q-1)
_gcd = gmpy2.gcd(e, phi)
d = gmpy2.invert(e//_gcd, phi)
m_gcd = gmpy2.powmod(c, d, n)
m = gmpy2.iroot(m_gcd1, _gcd) # 得到元组 (mpz(1920535408007397829480400151650246901210634018403879187581), True)
flag = libnum.n2s(int(m[0]))
print(flag)
类型三
gcd(e,phi)=16
也比较小,但尝试iroot
开根跑不出来,这时考虑有限域内开方
来求解.
示例(2023 一带一路金砖国家 crypto1)
from Crypto.Util.number import *
from flag import flag
import gmpy2
assert(len(flag)==38)
flag = bytes_to_long(flag)
p = getPrime(512)
q = getPrime(512)
e = 304
enc = pow(flag,e,p*q)
print(p)
print(q)
print(enc)
#9794998439882070838464987778400633526071369507639213778760131552998185895297188941828281554258704149333679257014558677504899624597863467726403690826271979
#10684338300287479543408040458978465940026825189952497034380241358187629934633982402116457227553161613428839906159238238486780629366907463456434647021345729
#88310577537712396844221012233266891147970635383301697208951868705047581001657402229066444746440502616020663700100248617117426072580419555633169418185262898647471677640199331807653373089977785816106098591077542771088672088382667974425747852317932746201547664979549641193108900510265622890793400796486146522028
exp
from Crypto.Util.number import *
p = 9794998439882070838464987778400633526071369507639213778760131552998185895297188941828281554258704149333679257014558677504899624597863467726403690826271979
q = 10684338300287479543408040458978465940026825189952497034380241358187629934633982402116457227553161613428839906159238238486780629366907463456434647021345729
c = 88310577537712396844221012233266891147970635383301697208951868705047581001657402229066444746440502616020663700100248617117426072580419555633169418185262898647471677640199331807653373089977785816106098591077542771088672088382667974425747852317932746201547664979549641193108900510265622890793400796486146522028
e = 304
n = p*q
P.<a>=PolynomialRing(Zmod(p),implementation='NTL')
f=a^e-c
mps=f.monic().roots()
P.<a>=PolynomialRing(Zmod(q),implementation='NTL')
g=a^e-c
mqs=g.monic().roots()
flag=[]
for mpp in mps:
x=mpp[0]
for mqq in mqs:
y=mqq[0]
solution = CRT_list([int(x), int(y)], [p, q])
flag.append(solution)
for i in flag:
m=long_to_bytes(i)
if b'flag'in m:
print(m)
# flag{947b6543117e32730a93d1b43c98bc57}
注意到flag
是38位,也就是304bit
,而p
、q
都是512bit
,满足
m
<
p
或
者
m
<
q
m<p或者m<q
m<p或者m<q,可以转化到模p
或者模q
下求解,简化过程
g c d ( e , q − 1 ) = 16 gcd(e,q-1)=16 gcd(e,q−1)=16
( m 16 ) q − 1 16 = c m o d q (m^{16})^{\frac{q-1}{16}}=c\textbf{ }mod \textbf{ }q (m16)16q−1=c mod q
求 q − 1 16 \frac{q-1}{16} 16q−1的逆元,计算 c d ′ = m 16 m o d q c^{d{'}}=m^{16}\textbf{ }mod\textbf{ }q cd′=m16 mod q
那么,就可以在模q
有限域下开根求解
exp
from Crypto.Util.number import *
import gmpy2
p = 9794998439882070838464987778400633526071369507639213778760131552998185895297188941828281554258704149333679257014558677504899624597863467726403690826271979
q = 10684338300287479543408040458978465940026825189952497034380241358187629934633982402116457227553161613428839906159238238486780629366907463456434647021345729
c = 88310577537712396844221012233266891147970635383301697208951868705047581001657402229066444746440502616020663700100248617117426072580419555633169418185262898647471677640199331807653373089977785816106098591077542771088672088382667974425747852317932746201547664979549641193108900510265622890793400796486146522028
e = 304
n = p*q
_gcd = gmpy2.gcd(e,(q-1))
# print(_gcd) # 16
d = gmpy2.invert(e//_gcd,q-1)
m_16 = pow(c,d,q)
P.<x> = PolynomialRing(Zmod(q),implementation='NTL')
e = _gcd
f = x^e - m_16
mqs = f.monic().roots()
# print(len(mqs)) # 16
for i in mqs:
flag = long_to_bytes(int(i[0]))
if b'flag' in flag:
print(flag)
补充
解释一下所用代码
P.<a>=PolynomialRing(Zmod(p),implementation='NTL')
f=a^e-c
mps=f.monic().roots()
定义了一个名为P的多项式环,a是这个环里面的变量,Zmod(p)是在模p下定义了一个整数环。多项式的每一个系数都是p的倍数,这样才能确保在模p下进行运算时不会出现浮点数。
参数implementation='NTL'指示SageMath使用NTL(Number Theory Library)作为该环的实现。
f=a^e-c定义了一个多项式
monic()是将多项式首一化
f.roots()得到方程的一个根的列表,该列表包含f(x)=a^e-c在模p意义下的所有根。
列表中的每个元素的格式为 (a,1),在此二元组表示中,1表示多重根,即该解在方程中的出现次数。
再详细看一下PolynomialRing()
PolynomialRing
是SageMath
的一个函数,用于构建一个多项式环
,看一个示例
# 创建一个多项式环,定义一个变量 'x'
R = PolynomialRing(QQ, 'x')
# 创建一个多项式
f = x^2 + 2*x + 1
# 可以对多项式进行代数运算,如加法、乘法
g = 2*x + 3
在上面的示例中,我们首先使用PolynomialRing()
创建了一个多项式环R
,其中QQ
表示有理数域(可以使用其他域,如整数域ZZ
或有限域GF(p)
)。然后,我们定义了多项式里的变量x
,并使用这个环创建了一个多项式f
。
类型四
g
c
d
(
e
,
φ
(
n
)
)
=
e
=
1009
gcd(e,\varphi(n))=e=1009
gcd(e,φ(n))=e=1009算是很大了,且
g
c
d
(
e
,
p
−
1
)
=
g
c
d
(
e
,
q
−
1
)
=
e
=
1009
gcd(e,p-1)=gcd(e,q-1)=e=1009
gcd(e,p−1)=gcd(e,q−1)=e=1009.直接用AMM
(很快,大概十几秒出结果)
之前做过的题,找不到附件了。。
from Crypto.Util.number import *
import random
import math
def onemod(e, q):
p = random.randint(1, q-1)
while(powmod(p, (q-1)//e, q) == 1): # (r,s)=1
p = random.randint(1, q)
return p
def AMM_rth(o, r, q): # r|(q-1)
"""
x^r % q = o
:param o:
:param r:
:param q:
:return:
"""
assert((q-1) % r == 0)
p = onemod(r, q)
t = 0
s = q-1
while(s % r == 0):
s = s//r
t += 1
k = 1
while((s*k+1) % r != 0):
k += 1
alp = (s*k+1)//r
a = powmod(p, r**(t-1)*s, q)
b = powmod(o, r*a-1, q)
c = powmod(p, s, q)
h = 1
for i in range(1, t-1):
d = powmod(int(b), r**(t-1-i), q)
if d == 1:
j = 0
else:
j = (-int(math.log(d, a))) % r
b = (b*(c**(r*j))) % q
h = (h*c**j) % q
c = (c*r) % q
result = (powmod(o, alp, q)*h)
return result
def ALL_Solution(m, q, rt, cq, e):
mp = []
for pr in rt:
r = (pr*m) % q
# assert(pow(r, e, q) == cq)
mp.append(r)
return mp
def ALL_ROOT2(r, q): # use function set() and .add() ensure that the generated elements are not repeated
li = set()
while(len(li) < r):
p = powmod(random.randint(1, q-1), (q-1)//r, q)
li.add(p)
return li
def attack(p, q, e, check=None):
cp = c % p
cq = c % q
mp = AMM_rth(cp, e, p)
mq = AMM_rth(cq, e, q)
rt1 = ALL_ROOT2(e, p)
rt2 = ALL_ROOT2(e, q)
amp = ALL_Solution(mp, p, rt1, cp, e)
amq = ALL_Solution(mq, q, rt2, cq, e)
if check is not None:
j = 1
t1 = invert(q, p)
t2 = invert(p, q)
for mp1 in amp:
for mq1 in amq:
j += 1
if j % 1000000 == 0:
print(j)
ans = (mp1 * t1 * q + mq1 * t2 * p) % (p * q)
if check(ans):
return ans
return amp, amq
def calc(mp, mq, e, p, q):
i = 1
j = 1
t1 = invert(q, p)
t2 = invert(p, q)
for mp1 in mp:
for mq1 in mq:
j += 1
if j % 1000000 == 0:
print(j)
ans = (mp1*t1*q+mq1*t2*p) % (p*q)
if check(ans):
return
return
def check(m):
try:
a = long_to_bytes(m)
if b'NSSCTF' in a:
print(a)
return True
else:
return False
except:
return False
if __name__ == '__main__':
e = 1009
n = 38041020633815871156456469733983765765506895617311762629687651104582466286930269704125415948922860928755218376007606985275046819516740493733602776653724917044661666016759231716059415706703608364873041098478331738686843910748962386378250780017056206432910543374411668835255040201640020726710967482627384460424737495938659004753604600674521079949545966815918391090355556787926276553281009472950401599151788863393804355849499551329
c = 2252456587771662978440183865248648532442503596913181525329434089345680311102588580009450289493044848004270703980243056178363045412903946651952904162045861994915982599488021388197891419171012611795147125799759947942753772847866647801312816514803861011346523945623870123406891646751226481676463538137263366023714001998348605629756519894600802504515051642140147685496526829541501501664072723281466792594858474882239889529245732945
p = 5220649501756432310453173296020153841505609640978826669340282938895377093244978215488158231209243571089268416199675077647719021740691293187913372884975853901554910056350739745148711689601574920977808625399309470283
q = 7286645200183879820325990521698389973072307061827784645416472106180161656047009812712987400850001340478084529480635891468153462119149259083604029658605921695587836792877281924620444742434168448594010024363257554563
cp = c % p
cq = c % q
mp = AMM_rth(cp, e, p)
mq = AMM_rth(cq, e, q)
rt1 = ALL_ROOT2(e, p)
rt2 = ALL_ROOT2(e, q)
amp = ALL_Solution(mp, p, rt1, cp, e)
amq = ALL_Solution(mq, q, rt2, cq, e)
calc(amp, amq, e, p, q)
有限域开方也能跑出来(需要三至四分钟吧)
exp1
from Crypto.Util.number import *
e = 1009
n = 38041020633815871156456469733983765765506895617311762629687651104582466286930269704125415948922860928755218376007606985275046819516740493733602776653724917044661666016759231716059415706703608364873041098478331738686843910748962386378250780017056206432910543374411668835255040201640020726710967482627384460424737495938659004753604600674521079949545966815918391090355556787926276553281009472950401599151788863393804355849499551329
c = 2252456587771662978440183865248648532442503596913181525329434089345680311102588580009450289493044848004270703980243056178363045412903946651952904162045861994915982599488021388197891419171012611795147125799759947942753772847866647801312816514803861011346523945623870123406891646751226481676463538137263366023714001998348605629756519894600802504515051642140147685496526829541501501664072723281466792594858474882239889529245732945
p = 5220649501756432310453173296020153841505609640978826669340282938895377093244978215488158231209243571089268416199675077647719021740691293187913372884975853901554910056350739745148711689601574920977808625399309470283
q = 7286645200183879820325990521698389973072307061827784645416472106180161656047009812712987400850001340478084529480635891468153462119149259083604029658605921695587836792877281924620444742434168448594010024363257554563
P.<a> = PolynomialRing(Zmod(p),implementation='NTL')
f = a^e - c
mps = f.monic().roots(multiplicities = False)
print(mps)
P.<a> = PolynomialRing(Zmod(q),implementation='NTL')
f = a^e - c
mqs = f.monic().roots(multiplicities = False)
for i in mps:
for j in mqs:
m = crt([int(i),int(j)],[p,q])
flag = long_to_bytes(m)
if b'NSSCTF' in flag:
print(flag)
multiplicities = False
不打印根的重数.
类型五(2023香山杯-list)
n
=
p
r
∗
q
n=p^{r}*q
n=pr∗q时,e
和phi
不互素问题
附件
import os
import gmpy2
from Crypto.Util.number import *
import random
from secrets import flag
def pad(s,l):
return s + os.urandom(l - len(s))
def gen():
g = getPrime(8)
while True:
p = g * random.getrandbits(138) + 1
if isPrime(p):
break
while True:
q = g * random.getrandbits(138) + 1
if isPrime(q):
break
N = p ** 5 * q
phi = p ** 4 * (p - 1) * (q - 1)
d = random.getrandbits(256)
e = inverse(d, phi)
E = e * g
hint = gmpy2.gcd(E, phi)
return N, E, hint
flag = pad(flag,64)
m = bytes_to_long(flag)
n,e,hint = gen()
c = pow(m,e,n)
print(f'hint = {hint}')
print(f'n = {n}')
print(f'e = {e}')
print(f'c = {c}')
# hint = 251
# n = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
# e = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
# c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162
解题思路
先恢复p
、q
e ∗ d = 1 m o d p h i , g c d ( e , p h i ) = 1 ⇒ e*d=1\textbf{ }mod\textbf{ }phi,gcd(e,phi)=1\Rightarrow e∗d=1 mod phi,gcd(e,phi)=1⇒
e
∗
x
−
1
=
k
∗
p
h
i
e*x-1=k*phi
e∗x−1=k∗phi
在多项式时间内求解上面方程(copper
)求出d
.
因
为
n
=
p
r
∗
q
,
所
以
g
c
d
(
e
∗
x
−
1
,
n
)
=
g
c
d
(
p
r
−
1
∗
(
p
−
1
)
∗
(
q
−
1
)
,
p
r
∗
q
)
=
p
r
−
1
因为n=p^{r}*q,所以gcd(e*x-1,n)=gcd(p^{r-1}*(p-1)*(q-1),p^{r}*q)=p^{r-1}
因为n=pr∗q,所以gcd(e∗x−1,n)=gcd(pr−1∗(p−1)∗(q−1),pr∗q)=pr−1
用 small_roots()
开r-1
次方就能恢复 p
from gmpy2 import *
hint = 251
n = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
e = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162
P.<d> = PolynomialRing(Zmod(n))
f = e*d - 251
res = f.monic().small_roots(X = 2^256,beta = 0.4)
p_4 = gcd(int(f(res[0])),n)
p = iroot(p_4,4)[0]
q = n//p**4
print(f"p,q = {p},{q}")
接下来就是有限域开方的问题了
# sage
# 开251次方
from Crypto.Util.number import *
import itertools
hint = 251
n = 108960799213330048807537253155955524262938083957673388027650083719597357215238547761557943499634403020900601643719960988288543702833581456488410418793239589934165142850195998163833962875355916819854378922306890883033496525502067124670576471251882548376530637034077
e = 3359917755894163258174451768521610910491402727660720673898848239095553816126131162471035843306464197912997253011899806560624938869918893182751614520610693643690087988363775343761651198776860913310798127832036941524620284804884136983215497742441302140070096928109039
c = 72201537621260682675988549650349973570539366370497258107694937619698999052787116039080427209958662949131892284799148484018421298241124372816425123784602508705232247879799611203283114123802597553853842227351228626180079209388772101105198454904371772564490263034162
p,q=69367143733862710652791985332025152581988181 ,67842402383801764742069883032864699996366777
p_list = [p,q]
n_list = [p**5,q]
print(n_list)
# print(reduce((lambda x, y: x * y), n_list) - n) # 0
# print(euler_phi(p_list[0])) # 直接求欧拉函数
res=[]
for pi in n_list:
d = inverse(int(e//251),euler_phi(pi)) # 对n_listt 每一个 pi 求欧拉函数
m = pow(c,d,pi)
temp = (Zmod(pi)(m).nth_root(251, all=True))
#print('temp =',temp) # 列表 251
if temp is not None:
res.append(temp)
else:
print("None")
for vc in itertools.product(*res):
_c = [int(x) for x in vc]
m = long_to_bytes(int(crt(_c, n_list)))
if b"flag" in m:
print(m)
# b'flag{4b68c7eece6be865f6da2a4323edd491}\x9d\xcf\xdc\xcb\xb8\xbdd\xec\xadh\xa6C\x99\xa0)7\xfb\x02\xba\x90q8\x10+\x7f}'