文章目录
- 知识导入:
- 题一
- 题目描述:
- 题目分析:
- 题二
- 题目描述:
- 题目分析:
- 题三
- 题目描述:
- 题目分析:
- 收获与体会:
知识导入:
- 总结:
Franklin-Reiter相关消息攻击(Franklin-Reiter related-message attack):如果两个信息之间存在已知的固定差异,并且在相同的模数n下进行RSA加密,那么就有可能恢复出这两个消息 - 简单点说就是m和m+a两个明文在相同的e和n下进行加密,那么m就可以破解
如果两条消息之间仅存在已知的固定差异
并且在相同的RSA模N下加密
那么就有可能同时恢复它们
m1 = bytes_to_long(flag)
m2 = a * m1 + b
c1 = pow(m1,e,n)
c2 = pow(a * m1 + b,e,n)
其中c1,c2,e,n都已知,那么m1,m2可被破解
题一
题目描述:
from secret import flag
from Crypto.Util.number import *
m1 = bytes_to_long(flag)
N = getPrime(512)*getPrime(512)
e = 17
c1 = pow(m1, e, N)
a = getRandomNBitInteger(512)
b = getRandomNBitInteger(512)
m2 = a * m1 + b
c2 = pow(m2, e, N)
print(N, a, b, c1, c2, sep="\n")
# 51296885372346449295388453471330409021784141081351581975478435681552082076338697136130122011636685327781785488670769096434920591920054441921039812310126089859349902066456998315283909435249794317277620588552441456327265553018986591779396701680997794937951231970194353001576159809798153970829987274504038146741
# 13256631249970000274738888132534852767685499642889351632072622194777502848070957827974250425805779856662241409663031192870528911932663995606616763982320967
# 12614470377409090738391280373352373943201882741276992121990944593827605866548572392808272414120477304486154096358852845785437999246453926812759725932442170
# 18617698095122597355752178584860764221736156139844401400942959000560180868595058572264330257490645079792321778926462300410653970722619332098601515399526245808718518153518824404167374361098424325296872587362792839831578589407441739040578339310283844080111189381106274103089079702496168766831316853664552253142
# 14091361528414093900688440242152327115109256507133728799758289918462970724109343410464537203689727409590796472177295835710571700501895484300979622506298961999001641059179449655629481072402234965831697915939034769804437452528921599125823412464950939837343822566667533463393026895985173157447434429906021792720
题目分析:
典型的Franklin-Reiter相关消息攻击,直接上解题代码:
n=51296885372346449295388453471330409021784141081351581975478435681552082076338697136130122011636685327781785488670769096434920591920054441921039812310126089859349902066456998315283909435249794317277620588552441456327265553018986591779396701680997794937951231970194353001576159809798153970829987274504038146741
a=13256631249970000274738888132534852767685499642889351632072622194777502848070957827974250425805779856662241409663031192870528911932663995606616763982320967
b=12614470377409090738391280373352373943201882741276992121990944593827605866548572392808272414120477304486154096358852845785437999246453926812759725932442170
c1=18617698095122597355752178584860764221736156139844401400942959000560180868595058572264330257490645079792321778926462300410653970722619332098601515399526245808718518153518824404167374361098424325296872587362792839831578589407441739040578339310283844080111189381106274103089079702496168766831316853664552253142
c2=14091361528414093900688440242152327115109256507133728799758289918462970724109343410464537203689727409590796472177295835710571700501895484300979622506298961999001641059179449655629481072402234965831697915939034769804437452528921599125823412464950939837343822566667533463393026895985173157447434429906021792720
e=17
import libnum
def franklinReiter(n,e,c1,c2,a,b):
R.<X> = Zmod(n)[]
f1 = X^e - c1
f2 = (X*a+ b)^e - c2
# coefficient 0 = -m, which is what we wanted!
return Integer(n-(compositeModulusGCD(f1,f2)).coefficients()[0]) # 系数
# GCD is not implemented for rings over composite modulus in Sage
# so we do our own implementation. Its the exact same as standard GCD, but with
# the polynomials monic representation
def compositeModulusGCD(a, b):
if(b == 0):
return a.monic()
else:
return compositeModulusGCD(b, a % b)
m=franklinReiter(n,e,c1,c2,a,b)
print(libnum.n2s(int(m)))
# flag{a593591a-3749-cc52-0c27-e897fac2c967}
或者(本质一样,看起来更简洁一些):
import libnum
n=51296885372346449295388453471330409021784141081351581975478435681552082076338697136130122011636685327781785488670769096434920591920054441921039812310126089859349902066456998315283909435249794317277620588552441456327265553018986591779396701680997794937951231970194353001576159809798153970829987274504038146741
a=13256631249970000274738888132534852767685499642889351632072622194777502848070957827974250425805779856662241409663031192870528911932663995606616763982320967
b=12614470377409090738391280373352373943201882741276992121990944593827605866548572392808272414120477304486154096358852845785437999246453926812759725932442170
c1=18617698095122597355752178584860764221736156139844401400942959000560180868595058572264330257490645079792321778926462300410653970722619332098601515399526245808718518153518824404167374361098424325296872587362792839831578589407441739040578339310283844080111189381106274103089079702496168766831316853664552253142
c2=14091361528414093900688440242152327115109256507133728799758289918462970724109343410464537203689727409590796472177295835710571700501895484300979622506298961999001641059179449655629481072402234965831697915939034769804437452528921599125823412464950939837343822566667533463393026895985173157447434429906021792720
e=17
import binascii
def franklinReiter(n,e,c1,c2,a,b):
PR.<x> = PolynomialRing(Zmod(n))
g1 = (x)^e - c1
g2 = (a*x+b)^e - c2
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic() #
return -gcd(g1, g2)[0]
m=franklinReiter(n,e,c1,c2,a,b)
print(libnum.n2s(int(m)))
# flag{a593591a-3749-cc52-0c27-e897fac2c967}
- 其中:
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic() #
return -gcd(g1, g2)[0]
- 使用辗转相除法求多项式的最大公因子
- 在代数中,一个多项式的首项系数通常被称为该多项式的引导系数(leading coefficient),而将多项式变成首项系数为1的形式被称为将多项式化为首一形式(monic form)
- 调用函数g1.monic()将g1转换为首一多项式(monic polynomial),并返回该多项式。
- 使用g.monic()[0],则会返回g(x)除以引导系数后得到的多项式的常数项
比如:g.monic() = x + 32412345
那么:g.monic()[0] = 32412345
题二
题目描述:
找不到题源,只有图片,将就看吧
题目分析:
- 首先来看一下下面这一串
assert(reduce(lambda x,y:x&y,[(i-5)*i+6==0 for i in x]))
1. [(i-5)*i+6==0 for i in x] 使用列表推导式判断等式是否成立,
若成立,返回True,否则False,故列表中最终得到的是[True,True]
2. 为什么是True呢,因为用了assert断言函数,
如果为真则正常执行,如果为假则报错,过程中断
给的题目是会正常运行下去的,故该结果应为True,
3. 由此可以反推出x = [2,3](解方程得到),显然也可以知道下一串中y = [4,11]
4. reduce函数是应用lambda表达式对列表([True,True])中的每一个元素依次进行异或操作
5. PS:“lambda x,y:x&y”中的‘x’
与
“[(i-5)*i+6==0 for i in x]”中的‘x’
并不是同一个!!!(如果这里清楚了,那这句话就是真的弄明白了!)
- 所以推出(解方程相信大家都会)
x = [2,3]
y = [4,11]
- 接下来看最后一串:
reduce(lambda x,y:x*m+y,x)
这里就不过多说理论方面的了,我直接举个例子吧
x = [1,2,3,4]
reduce(lambda x,y:x+y,x),使用reduce对x进行累积
x = 1,y = 2,==>
x+y = 3 ①
x = ①,y = 3,==>
x+y = 6 ②
x = ②,y = 4,==>
x+y = 10 ③
即 1+2+3+4
最终得到的结果为10
再举个更难一点的例子
x = [1,2,3,4]
reduce(lambda x,y:x*m+y,x),使用reduce对x进行累积
x = 1,y = 2,==>
x*m+y = 1*m+2 ①
x = ①,y = 3,==>
x*m+y = (1*m+2)*m+3 ②
x = ②,y = 4,==>
x*m+y = ((1*m+2)*m+3)*m+4 ③
最终得到的即为式③
- 由此可以得知题目中
reduce(lambda x,y:x*m+y,x) = 2*m+3
reduce(lambda x,y:x*m+y,y) = 4*m+11
- 最终得到:
c1 = pow(2*m+3,17,n)
c2 = pow(4*m+11,17,n)
- 又是典型的Franklin-Reiter相关消息攻击,直接上代码解题
(没找到题源,将就看图片吧)
题三
(NEEPUSec CTF 2021 RSA)
题目描述:
from Crypto.Util.number import *
from sympy import nextprime
import gmpy2
import random
def encode (p1,p2,e):
not_hint = (p1 + 1) * (p2 + 1)
S = gmpy2.invert(e, not_hint)
not_p = S%(p1+1)
return not_p
flag = b'Neepu{********************}'
flag = bytes_to_long(flag)
p = getPrime(512)
q = getPrime(512)
n = p*q
e = nextprime(random.randint(1,1000))
d = gmpy2.invert(e, (p-1)*(q-1))
c = pow(flag, e, n)
print(c)
print(n)
m = encode(p, q, e)
c1 = pow(m, 7, n)
c2 = pow(m+e, 7, n)
print(c1)
print(c2)
c = 78543767285872349029076059073458316000847341792088805258173041942425687239313215276670106926320359777962661495032475004417723103701253550583245518206305422982968675291500865382213182669036827898932991063338163290845510339896689210314509493839746410486257998875782496654704288722251878269643040214139429715671
n = 91995272927105081122659192011056020468305570748555849650309966887236871318156855318666540461669669247866754568189179687694315627673545298267458869140096224628114424176937828378360997230874932015701507629238213240839370628366083111028544554453150572165461450371411341485911677167168492357154684642531577228543
c1 = 10186066785511829759164194803209819172224966119227668638413350199662683285189286077736537161204019147791799351066849945954518642600518196927152098131117402608793752080104402893792812059620726950782670809837962606250674588612783027976958719051829085903720655233948024280118985875980227528403883475592567727892
c2 = 46182103994299145562022812023438495797686077104477472631494150222038404419414100727667171290098624214113241032861128455086601197239761085752413519627251290509474327611253599768650908336142621210005389246714504358370629231557080301516460985022782887233790302054696967900384601182742759555421864610431428746119
题目分析:
c1 = pow(m, 7, n)
c2 = pow(m+e, 7, n)
很熟悉的两串,但其中有两个未知数,以上两题都只有一个未知数,所以先要把e给求出来,再使用Franklin-Reiter相关消息攻击求出m。如何求e呢,这里便用到了短填充攻击(Coppersmith’s short-pad attack)【目前本人也没看此攻击手法,等推到了原理再说】,不多说,直接套脚本
def short_pad_attack(c1, c2, e, n):
PRxy.< x, y > = PolynomialRing(Zmod(n))
PRx.< xn > = PolynomialRing(Zmod(n))
PRZZ.< xz, yz > = PolynomialRing(Zmod(n))
g1 = x ^ e - c1
g2 = (x + y) ^ e - c2
q1 = g1.change_ring(PRZZ)
q2 = g2.change_ring(PRZZ)
h = q2.resultant(q1)
h = h.univariate_polynomial()
h = h.change_ring(PRx).subs(y=xn)
h = h.monic()
kbits = n.nbits() // (2 * e * e)
diff = h.small_roots(X=2 ^ kbits, beta=0.4)[0] # find root < 2^kbits with factor >= n^0.4
return diff
def related_message_attack(c1, c2, diff, e, n):
PRx.< x > = PolynomialRing(Zmod(n))
g1 = x ^ e - c1
g2 = (x + diff) ^ e - c2
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic()
return -gcd(g1, g2)[0]
if __name__ == '__main__':
c2 = 10186066785511829759164194803209819172224966119227668638413350199662683285189286077736537161204019147791799351066849945954518642600518196927152098131117402608793752080104402893792812059620726950782670809837962606250674588612783027976958719051829085903720655233948024280118985875980227528403883475592567727892
c1 = 46182103994299145562022812023438495797686077104477472631494150222038404419414100727667171290098624214113241032861128455086601197239761085752413519627251290509474327611253599768650908336142621210005389246714504358370629231557080301516460985022782887233790302054696967900384601182742759555421864610431428746119
n = 91995272927105081122659192011056020468305570748555849650309966887236871318156855318666540461669669247866754568189179687694315627673545298267458869140096224628114424176937828378360997230874932015701507629238213240839370628366083111028544554453150572165461450371411341485911677167168492357154684642531577228543
e = 7
diff = short_pad_attack(c1, c2, e, n)
print("difference of two messages is %d" % diff)
m1 = related_message_attack(c1, c2, diff, e, n)
print("m1:", m1)
print("m2:", m1 + diff)
- 另一种方法求m(直接爆破e):
import binascii
def attack(c1, c2, n, e):
PR.<x> = PolynomialRing(Zmod(n))
g1 = (x)^7 - c1
g2 = (x+e)^7 - c2
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic()
return -gcd(g1, g2)[0]
c1 = 10186066785511829759164194803209819172224966119227668638413350199662683285189286077736537161204019147791799351066849945954518642600518196927152098131117402608793752080104402893792812059620726950782670809837962606250674588612783027976958719051829085903720655233948024280118985875980227528403883475592567727892
c2 = 46182103994299145562022812023438495797686077104477472631494150222038404419414100727667171290098624214113241032861128455086601197239761085752413519627251290509474327611253599768650908336142621210005389246714504358370629231557080301516460985022782887233790302054696967900384601182742759555421864610431428746119
n = 91995272927105081122659192011056020468305570748555849650309966887236871318156855318666540461669669247866754568189179687694315627673545298267458869140096224628114424176937828378360997230874932015701507629238213240839370628366083111028544554453150572165461450371411341485911677167168492357154684642531577228543
for e in range(1,1000):
m = attack(c1, c2, n, e)
try:
if pow(m,7,n) == c1:
print((e,m))
except:
pass
#结果:(71, 129256555243625096140386916253259867206651269142565502540823654159666398099455456877012993395632742360829588042575108302297567291349420390228163587340859)
#e = 71
#m = 129256555243625096140386916253259867206651269142565502540823654159666398099455456877012993395632742360829588042575108302297567291349420390228163587340859
- 求到m,又m = s % (p+1) = d % (p+1),这不就类似于dp泄露吗,简单推导一下,把加1换成减1即可得到p,q,直接上脚本了
import gmpy2
from Crypto.Util.number import *
e = 71
# m = 129256555243625096140386916253259867206651269142565502540823654159666398099455456877012993395632742360829588042575108302297567291349420390228163587340859
c = 78543767285872349029076059073458316000847341792088805258173041942425687239313215276670106926320359777962661495032475004417723103701253550583245518206305422982968675291500865382213182669036827898932991063338163290845510339896689210314509493839746410486257998875782496654704288722251878269643040214139429715671
n = 91995272927105081122659192011056020468305570748555849650309966887236871318156855318666540461669669247866754568189179687694315627673545298267458869140096224628114424176937828378360997230874932015701507629238213240839370628366083111028544554453150572165461450371411341485911677167168492357154684642531577228543
# dp = m ,类似的dp
dp = 129256555243625096140386916253259867206651269142565502540823654159666398099455456877012993395632742360829588042575108302297567291349420390228163587340859
for i in range(1,65535):
p = (dp*e-1)//i-1
if n%p == 0:
q = n//p
d = gmpy2.invert(e, (p - 1) * (q - 1))
flag = pow(c,d,n)
print(long_to_bytes(flag))
break
# Neepu{Have-a-g00d-day12138}
收获与体会:
学到挺多,对相关消息攻击掌握了,对reduce(),lambda()函数理解更透彻了,
学到了dp泄露的另一种变形,嗯,不得不说,收获挺大的
浅记一下:
做完NEEPUSec CTF 2023后,感觉这个比赛的题目质量很不错,于是就找了一下它往年的题,然后就做到了NEEPUSec CTF 2021 RSA这题。一开始看它题目描述感觉很简单,但推来推去也只能得到 m * e = 1 % (p+1),m和e还是不知道,结果就是,啥都没求出来。看完wp后知道原来这里对m,e的求解有具体知识点,于是查找了很多资料,最终把这题给弄懂了。
其实一开始是本着直接套脚本的想法,但突然想到了某位前辈说的一句话 “我们是搞网络安全的,不是找flag的ctfer,别把两者搞反了”。
但其实说完全弄懂这题还是有些勉强,里面有一些小点还是不太懂的,比如求公因子后会得到x-M,其中M即为所求的m,不太明白(挠头),比如短填充攻击(还没找资料看),总之大体步骤和含义是清楚了。