p+q高位攻击变体
题目一
from Crypto.Util.number import *
from secret import flag
def get_gift(p, q):
noise = getPrime(40)
p1, q1 = p + 2 * noise + 1, q - pow(noise, 2)
gift = 2024 * (p1 + q1)
return gift
p = getPrime(512)
q = getPrime(512)
n = p * q
e = 0x10001
m = bytes_to_long(flag)
c = pow(m, e, n)
gift = get_gift(p, q)
print(f'c = {c}')
print(f'n = {n}')
print(f'gift = {gift}')
'''
c = 101383046356447336426623798470530695448361708798731382238747567108067236241251384089401506320741815081024352908156466877907424203888923965647318146770258139921360377246187637085549628797640957048672797430217647039035455011311505942632107576730906489223641894279483592789523228409885925263914621255862261546919
n = 131097719698687108485813302886652389604731026998272796315024695395496199386497660846418712521921387496051077394308820230360184411431376692252923609505060476542577219656866593501271690536991944882324175509626138475159461332403161471880082192150081456601522403673111515117219716055561941951891570977025178643791
gift = 46635322848619790584491725916282901439691751328335921415278638528896063068132242718070261114525516272650970256270551306096774004921902972838212903368063625872
'''
解题思路
法1
- 首先我们注意到
noise=getPrime(40)
,可知noise
是40
位的 - 已知
gift=2024*(p1+q1)=2024*(p+q+1+2*noise-noise**2)=2024*[(p+q)-(noise-1)**2+2]
- 其中
noise-1
在平方后大概是80
位 - 而
p
和q
都是512
位的,所以可以构造右偏>>
抵消掉(noise-1)**2
,仅泄露p+q
的高位 - 因此构造右偏(需要注意的是宁偏多也不要偏少,如果刚好取
80
则可能无法完全抵消掉noise
)(取大也不影响求解结果) gift=(gift//2024)-2
x1=(gift>>90)<<90
- 然后copper就可以还原
p
,q
- 脚本参考已知p+q高位攻击 - sevensnight - 博客园的方法1
法2
- 剪枝思想的逐位爆破(效率比较低)
- 脚本参考(p+q)>>nbits - sevensnight - 博客园
解答一
法1
脚本1
import gmpy2
gift = 46635322848619790584491725916282901439691751328335921415278638528896063068132242718070261114525516272650970256270551306096774004921902972838212903368063625872
c=101383046356447336426623798470530695448361708798731382238747567108067236241251384089401506320741815081024352908156466877907424203888923965647318146770258139921360377246187637085549628797640957048672797430217647039035455011311505942632107576730906489223641894279483592789523228409885925263914621255862261546919
n=131097719698687108485813302886652389604731026998272796315024695395496199386497660846418712521921387496051077394308820230360184411431376692252923609505060476542577219656866593501271690536991944882324175509626138475159461332403161471880082192150081456601522403673111515117219716055561941951891570977025178643791
gift=(gift//2024)-2
x1=(gift>>90)<<90 #往大的取
e = 0x10001
PR.<x> = PolynomialRing(RealField(1000))
f = x*(x1-x) - n
phigh = int(f.roots()[0][0])
print(phigh,phigh.bit_length())
#10245385971395060175404025994260899026018327732803454139079733202684248559362997918630991266112903887717159293307112056037936651001716489445626599091213586
PR.<x> = PolynomialRing(Zmod(n))
f = phigh + x
res = f.small_roots(X=2**90, beta=0.4,epsilon=0.01)[0] #X的上限一般大于根的位数
p=int(res+phigh)
q=n//p
assert p*q==n
d=gmpy2.invert(e,(p-1)*(q-1))
m=int(pow(c,d,n))
print(bytes.fromhex(hex(m)[2:]))
#BuildCTF{M@y_b3_S0m3th1ng_go_wr0ng}
脚本2
c = 101383046356447336426623798470530695448361708798731382238747567108067236241251384089401506320741815081024352908156466877907424203888923965647318146770258139921360377246187637085549628797640957048672797430217647039035455011311505942632107576730906489223641894279483592789523228409885925263914621255862261546919
n = 131097719698687108485813302886652389604731026998272796315024695395496199386497660846418712521921387496051077394308820230360184411431376692252923609505060476542577219656866593501271690536991944882324175509626138475159461332403161471880082192150081456601522403673111515117219716055561941951891570977025178643791
gift = 46635322848619790584491725916282901439691751328335921415278638528896063068132242718070261114525516272650970256270551306096774004921902972838212903368063625872
gift = (gift//2024) >> 233 #随便取的(>=86即可)
e = 65537
BITS = 233
PR.<x> = PolynomialRing(RealField(1000))
f = x * ((gift << BITS) - x) - n
p2high = int(f.roots()[0][0])
PR.<x> = PolynomialRing(Zmod(n))
f = p2high + x
res = f.small_roots(X=2^(BITS+10), beta=0.4, epsilon=0.01)[0]
p2 = int(p2high + res)
print(p2)
#10245385971395060175404025994260899026018327732803454139079733202684248559362997918630991266112903887717159293307112056037936650791910220424100097140852483
q2 = n // p2
print(q2)
#12795781443930923314957498667934210380943999998587811382302993343213015802362418444051944067150691108651897157419444913377465525553505809994424022558382277
d2 = inverse_mod(e, (p2-1)*(q2-1))
leak2 = pow(c, d2, n)
print(bytes.fromhex(hex(leak2)[2:]))
#BuildCTF{M@y_b3_S0m3th1ng_go_wr0ng}
法2
N = 131097719698687108485813302886652389604731026998272796315024695395496199386497660846418712521921387496051077394308820230360184411431376692252923609505060476542577219656866593501271690536991944882324175509626138475159461332403161471880082192150081456601522403673111515117219716055561941951891570977025178643791
gift = 46635322848619790584491725916282901439691751328335921415278638528896063068132242718070261114525516272650970256270551306096774004921902972838212903368063625872
gift=gift//2024
gift=(gift>>90)<<90
PR.<x> = PolynomialRing(Zmod(N))
ok = False
def pq_add(tp,tq,tgift,idx):
global ok
if ok:
return
if tp*tq>N:
#print('>')
return
if (tp+(2<<idx))*(tq+(2<<idx))<N:
#print('<', hex((tp+(1<<(idx+2))))[:20], hex(tq+(2<<idx))[:20], hex(N)[:20])
return
if idx<=90:
try:
f = tp + x
rr = f.monic().small_roots(X=2^90, beta=0.4)
if rr != []:
print(rr)
print(tp)
print('p =',f(rr[0]))
ok = True
return
except:
pass
return
idx -=1
b = tgift >>idx
one = 1<<idx
#print(hex(tp)[:20],hex(tq)[:20],hex(tgift)[:20],idx,b)
if b==0 or b==1:
pq_add(tp,tq,tgift,idx)
if b==1 or b==2:
pq_add(tp+one,tq,tgift-one,idx)
pq_add(tp,tq+one,tgift-one,idx)
if b==2 or b==3:
pq_add(tp+one,tq+one,tgift-(one<<1),idx)
tp = 1<<511
tq = 1<<511
tgift = gift -tp -tq
pq_add(tp,tq,tgift,511)
'''
[700928575142400370721495237]
12795781443930923314957498667934210380943999998587811382302993343213015802362418444051944067150691108651897157419444913377465524852577234852023651836887040
p = 12795781443930923314957498667934210380943999998587811382302993343213015802362418444051944067150691108651897157419444913377465525553505809994424022558382277
'''
from Crypto.Util.number import *
e = 65537
c = 101383046356447336426623798470530695448361708798731382238747567108067236241251384089401506320741815081024352908156466877907424203888923965647318146770258139921360377246187637085549628797640957048672797430217647039035455011311505942632107576730906489223641894279483592789523228409885925263914621255862261546919
n = 131097719698687108485813302886652389604731026998272796315024695395496199386497660846418712521921387496051077394308820230360184411431376692252923609505060476542577219656866593501271690536991944882324175509626138475159461332403161471880082192150081456601522403673111515117219716055561941951891570977025178643791
p = 12795781443930923314957498667934210380943999998587811382302993343213015802362418444051944067150691108651897157419444913377465525553505809994424022558382277
q = n // p
#10245385971395060175404025994260899026018327732803454139079733202684248559362997918630991266112903887717159293307112056037936650791910220424100097140852483
phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
#BuildCTF{M@y_b3_S0m3th1ng_go_wr0ng}
题目二
from Crypto.Util.number import bytes_to_long, getPrime, inverse
from secret import flag
def genKeys(nbits):
e = 0x10001
p = getPrime(nbits // 2)
q = getPrime(nbits // 2)
n = p * q
phi = n - (p + q) + 1
d = inverse(e, phi)
pubkey = (n, e)
prikey = (d, p, q)
return pubkey, prikey
def encrypt(msg, pubkey):
m = bytes_to_long(msg)
n, e = pubkey
c = pow(m, e, n)
return c
def get_gift(prikey):
a = bytes_to_long(b'miniL')
b = bytes_to_long(b'mini7')
p, q = prikey[1:]
phi = (p - 1)*(q - 1)
giftp = p + a
giftq = q + b
gift = pow((giftp + giftq + a*b), 2, phi)
return gift >> 740
if __name__ == "__main__":
nbits = 1024
pubkey, prikey = genKeys(nbits)
c = encrypt(flag, pubkey)
gift = get_gift(prikey)
with open('output.txt', 'a') as f:
f.write('pubkey = ' + str(pubkey) + '\n')
f.write('c = ' + str(c) + '\n')
f.write('gift = ' + str(gift) + '\n')
'''
pubkey = (65537,103894244981844985537754880154957043605938484102562158690722531081787219519424572416881754672377601851964416424759136080204870893054485062449999897173374210892603308440838199225926262799093152616430249061743215665167990978654674200171059005559869946978592535720766431524243942662028069102576083861914106412399)
c = 50810871938251627005285090837280618434273429940089654925377752488011128518767341675465435906094867261596016363149398900195250354993172711611856393548098646094748785774924511077105061611095328649875874203921275281780733446616807977350320544877201182003521199057295967111877565671671198186635360508565083698058
gift = 2391232579794490071131297275577300947901582900418236846514147804369797358429972790212
'''
解题思路
- 已知
giftp = p + a
giftq = q + b
gift = pow((giftp + giftq + a*b), 2, phi)
- 则
gift = (p + q + a + b + a * b)^2 % phi
- 令
C = a + b + a * b
- 则
gift = (p + q + C)^2 - k*phi
from Crypto.Util.number import *
a = bytes_to_long(b'miniL') # 469920278860
b = bytes_to_long(b'mini7') # 469920278839
C = a + b + a * b # 220825068474931677601239
print(len(bin(a))) # 41
print(len(bin(b))) # 41
print(len(bin(C))) # 80
- 这里的
C
才80
位,因此在题右偏中已抵消了,再左偏回去就是(p+q)^2
的高位(还是差很多) - 通过将
<font style="background-color:rgb(236, 236, 236);">gift << 740</font>
视为某个平方数的高位,计算其平方根近似值<font style="background-color:rgb(236, 236, 236);">a</font>
(对应<font style="background-color:rgb(236, 236, 236);">p - q</font>
的估值),基于公式(p-q)^2 = <font style="background-color:rgb(236, 236, 236);">(p+q)^2 - 4pq</font>
(其中<font style="background-color:rgb(236, 236, 236);">pq = n</font>
),但是需要注意这里的gift
实际上减去了k
倍的phi
,因此无需再-4n
- 计算
<font style="background-color:rgb(236, 236, 236);">(gift << 740) + 4n</font>
的平方根得到<font style="background-color:rgb(236, 236, 236);">b</font>
(对应<font style="background-color:rgb(236, 236, 236);">p + q</font>
的估值),基于公式<font style="background-color:rgb(236, 236, 236);">(p+q)^2 = (p-q)^2 + 4pq</font>
(其中<font style="background-color:rgb(236, 236, 236);">pq = n</font>
) - 接着
a+b//2
得到p
的近似值 - 然后就是
p
的高位攻击求解即可
解答二
import gmpy2
n = 103894244981844985537754880154957043605938484102562158690722531081787219519424572416881754672377601851964416424759136080204870893054485062449999897173374210892603308440838199225926262799093152616430249061743215665167990978654674200171059005559869946978592535720766431524243942662028069102576083861914106412399
c = 50810871938251627005285090837280618434273429940089654925377752488011128518767341675465435906094867261596016363149398900195250354993172711611856393548098646094748785774924511077105061611095328649875874203921275281780733446616807977350320544877201182003521199057295967111877565671671198186635360508565083698058
gift = 2391232579794490071131297275577300947901582900418236846514147804369797358429972790212
a = int(gmpy2.iroot(gift << 740, 2)[0]) # p-q
b = int(gmpy2.iroot((gift << 740) + 4*n, 2)[0]) # p+q
high = (a + b)//2
for i in range(2**10):
tmp = ((high >> 235 << 10) + i) << 225
R.<x> = PolynomialRing(Zmod(n))
f = tmp + x
res = f.small_roots(X=2**225, beta=0.4)
if res != []:
print(res)
break
p = int(tmp + res[0])
q = n // p
assert p*q == n
d = pow(65537, -1, (p-1)*(q-1))
m = pow(c,d,n)
c = bytes.fromhex(hex(m)[2:])
print(c)
#miniL{D0_Y@U_Li)e_T&@_RRRSA??}