RSA原理及其攻击方法
RSA 基于一个简单的数论事实,两个大素数相乘十分容易,将其进行因式分解却是困难的
密钥产生:
1.选两个大素数p和q
2.计算n=p*q,欧拉函数*φ(n) =(p-1) (q-1)
3.选一整数e,满足1<e<φ(n),且gcd(φ(n),e)=1,即φ(n)与e互质
4.计算d,满足d *e≡1 mod φ(n),即d是e在模φ(n)下的乘法逆元,因为e与φ(n)互素,由模运算可知,它的乘法逆元一定存在(欧拉定理:若a与n互素,则a^φ(n)≡1 mod n)
5.以{e,n}为公钥,{d,n}为私钥
加密:
加密前先进行分组,使得每个分组对应的十进制数小于n,即分组长度小于 log2n,然后对明文分组m做加密运算:c≡m^e mod n
解密:
分组解密运算:m ≡ c^d mod n
RSA算法中的计算问题
加密解密:
加密解密过程中直接求一个整数的整数次幂再进行取模很有可能直接爆了,所以这里需要用到模运算的性质减小中间量的值:
(a*b)mod n=[(a mod n) * (b mod n)] mod n
再考虑指数运算的有效性,例如x ^ 16 , 直接计算会计算15次,如果重复对部分结果进行平方运算,即x,x^ 2 , x^ 4,x ^ 8,x^16,就只需要计算4次
一般求a ^ m可以先将m化为二进制形式,即m=bk2^k+ bk-12^k-1+…+b12+b0
因此,a^m=(…((( a^ bk ) ^2 * a ^ bk-1)^ 2 * a^ bk-2) ^ 2)…a^ b1)^2* a^b0
例如:
19=>10011#化为二进制
19=1*2^4+0 *2^3+0 *2^2+1 *2^1+1 *2^0
a^19=((((a ^ 1)^2 *a ^ 0)^2 *a ^ 0)^2 *a ^ 1)^2 *a^1
密钥的产生:
为了防止穷搜索发现p,q这两个大素数应是在足够大的整数集合中选取的大数,如果选取p,q为10 ^ 100 左右的大数,那么n的阶就大概再10^ 200左右,那么每个明文分组就可以包含664位(因为10^ 200 ≈ 2^664),即83个8byte字节,所以如何寻找这两个大素数是一个比较重要的问题,首先一般先随机选取一个大的奇数
然后用素性检验算法检验这一奇数是否为素数,如果不是,则选取另一大奇数,重复过程直到找到为止
之后是选取需要满足1<e<φ(n)和gcd(φ(n),e)=1的e并且计算d *e≡1 mod φ(n)的d(使用欧几里得算法实现)
RSA的安全问题
如果RSA的p,q被成功分解就可以立即获取φ(n),从而可以通过d ≡e^-1 mod φ(n)(快速幂求取乘法逆元)获取私钥d,攻击成功,经过较长时间的发展,如今被破解的最长RSA密钥是768个二进制位,目前密钥长度介于1024~2048比特之间的RSA密钥是安全的
为保证RSA算法的安全性,对p,q提出以下要求:
1.|p-q|要相对大
2.p-1和q-1都应有大素因子,
此外,如果e<n,d<n^1/4,则d能被容易地确定
目前针对rsa的攻击
即便 RSA 算法目前来说是安全可靠的,但是错误的应用场景,错误的环境配置,以及错误的使用方法都会导致 RSA 的算法体系出现问题,从而也派生出针对各种特定场景下的 RSA 攻击方法
n可直接分解
n的大小小于256bites,可以直接利用windows的RSATool对n进行分解
那么n可分解的情况下实际上就已经知道φ(n)的了,通过欧几里得算法求模逆元就可以得出私钥d
例题:
p = 3487583947589437589237958723892346254777
q = 8767867843568934765983476584376578389
e = 65537
求 d
code:
import gmpy2
p = 3487583947589437589237958723892346254777
q = 8767867843568934765983476584376578389
e = 65537
print(gmpy2.invert(e, (p - 1) * (q - 1)))#求模逆元
yafu
当大整数N的两个因子p和q相差不大时,我们可以通过费马分解的办法很快分解大整数,当p,q相差较大时,我们可以通过pollard rho算法分解,yafu使用此类强大的现代算法自动化地实现分解的目的,大多数的算法都实现了多线程,让yafu能充分利用多核处理器(算法包括 SNFS, GNFS, SIQS, 和 ECM)。
factordb
n大于768bite时或者对一个大整数用一些特殊算法也分解不了的时候,我们可以在 http://factordb.com/ 中查询一下数据库,此网站存储一些已经分解成功的n
n1,n2具有相同素因子
通过欧几里得算法可以直接求出 n1 和 n2 的最大公约数 p:
gcd(n1,n2)=p
可以得出:
n1=pq1
n2=pq2
例题:
n1 = 9051013965404084482870087864821455535159008696042953021965631089095795348830954383127323853272528967729311045179605407693592665683311660581204886571146327720288455874927281128121117323579691204792399913106627543274457036172455814805715668293705603675386878220947722186914112990452722174363713630297685159669328951520891938403452797650685849523658191947411429068829734053745180460758604283051344339641429819373112365211739216160420494167071996438506850526168389386850499796102003625404245645796271690310748804327
n2 = 13225948396179603816062046418717214792668512413625091569997524364243995991961018894150059207824093837420451375240550310050209398964506318518991620142575926623780411532257230701985821629425722030608722035570690474171259238153947095310303522831971664666067542649034461621725656234869005501293423975184701929729170077280251436216167293058560030089006140224375425679571181787206982712477261432579537981278055755344573767076951793312062480275004564657590263719816033564139497109942073701755011873153205366238585665743
code:
import gmpy2
n1 = 9051013965404084482870087864821455535159008696042953021965631089095795348830954383127323853272528967729311045179605407693592665683311660581204886571146327720288455874927281128121117323579691204792399913106627543274457036172455814805715668293705603675386878220947722186914112990452722174363713630297685159669328951520891938403452797650685849523658191947411429068829734053745180460758604283051344339641429819373112365211739216160420494167071996438506850526168389386850499796102003625404245645796271690310748804327
n2 = 13225948396179603816062046418717214792668512413625091569997524364243995991961018894150059207824093837420451375240550310050209398964506318518991620142575926623780411532257230701985821629425722030608722035570690474171259238153947095310303522831971664666067542649034461621725656234869005501293423975184701929729170077280251436216167293058560030089006140224375425679571181787206982712477261432579537981278055755344573767076951793312062480275004564657590263719816033564139497109942073701755011873153205366238585665743
p=gmpy2.gcd(n1,n2)#求最大公约数p
q1=n1/p#求q1
q2=n2/p#求q2
print(q1,q2)
d=gmpy2.invert(e,(p-1)*(q1-1))
m=pow(c1,d,n1)#c1的d次方模n1
print(long_to_bytes(m))#long型转byte型
低加密指数攻击
加密指数较小可以加快加密过程,但同时也存在安全问题
第一种情况:
当 e=3 时,如果明文过小,导致明文的三次方仍然小于 n,那么就可以通过直接对m开三次方,即可得到明文。
即:
c≡m^e mod n
如果 e=3, m^3<n
=>c=m^3
=>m=c^1/3
还有一种情况是:
如果明文的 3 次方比 n 大,但不足够大,那么可以设 k,就有:
c=m^3+kn
爆破 k,如果 c − kn 能开三次根式得到整数k,那么可以直接得到明文
例题:
得到
一个pem文件:网络中的证书文件,里面藏着n,e
用openssl rsa -pubin -text -modulus -in warmup -in public.pem
可以提取出来,注意文件需放到同一目录下
一个flag.enc文件:被加密后的文件,写解密脚本时打开
先用openssl命令找到n和e,发现e=3那么就可以试试低加密指数攻击了
exp:
import gmpy2
import rsa
from rsa import transform, core
c = open('flag.enc', 'rb').read()
c = transform.bytes2int(c) # 字节数组转为大整数
n = 721059527572145959497866070657244746540818298735241721382435892767279354577831824618770455583435147844630635953460258329387406192598509097375098935299515255208445013180388186216473913754107215551156731413550416051385656895153798495423962750773689964815342291306243827028882267935999927349370340823239030087548468521168519725061290069094595524921012137038227208900579645041589141405674545883465785472925889948455146449614776287566375730215127615312001651111977914327170496695481547965108836595145998046638495232893568434202438172004892803105333017726958632541897741726563336871452837359564555756166187509015523771005760534037559648199915268764998183410394036820824721644946933656264441126738697663216138624571035323231711566263476403936148535644088575960271071967700560360448191493328793704136810376879662623765917690163480410089565377528947433177653458111431603202302962218312038109342064899388130688144810901340648989107010954279327738671710906115976561154622625847780945535284376248111949506936128229494332806622251145622565895781480383025403043645862516504771643210000415216199272423542871886181906457361118669629044165861299560814450960273479900717138570739601887771447529543568822851100841225147694940195217298482866496536787241
print(hex(c)) #提取密文c
def dec(c, N): # 爆破k
k=0
#k =118719487
while 1:
print(k)
(x, y) = gmpy2.iroot(c + k * N, 3)
if y:
print(gmpy2.iroot(c + k* N, 3))
break
k = k + 1
dec(c, n)
m = 440721643740967258786371951429849843897639673893942371730874939742481383302887786063966117819631425015196093856646526738786745933078032806737504580146717737115929461581126895844008044713461807791172016433647699394456368658396746134702627548155069403689581548233891848149612485605022294307233116137509171389596747894529765156771462793389236431942344003532140158865426896855377113878133478689191912682550117563858186
m = transform.int2bytes(m) # 大整数转为字节数组
print(m)
flag:
(mpz(440721643740967258786371951429849843897639673893942371730874939742481383302887786063966117819631425015196093856646526738786745933078032806737504580146717737115929461581126895844008044713461807791172016433647699394456368658396746134702627548155069403689581548233891848149612485605022294307233116137509171389596747894529765156771462793389236431942344003532140158865426896855377113878133478689191912682550117563858186), True)#可完全开方,不带根号
b"Didn't you know RSA padding is really important? Now you see a non-padding message is so dangerous. And you should notice this in future.Fl4g: PCTF{Sm4ll_3xpon3nt_i5_W3ak}\n"
低解密指数攻击(Wiener’s attack)
一种基于连分数的特殊攻击类型,与低加密指数相同,低解密指数可以加快解密的过程,因此它也会存在一些安全问题
如果满足e很大, d < (1/3) N^(1/4),q<p<2q时,我们可以通过Wiener’s attack分解得到d
原理:
Boneh Durfee Method
当满足 d ≤ N^0.292 时(d没有足够小),我们可以利用该方法分解N,理论上比wiener attack要强一些。
参考github代码
Rabin算法
仅限用于e等于2时
例题:
用同样的方法进行提取,发现e=2所以可以用到Rabin算法
提取出n和e后对n进行yafu分解
code:
import gmpy2
import string
from Crypto.PublicKey import RSA
with open('pubkey.pem', 'r')as f:#提取n,e
key = RSA.importKey(f)
N = key.n
e = key.e
with open('flag.enc', 'r')as f:
cipher = f.read().encode('hex')
cipher = string.atoi(cipher, base=16) # atoi()字符串转换成整数型
print(cipher)
p = 275127860351348928173285174381581152299
q = 319576316814478949870590164193048041239
inv_p = gmpy2.invert(p, q)
inv_q = gmpy2.invert(q, p)
mp = pow(cipher, (p + 1) / 4, p)
mq = pow(cipher, (q + 1) / 4, q)
a = (inv_p * p * mq + inv_q * q * mp) % N
b = N - int(a)
c = (inv_p * p * mq - inv_q * q * mp) % N
d = N - int(c)
for i in (a, b, c, d):
s = '%x' % i
if len(s) % 2 != 0:
s = '0' + s
print s.decode('hex')
Rabin算法具有其致命的缺陷:一个密文对应四个明文。但此算法仍然包含了密码学中的基本概念和技巧,如单向函数、整数的因数分解等。
Rabin算法的安全性基于整数的因式分解问题:只有将公钥n正确分解为私钥p、q,才可以将公钥加密后的密文还原为原文,而通常p、q都会取相当大的素数,因此n也是一个非常大的数字;数字越大,其因式分解越困难。
共模攻击
条件:在RSA的使用中使用了相同的模n对明文m进行加密
解决方法:可以在不分解n的情况下还原明文m
要求:e1与e2互质
c1≡m^e1 mod n
c2≡m^e2 mod n
c1 ^ r* c2 ^ s≡m^(e1 * r+e2 * s) ≡m^1 (mod n)
e1 r + e2 s=1 # 扩展欧几里得算法求出r,s,其中一个必定为负,设为s<0
,再次用扩展欧几里得算法求出c1 ^-1由此(c1 -1) ^-s1 * c2^s2≡m mod n
m=(c1^ s1*c2^s2)mod n #最终结果
推导过程:
def exgcd(a,b):#扩展欧几里得算法
if b==0:
return (1,0)
else:
x,y=exgcd(b,a%b)
tempx=x
x=y
y=tempx-int((a/b))*y
return x,y
a=exgcd(13,2)
print(a)
调用函数:
gmpy2.gcdext(x,y)#扩展欧几里得算法
已知p+q或p-q
可以通过解方程组或推导求出p和q
p*q=n
p+q=a
使用SageMath解方程组:
var('p q')
solve([p*q==n,p+q==a],[p,q])
Small q
当q较小时,即|p-q|较大时,我们可以直接爆破因子
d泄露攻击
如果我们知道一组过期的(N,n1,d1)和一组由新的e2组成的公钥及其加密的密文(N,e2,c),我们可以由(e1,d1)得到模数N的两个因子p和q,之后求出φ(n),再去算e2的模逆元d2,然后解出密文
已知明文高位攻击
Coppersmith定理指出在一个e阶modn的多项式f(x)中,如果又一个根小于n^1/e,就可以运用一个o(log n)的算法求出这些根
所以在e=3,并且已知明文高位,就可以尝试Coppersmith攻击
Partial p攻击
RSA Last BIt Oracle Attack
假设存在一个Oracle,它会对一个给定的密文进行解密,并且会检查解密的密文的奇偶性,并根据奇偶性返回相应的值,比如1表示奇数,0表示偶数,那么给定一个加密后的密文,我们只需要log(N)次就可以知道这个密文对应的明文消息
服务器会计算得到2Pmod N
2P是偶数,它的幂次也是偶数
N是奇数,因为它是由两个大素数相乘得到的
最后还需要在小范围之内枚举确定明文的正确性
总结:
虽然目前密钥选择在1024~2048比特 的时候安全性比较高,但是我们选择其他参数时也要合理才能尽量避免攻击保证算法的安全