RSA总结
常用工具
分解大素數
factordb http://www.factordb.com
yafu(p,qp,q相差過大或過小yafu可分解成功)
sage divisors(n)(小素數)
Openssl
解析加密密鑰:
openssl rsa -pubin -text -modulus -in pub.key
生成解密密鑰:
python rsatool.py -f PEM -o key.key -p 1 -q 1 -e 1openssl rsautl -decrypt -inkey key.pem -in flag.enc -out flagopenssl rsautl -decrypt -oaep -inkey key.pem -in flag.enc -out flag (OAEP方式)腳本生成解密密鑰:
# coding=utf-8 import math import sys from Crypto.PublicKey import RSAkeypair = RSA.generate(1024) keypair.p = keypair.q = keypair.e = keypair.n = keypair.p * keypair.q Qn = long((keypair.p - 1) * (keypair.q - 1))i = 1 while (True):x = (Qn * i) + 1if (x % keypair.e == 0):keypair.d = x / keypair.ebreaki += 1 private = open('private.pem', 'w') private.write(keypair.exportKey()) private.close()RSA套路
給p,q,e,c
import gmpy2 as gp import binascii p = q = e = c = n = p*q phi = (p-1)*(q-1) d = gp.invert(e,phi) m = pow(c,d,n) print(m) print(bytes.fromhex(hex(m)[2:]))給n,e,dp,c
import gmpy2 as gpe = n = dp = c = for x in range(1, e):if(e*dp%x==1):p=(e*dp-1)//x+1if(n%p!=0):continueq=n//pphin=(p-1)*(q-1)d=gp.invert(e, phin)m=gp.powmod(c, d, n)if(len(hex(m)[2:])%2==1):continueprint('--------------')print(m)print(hex(m)[2:])print(bytes.fromhex(hex(m)[2:]))變種給 p,e,dp,c,b其中 n=p**b*q
from Crypto.Util.number import * import gmpy2 p = dp = c = b = e = mp1 = pow(c, dp, p) mp = pow(c, dp - 1, p) for i in range(1, b - 2):x = pow(c - pow(mp1, e), 1, p**(i + 1))y = pow(x * mp * (gmpy2.invert(e, p)), 1, p**(i + 1))mp1 = mp1 + y print(long_to_bytes(mp1))變種 給n,e,dp0,c,k 其中dp0為dp高位即dp0 = dp>>k
#Sage dp0 = e = n = F.<x> = PolynomialRing(Zmod(n)) d = inverse_mod(e, n) for k in range(1, e):f = (secret << 200) + x + (k - 1) * dx0 = f.small_roots(X=2 ** (200 + 1), beta=0.44, epsilon=1/32)if len(x0) != 0:dp = x0[0] + (secret << 200)for i in range(2, e):p = (e * Integer(dp) - 1 + i) // iif n % p == 0:breakif p < 0:continueelse:print('k = ',k)print('p = ',p)print('dp = ',dp)break給p,q,dp,dq,c
import gmpy2 as gpp = q = dp = dq = c = n = p*q phin = (p-1)*(q-1) dd = gp.gcd(p-1, q-1) d=(dp-dq)//dd * gp.invert((q-1)//dd, (p-1)//dd) * (q-1) +dq print(d)m = gp.powmod(c, d, n) print('-------------------') print(m) print(hex(m)[2:]) print(bytes.fromhex(hex(m)[2:]))低解密指數攻擊/低私鑰指數攻擊(e長度較大,d小,Wiener Attack)
RSAWienerHacker工具:https://github.com/pablocelayes/rsa-wiener-attack
變種 N1/N2 < q1/q2 <1
參考:2020年羊城杯 - RRRRRRRSA
Paper: https://eprint.iacr.org/2015/399.pdf
連分數逼近:
def transform(x,y): #使用輾轉相除將分數x/y轉為連分數的形式res=[]while y:res.append(x//y)x,y=y,x%yreturn resdef continued_fraction(sub_res):numerator,denominator=1,0for i in sub_res[::-1]: #從sublist的后面往前循環denominator,numerator=numerator,i*numerator+denominatorreturn denominator,numerator #得到漸進分數的分母和分子,并返回#求解每個漸進分數 def sub_fraction(x,y):res=transform(x,y)res=list(map(continued_fraction,(res[0:i] for i in range(1,len(res))))) #將連分數的結果逐一截取以求漸進分數return resdef wienerAttack(n1,n2):for (q2,q1) in sub_fraction(n1,n2): #用一個for循環來注意試探n1/n2的連續函數的漸進分數,直到找到一個滿足條件的漸進分數if q1==0: #可能會出現連分數的第一個為0的情況,排除continueif n1%q1==0 and q1!=1: #成立條件return (q1,q2)print("該方法不適用")N1=60143104944034567859993561862949071559877219267755259679749062284763163484947626697494729046430386559610613113754453726683312513915610558734802079868190554644983911078936369464590301246394586190666760362763580192139772729890492729488892169933099057105842090125200369295070365451134781912223048179092058016446222199742919885472867511334714233086339832790286482634562102936600597781342756061479024744312357407750731307860842457299116947352106025529309727703385914891200109853084742321655388368371397596144557614128458065859276522963419738435137978069417053712567764148183279165963454266011754149684758060746773409666706463583389316772088889398359242197165140562147489286818190852679930372669254697353483887004105934649944725189954685412228899457155711301864163839538810653626724347 N2=60143104944034567859993561862949071559877219267755259679749062284763163484947626697494729046430386559610613113754453726683312513915610558734802079868195633647431732875392121458684331843306730889424418620069322578265236351407591029338519809538995249896905137642342435659572917714183543305243715664380787797562011006398730320980994747939791561885622949912698246701769321430325902912003041678774440704056597862093530981040696872522868921139041247362592257285423948870944137019745161211585845927019259709501237550818918272189606436413992759328318871765171844153527424347985462767028135376552302463861324408178183842139330244906606776359050482977256728910278687996106152971028878653123533559760167711270265171441623056873903669918694259043580017081671349232051870716493557434517579121 print(wienerAttack(N1,N2))低加密指數廣播攻擊(Hastad攻擊)
#sage def chinese_remainder(modulus, remainders):Sum = 0prod = reduce(lambda a, b: a*b, modulus)for m_i, r_i in zip(modulus, remainders):p = prod // m_iSum += r_i * (inverse_mod(p,m_i)*p)return Sum % prod chinese_remainder([3,5,7],[2,3,2]) #23 #sage crt([2,3,2],[3,5,7])共模攻擊(n,m相同,c,e不同)
import gmpy2 as gp def egcd(a, b):if a == 0:return (b, 0, 1)else:g, y, x = egcd(b % a, a)return (g, x - (b // a) * y, y)n = c1 = c2 = e1 = e2 = s = egcd(e1, e2) s1 = s[1] s2 = s[2] if s1<0:s1 = - s1c1 = gp.invert(c1, n) elif s2<0:s2 = - s2c2 = gp.invert(c2, n)m = pow(c1,s1,n)*pow(c2,s2,n) % n print(hex(m)[2:]) print(bytes.fromhex(hex(m)[2:]))e,m相同,多個n中存在兩個n有GCD(模不互素)
import gmpy2 as gpn=[] for i in n:for j in n:if (i<>j):pub_p=gp.gcdext(i,j)if (pub_p[0]<>1)&(i>j):print(i)print(j)print(pub_p[0])a=i,p=pub_p[0] q=a//p p = q = e = c = n = p*q phi = (p-1) * (q-1) d = gp.invert(e, phi) m = pow(c, d, n) print(hex(m)[2:]) print(bytes.fromhex(hex(m)[2:]))Rabin加密
適用情況:e=2e=2 。
一般先通過其他方法分解得到 p,qp,q,然后解密。
函數返回四個數,這其中只有一個是我們想要的明文,需要通過其他方式驗證。
import gmpy2def rabin_decrypt(c, p, q, e=2):n = p * qmp = pow(c, (p + 1) // 4, p)mq = pow(c, (q + 1) // 4, q)yp = gmpy2.invert(p, q)yq = gmpy2.invert(q, p)r = (yp * p * mq + yq * q * mp) % nrr = n - rs = (yp * p * mq - yq * q * mp) % nss = n - sreturn (r, rr, s, ss)c = p = q = m = rabin_decrypt(c,p,q) for i in range(4):try:print(bytes.fromhex(hex(m[i])[2:]))except:passBoneh和Durfee攻擊
參考 https://github.com/mimoo/RSA-and-LLL-attacks
Coppersmith攻擊(已知p的高位攻擊)
知道 p 的高位為 p 的位數的約1/2時即可。
#Sage from sage.all import * n = p4 = #p去0的剩余位 e = pbits = 1024 kbits = pbits - p4.nbits() print(p4.nbits()) p4 = p4 << kbits PR.<x> = PolynomialRing(Zmod(n)) f = x + p4 roots = f.small_roots(X=2^kbits, beta=0.4) #經過以上一些函數處理后,n和p已經被轉化為10進制 if roots: p = p4+int(roots[0]) print("n: "+str(n))print("p: "+str(p))print("q: "+str(n//p))Coppersmith攻擊(已知明文高位攻擊,部分m)
這里我們假設我們首先加密了消息 mm,如下
C≡m**e * modN
并且我們假設我們知道消息 m 的很大的一部分 m0,即 m=m0+x,但是我們不知道 x。那么我們就有可能通過該方法進行恢復消息。這里我們不知道的 x 其實就是多項式的根,需要滿足 Coppersmith 的約束。
可以參考 https://github.com/mimoo/RSA-and-LLL-attacks 。
ee 足夠小,且部分明文泄露時,可以采用Coppersmith單變量模等式的攻擊,如下
Coppersmith攻擊(已知d的低位攻擊,部分d)
#Sage def partial_p(p0, kbits, n):PR.<x> = PolynomialRing(Zmod(n))nbits = n.nbits()f = 2^kbits*x + p0f = f.monic()roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.4) # find root < 2^(nbits//2-kbits) with factor >= n^0.4if roots:x0 = roots[0]p = gcd(2^kbits*x0 + p0, n)return ZZ(p) def find_p(d0, kbits, e, n):X = var('X')for k in range(1, e+1):results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits)for x in results:p0 = ZZ(x[0])p = partial_p(p0, kbits, n)if p and p != 1:return p if __name__ == '__main__':n = e = c = d0 = beta = 0.5nbits = n.nbits()kbits = d0.nbits()print("lower %d bits (of %d bits) is given" % (kbits, nbits))p = int(find_p(d0, kbits, e, n))print("found p: %d" % p)q = n//int(p)print("d:", inverse_mod(e, (p-1)*(q-1)))變種 n = pqr
Coppersmith攻擊(已知N一個因子的高位,部分p)
當我們知道一個公鑰中模數 N 的一個因子的較高位時,我們就有一定幾率來分解 N。
參考 https://github.com/mimoo/RSA-and-LLL-attacks 。
關注下面的代碼:
beta = 0.5 dd = f.degree() epsilon = beta / 7 mm = ceil(beta**2 / (dd * epsilon)) tt = floor(dd * mm * ((1/beta) - 1)) XX = ceil(N**((beta**2/dd) - epsilon)) + 1000000000000000000000000000000000 roots = coppersmith_howgrave_univariate(f, N, beta, mm, tt, XX) #Sage n = e = c = pbar = kbits = print("upper %d bits (of %d bits) is given" % (pbar.nbits()-kbits, pbar.nbits())) PR.<x> = PolynomialRing(Zmod(n)) f = x + pbar x0 = f.small_roots(X=2^kbits, beta=0.4)[0] # find root < 2^kbits with factor >= n^0.4 p = x0 + pbar print("p:", p) q = n // int(p) d = inverse_mod(e, (p-1)*(q-1)) print("m:", pow(c, d, n))Coppersmith’s Short-pad Attack & Related Message Attack(Franklin-Reiter攻擊)
#腳本1 #Sage import binascii def attack(c1, c2, b, e, n):PR.<x>=PolynomialRing(Zmod(n))g1 = x^e - c1g2 = (x+b)^e - c2def gcd(g1, g2):while g2:g1, g2 = g2, g1 % g2return g1.monic()return -gcd(g1, g2)[0] c1 = c2 = n = e= a = 1 id1 = 1 id2 = 2 b = id2 - id1 m1 = attack(c1,c2, b,e,n) print(binascii.unhexlify("%x" % int(m1 - id1))) #腳本2 #Sage 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 - c1g2 = (x+y)^e - c2q1 = 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.4return diff def related_message_attack(c1, c2, diff, e, n):PRx.<x> = PolynomialRing(Zmod(n))g1 = x^e - c1g2 = (x+diff)^e - c2def gcd(g1, g2):while g2:g1, g2 = g2, g1 % g2return g1.monic()return -gcd(g1, g2)[0] if __name__ == '__main__':n = e = c1 =c2 = 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)RSA Hastad Attack with non-linear padding and different public keys(帶非線性padding和不同公鑰的廣播攻擊)
參考:2020年羊城杯 - Invitation
Least Significant Bit Oracle Attack (LSB Oracle Attack / Parity Oracle)
import decimal def oracle():return lsb == 'odd'def partial(c, e, n):k = n.bit_length()decimal.getcontext().prec = k # for 'precise enough' floatslo = decimal.Decimal(0)hi = decimal.Decimal(n)for i in range(k):if not oracle(c):hi = (lo + hi) / 2else:lo = (lo + hi) / 2c = (c * pow(2, e, n)) % n# print i, int(hi - lo)return int(hi)Common Private Exponent(共私鑰指數攻擊,d相同)
參考:SCTF 2020 - RSA
多組低解密指數攻擊
適用情況:2-4組 ee,且 dd 較小
給定2組
參考:De1CTF 2020 - easyRSA
給定3組
類似2組情況,其中
多項式RSA
#腳本1 #Sage #已知p,n,m^e p= P = PolynomialRing(Zmod(p), name = 'x') x = P.gen() e = n = c =#分解N q1, q2 = n.factor() q1, q2 = q1[0], q2[0]#求φ,注意求法, phi = (p**q1.degree() - 1) * (p**q2.degree() - 1) assert gcd(e, phi) == 1 d = inverse_mod(e, phi) m = pow(c,d,n)#取多項式系數 flag = bytes(m.coefficients()) print("Flag: ", flag.decode()) #腳本2 #Sage #已知p=2,n,e,c p = P = PolynomialRing(GF(p), name = 'x') x = P.gen() e = n = R.<a> = GF(2^2049) c = []q1, q2 = n.factor() q1, q2 = q1[0], q2[0]phi = (p**q1.degree() - 1) * (p**q2.degree() - 1) assert gcd(e, phi) == 1 d = inverse_mod(e, phi)ans = '' for cc in c:cc = P(R.fetch_int(cc))m = pow(cc,d,n)m = R(P(m)).integer_representation()print(m)ans += chr(m) print(ans) 參考:[0ctf - babyrsa](https://xz.aliyun.com/t/4545)[watevrCTF 2019 - Swedish RSA](https://blog.csdn.net/cccchhhh6819/article/details/103563019)[InCTF 2020 - PolyRSA](https://github.com/S3v3ru5/CTF-writeups/tree/master/Inctfi-2020)[Polynomial based RSA](http://www.diva-portal.se/smash/get/diva2:823505/FULLTEXT01.pdf)其他特別情況
總結