RSA是每个新手都要跨越的第一道坎,相信你也经常遇见奇奇怪怪的RSA题型,这里记录了我遇见的各种题型,难度不分先后,随时更新。
dp泄露(dp=d mod p-1) 分析: $\because dp\equiv d \mod p-1$ $\therefore edp\equiv ed\mod p-1$ $\because ed \equiv 1 \mod (p-1)(q-1)$ $\therefore edp+k_1(p-1) = 1+k_2(p-1)(q-1)$ $\therefore edp=(p-1)[k_1+k_2(q-1)]+1$ $\because dp<p-1$ $\therefore 令A=k_1+k_2(q-1),有e>A$ $\therefore A\in (0,e)$ $\because 转换得p=\frac{edp-1}{A}+1$ $\therefore 遍历A(此时A不等于0)\in(1,e),找到能整除的A,进而计算出p$代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from Crypto.Util.number import * dp= n = c = e = x = e*dp-1 for i in range (1 ,e): if x % i == 0 : p_us = x//i +1 if n % p_us == 0 : p = p_us break q=n//p phi=(p-1 )*(q-1 ) d=inverse(e,phi) m=pow (c,d,n) ans=long_to_bytes(m)print (ans)
明文爆破 已知: $n,e,c_list(每个明文字符单独加密的结果)$分析: 使用string.printable
来表示所有可打印字符,逐个加密每个字符来对照密文。代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from Crypto.Util.number import *import string n = c_list = ans = '' for i in range (len (c_list)): for j in string.printable: if pow (ord (j), e, n) == c_list[i]: print (f"Found char: {j} " ) ans += j break print (ans)
共模攻击 介绍: 使用不同的e(如e1,e2)
来加密同一段明文m
,并且使用相同的模n
。分析: 此时已知两个不同的e1,e2
,所以不需要再求d
了,这里使用扩展欧几里得算法 ,求出$k_1e_1+k_2e_2 = 1$(若此时e1,e2不互素,也一般有较小的公因数,开小次方即可)。 所以$c_1^{k_1}c_2^{k_2} \equiv m^{k_1e_1+k_2e_2} \equiv m \mod n$代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from Crypto.Util.number import *import gmpy2 c1= c2= e1= e2= n= _ , k1 , k2 = gmpy2.gcdext(e1,e2) m = pow (c1,k1,n)*pow (c2,k2,n) % n ans = long_to_bytes(m)print (ans)
公共因子攻击 原本觉得太简单是不想放的,但是后来考虑到许多题都是使用了类似的思想,所以本题型的重点是学会利用 公因数 求解一系列问题。这里以最简单的RSA为例。问题:
1 2 3 4 5 6 n1 = p*q1 n2 = p*q2 e = m = bytes_to_long(flag) c = pow (m,e,n1)
分析: 可以看出$p$是$n_1,n_2$的公共因子,因此可以通过求两者的最大公因数来求$p$ $即p=gcd(n_1,n_2)$代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from Crypto.Util.number import * n1= n2= c = e = p=GCD(n1,n2) q1=n1//p phi=(p-1 )*(q1-1 ) d=inverse(e,phi) m=pow (c,d,n1) ans=long_to_bytes(m)print (ans)
低加密指数攻击(e很小) 1.单组$(e,n,c)$ 分析: 不用多说,这种题一般只需要直接开方 或者小范围爆破
2.多组$(e,n_i,c_i)$(也叫做广播攻击) 分析:
思路一: 可以先分析各个$n$之间是否有公共因子 ,如果有,可以使用上边的公共因子攻击。
思路二: 使用中国剩余定理(CRT) ,通过中国剩余定理,可以更加逼近正确的$n$。中国剩余定理求出来一组$(n,c)$,此时攻击方法便和单组的一样了。代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 from Crypto.Util.number import *import gmpy2 n_list=[...] c_list=[...] e = n = 1 n_list_2 = [] n_list_2_inv = []for i in n_list: n *= i for i in n_list: tmp = n // i tmp_inv = inverse(tmp,i) n_list_2.append(tmp) n_list_2_inv.append(tmp_inv) c = 0 for i in range (len (c_list)): c += c_list[i]*n_list_2[i]*n_list_2_inv[i] % n c = c % n m = gmpy2.iroot(c,e)[0 ]''' for k in range(1000) #自定义爆破k的范围 c = c + k*n t = gmpy2.iroot(c,e) if t[1]: m = t[0] ''' ans = long_to_bytes(m)print (ans)
p,q相近 已知: $n,c$,且p,q是十分接近的素数,或者是相邻的素数解法:
一、 factordb、yafu直接分解n
二、 先对n开平方得到大概值,再使用gmpy2
中的next_prime()
来获取q
,此时p=n//q
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from Crypto.Util.number import *import gmpy2 n = ... c = ... e = 65537 q = gmpy2.iroot(n,2 )[0 ] q = gmpy2.next_prime(q) p = n // q phi = (p-1 )*(q-1 ) d = gmpy2.invert(e, phi) m = pow (c, d, n) flag = long_to_bytes(ans)print (flag)
Coppersmith攻击 介绍: Coppersmith方法基于LLL算法 求多项式小根 ,求“已知某些二进制位,求剩余位 ”问题,功能: 假如有一个$e$阶的多项式$f$,则有
在模$n$下,可以用$O(\log n)$的算法求出$n^{\frac1e}$以内的根
给定$\beta$,快速求出模$b$下的小根(其中$b\geq n^\beta$,且$b$是$n$的因数)
实现: 在$Sagemath$中使用small_roots()
方法实现。
P高位泄露,求低位 分析: 利用Coppersmith,此时$p=pHigh+x$,则有$\frac 1e=1$ , 令$p\equiv 0 \mod b$ ,其中$n^\beta \leq b \leq n$ , 若想$b=p$,则需要$p\geq n^\beta$ ,由于RSA中$p,q$ 一般位数相近,都在$n^{0.5}$,所以一般取$\beta \in [0.1,0.4]$。
据说$p$的泄露位数$\frac{pHigh.nbits()}{p.nbits()}$要大于0.56 左右才能解出,对512bits的$p$来说需要288位已知,对1024bits的$p$来说要576位左右。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 n= p_high= pbits= kbits=pbits-p_high.nbits() R.<x> = PolynomialRing(Zmod(n)) p_high <<= kbits p = p_high + x roots = p.small_roots(X=2 ^kbits,beta=0.4 ) p = p_high + int (roots[0 ]) q = n // p
例题 (鹤城杯 2021 babyrsa)题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from Crypto.Util.number import getPrime, bytes_to_longfrom secret import flag p = getPrime(1024 ) q = getPrime(1024 ) n = p * q e = 65537 hint1 = p >> 724 hint2 = q % (2 ** 265 ) ct = pow (bytes_to_long(flag), e, n)print (hint1)print (hint2)print (n)print (ct)""" 1514296530850131082973956029074258536069144071110652176122006763622293335057110441067910479 40812438243894343296354573724131194431453023461572200856406939246297219541329623 21815431662065695412834116602474344081782093119269423403335882867255834302242945742413692949886248581138784199165404321893594820375775454774521554409598568793217997859258282700084148322905405227238617443766062207618899209593375881728671746850745598576485323702483634599597393910908142659231071532803602701147251570567032402848145462183405098097523810358199597631612616833723150146418889589492395974359466777040500971885443881359700735149623177757865032984744576285054725506299888069904106805731600019058631951255795316571242969336763938805465676269140733371287244624066632153110685509892188900004952700111937292221969 19073695285772829730103928222962723784199491145730661021332365516942301513989932980896145664842527253998170902799883262567366661277268801440634319694884564820420852947935710798269700777126717746701065483129644585829522353341718916661536894041337878440111845645200627940640539279744348235772441988748977191513786620459922039153862250137904894008551515928486867493608757307981955335488977402307933930592035163126858060189156114410872337004784951228340994743202032248681976932591575016798640429231399974090325134545852080425047146251781339862753527319093938929691759486362536986249207187765947926921267520150073408188188 """
分析: 已知p的高1024-724 = 300位,以及q的低265位。已知的p的高300位远小于576位,因此不能直接用coppersmith定理,需要找到更多的已知位数。 由$n = pq$得,$n\equiv p_lq_l\mod 2^{265}$ 所以得,$p_l \equiv nq_l^{-1} \mod 2^{265}$ 求出了p的低265位,此时已知265+300 = 565,很接近576位,但是还是有些距离,进行几位爆破。代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from Crypto.Util.number import * n = 21815431662065695412834116602474344081782093119269423403335882867255834302242945742413692949886248581138784199165404321893594820375775454774521554409598568793217997859258282700084148322905405227238617443766062207618899209593375881728671746850745598576485323702483634599597393910908142659231071532803602701147251570567032402848145462183405098097523810358199597631612616833723150146418889589492395974359466777040500971885443881359700735149623177757865032984744576285054725506299888069904106805731600019058631951255795316571242969336763938805465676269140733371287244624066632153110685509892188900004952700111937292221969 p_high = 1514296530850131082973956029074258536069144071110652176122006763622293335057110441067910479 q_low = 40812438243894343296354573724131194431453023461572200856406939246297219541329623 c = 19073695285772829730103928222962723784199491145730661021332365516942301513989932980896145664842527253998170902799883262567366661277268801440634319694884564820420852947935710798269700777126717746701065483129644585829522353341718916661536894041337878440111845645200627940640539279744348235772441988748977191513786620459922039153862250137904894008551515928486867493608757307981955335488977402307933930592035163126858060189156114410872337004784951228340994743202032248681976932591575016798640429231399974090325134545852080425047146251781339862753527319093938929691759486362536986249207187765947926921267520150073408188188 e = 65537 mod_num = 2 ^265 p_low = inverse(q_low,mod_num)*n % mod_num pbits = 1024 R.<x> = PolynomialRing(Zmod(n))for i in range (2 ^6 ): p_high1 = p_high << 6 p_high1 = p_high1 + i kbits = pbits - p_high1.nbits() p_high1 <<= kbits p_bar = p_high1 + p_low kbits = kbits - p_low.nbits() p = p_bar + x*mod_num p = p.monic() print (i) roots = p.small_roots(X = 2 ^kbits,beta = 0.4 ) if roots: p = p_bar + int (roots[0 ])*mod_num print (p) if n%p == 0 : q = n // p phi = (p-1 )*(q-1 ) d = inverse(e,phi) m = pow (c,d,n) ans = long_to_bytes(m) print (ans)
m高位泄露,求低位 分析: 利用coppersmith方法,可以构造$(m_{high}+x)^e-c\equiv 0 \mod n$ ,此时有$b \geq n^{\beta}$ ,因此取$\beta = 1$ 。代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 c = e = m_high = mbits = kbits = mbits - m_high.nbits() m_high <<= kbits R.<x> = PolynomialRing(Zmod(n)) f = (m_high + x)^e - c f=f.monic() roots = f.small_roots(X = 2 ^ kbits,beta = 1 ) m = m_high + roots[0 ]print (m)
dp高位泄露 已知: $d_{ph},e,n,c$,求$dp$,从而求明文分析: 这里分情况讨论,分别记为 $e$较大的情况 和 $e$较小的情况 。
1.$e$较大 :使用二元coppersmith求解,可以求出$d_p$,之后就利用费马小定理,得到$a^{e\cdot d_p}\equiv a\mod p$,所以$p=gcd(a^{e\cdot d_p}-a,n)$
2.$e$较小: 因为$e\cdot d_p\equiv 1\mod (p-1)$,所以有$ed_p=k(p-1)+1$,即$ed_p+k-1\equiv 0\mod p(由d_p<p-1可得k\in(1,e))$。令$tmp = e^{-1}\mod n$,则$tmp\cdot ed_p+tmp(k-1)\equiv 0\mod p$,即$d_p+tmp(k-1)\equiv 0 \mod n$。对$k$进行爆破,利用coppersmith定理求$d_p$低位代码: 1.$e$较大:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 from Crypto.Util.number import *import gmpy2import itertoolsdef small_roots (f, bounds, m=1 , d=None ): if not d: d = f.degree() print (d) R = f.base_ring() N = R.cardinality() f /= f.coefficients().pop(0 ) f = f.change_ring(ZZ) G = Sequence ([], f.parent()) for i in range (m + 1 ): base = N ^ (m - i) * f ^ i for shifts in itertools.product(range (d), repeat=f.nvariables()): g = base * prod(map (power, f.variables(), shifts)) G.append(g) B, monomials = G.coefficient_matrix() monomials = vector(monomials) factors = [monomial(*bounds) for monomial in monomials] for i, factor in enumerate (factors): B.rescale_col(i, factor) B = B.dense_matrix().LLL() B = B.change_ring(QQ) for i, factor in enumerate (factors): B.rescale_col(i, 1 / factor) H = Sequence ([], f.parent().change_ring(QQ)) for h in filter (None , B * monomials): H.append(h) I = H.ideal() if I.dimension() == -1 : H.pop() elif I.dimension() == 0 : roots = [] for root in I.variety(ring=ZZ): root = tuple (R(root[var]) for var in f.variables()) roots.append(root) return roots return [] n = c = e = hint = kbits = leak = hint << kbits R.<x,y> = PolynomialRing(Zmod(n)) f = e * (leak + x) + (y - 1 ) res = small_roots(f,(2 ^kbits,2 ^kbits),m=1 ,d=4 ) for root in res: print (root) dp_low = root[0 ] dp = leak + dp_low tmp = pow (2 ,e*dp,n) - 2 p = gmpy2.gcd(tmp,n) q = n // p d = inverse(e,(p-1 )*(q-1 )) m = pow (c,d,n) print (long_to_bytes(m))
2.$e$较小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from Crypto.Util.number import * dp0 = e = n = kbits = P.<x> = PolynoimalRing(Zmod(n)) tmp = inverse(e,n)for k in range (1 ,e): f = dp0 << kbits + x + tmp * (k - 1 ) root = small_roots(X = 2 ^kbits,beta = 0.4 )[0 ] if root: dp = dp0 << kbits + root x = e*dp - 1 for i in range (1 ,e): if x % i == 0 : p_us = x//i +1 if n % p_us == 0 : p = p_us break if p<0 : continue else : q=n//p phi=(p-1 )*(q-1 ) d=inverse(e,phi) m=pow (c,d,n) ans=long_to_bytes(m) print (ans) break
参考: RSA | Lazzaro