0x01 前言
本文对RSA中常用的模逆运算、欧几里得、拓展欧几里得、中国剩余定理等算法不展开作详细介绍,仅对遇到的CTF题的攻击方式,以及使用到的这些算法的python实现进行介绍。目的是让大家能轻松解决RSA在CTF中的套路题目。
0x02 RSA介绍
介绍
首先,我这边就不放冗长的百度百科的东西了,我概括一下我自己对RSA的看法。
RSA是一种算法,并且广泛应用于现代,用于保密通信。
RSA算法涉及三个参数,n,e,d,其中分为私钥和公钥,私钥是n,d,公钥是n,e
n是两个素数的乘积,一般这两个素数在RSA中用字母p,q表示
e是一个素数
d是e模 varphi(n) 的逆元,CTF的角度看就是,d是由e,p,q可以求解出的
一般CTF就是把我们想要获得的flag作为明文,RSA中表示为m。然后通过RSA加密,得到密文,RSA中表示为C。
加密过程
c=m^e mod n
c=pow(m,e,n)
解密过程
m=c^d mod n
m=pow(c,d,n)
求解私钥d
d = gmpy2.invert(e, (p-1)*(q-1))
一般来说,n,e是公开的,但是由于n一般是两个大素数的乘积,所以我们很难求解出d,所以RSA加密就是利用现代无法快速实现大素数的分解,所存在的一种安全的非对称加密。
基础RSA加密脚本
from Crypto.Util.number import *
import gmpy2
msg = 'flag is :testflag'
hex_msg=int(msg.encode("hex"),16)
print(hex_msg)
p=getPrime(100)
q=getPrime(100)
n=p*q
e=0x10001
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
print("d=",hex(d))
c=pow(hex_msg,e,n)
print("e=",hex(e))
print("n=",hex(n))
print("c=",hex(c))
基础RSA解密脚本
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import binascii
import gmpy2
n=0x80b32f2ce68da974f25310a23144977d76732fa78fa29fdcbf
#这边我用yafu分解了n
p=780900790334269659443297956843
q=1034526559407993507734818408829
e=0x10001
c=0x534280240c65bb1104ce3000bc8181363806e7173418d15762
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,n)
print(hex(m))
print(binascii.unhexlify(hex(m)[2:].strip("L")))
0x03 p和q相差过大或过小
利用条件
因为n=p*q
其中若p和q的值相差较小,或者较大,都会造成n更容易分解的结果
例如出题如下
p=getPrime(512)
q=gmpy2.next_prime(p)
n=p*q
因为p和q十分接近,所以可以使用yafu直接分解
yafu分解
使用
factor(*)
括号中为要分解的数
在线网站分解
http://factordb.com/
通过在此类网站上查询n,如果可以分解或者之前分解成功过,那么可以直接得到p和q
0x04 公约数分解n
利用条件
当题目给的多对公钥n是公用了一个素数因子的时候,可以尝试公约数分解
出题一般如下
p1=getPrime(512)
p2=getPrime(512)
q=getPrime(512)
n1=p1*q
n2=p2*q
所以当题目给了多个n,并且发现n无法分解,可以尝试是否有公约数。
欧几里得辗转相除法
求公约数可以使用欧几里得辗转相除法,实现python脚本如下
def gcd(a, b): #求最大公约数
if a < b:
a, b = b, a
while b != 0:
temp = a % b
a = b
b = temp
return a
用例
def gcd(a, b): #求最大公约数
if a < b:
a, b = b, a
while b != 0:
temp = a % b
a = b
b = temp
return a
n1=0x6c9fb4bf11344e4c818be178e3d3db352797099f929e4ba8fa86d9c4ce3d8f71e3daa8c795b67dc2dabe1e1608836904386c364ecec759c27eaa83eb93710003d4cc848e558f7b11372405c5787b60eca627372767455a5fcf30cb6c157ca5a6267d63ffa16fe49e7433136a47945de2219f46a35f2b6a58196057c602e72a0b
n2=0x46733cc071bdee0d178fb32836a6b0a2f145a681df47d31ea9d9fc5b5fa0cc7ddbcd34531aefeace9840fc890f7a111f73593c9a41886b9a6f91cde3e6f9c71821a8ad877de51f78094599209746e80635c5625459ad7ba14f926b74875c8980a9436d6bbd54e1d9da72ae200383516098c04e24f58b23b4a8142cef0c931a55
print(gcd(n1,n2))
使用欧几里得辗转相除得到共有的因子,然后n1和n2除以这个因子,即可得到另一个素数因子。
0x05 模数分解
场景
已知e,d,n求p,q
例如
('d=', '0x455e1c421b78f536ec24e4a797b5be78df09d8d9e3b7f4e2244138a7583e810adf6ad056bb59a91300c9ead5ed77ea6bafdebf7ab2d9ec200127901083c7ffca45e83f2c934358366a2b6207b96a0eae6df0476060c063c281512834a42350a3b56bc09f5cec1a6975257d7f12a58f6389060e49b41f05e88ea2b30b395f6391')
('e=', '0x10001')
('n=', '0x71ee0f4883690893ab503e97e25e6308d4c1e0a050cbea7b9c040f7a5b5b484afcecc8a9b3cc6bf089a1e83281562df217caab7220e3dfc14399139ce437af2f131f9345675e4d848cfab5827818eeab7834374be4a0513f81f3df125a932c2bb4c24c834d798bcc80f9c4a8770b01f8e54620b72a4f0491edd391e635d48e71L')
模数分解
私钥d的获取是通过
d = gmpy2.invert(e, (p-1)*(q-1))
分解p,q python实现如下
import random
def gcd(a, b):
if a < b:
a, b = b, a
while b != 0:
temp = a % b
a = b
b = temp
return a
def getpq(n,e,d):
p = 1
q = 1
while p==1 and q==1:
k = d * e - 1
g = random.randint ( 0 , n )
while p==1 and q==1 and k % 2 == 0:
k /= 2
y = pow(g,k,n)
if y!=1 and gcd(y-1,n)>1:
p = gcd(y-1,n)
q = n/p
return p,q
完整用例
import random
def gcd(a, b):
if a < b:
a, b = b, a
while b != 0:
temp = a % b
a = b
b = temp
return a
def getpq(n,e,d):
p = 1
q = 1
while p==1 and q==1:
k = d * e - 1
g = random.randint ( 0 , n )
while p==1 and q==1 and k % 2 == 0:
k /= 2
y = pow(g,k,n)
if y!=1 and gcd(y-1,n)>1:
p = gcd(y-1,n)
q = n/p
return p,q
n=0x71ee0f4883690893ab503e97e25e6308d4c1e0a050cbea7b9c040f7a5b5b484afcecc8a9b3cc6bf089a1e83281562df217caab7220e3dfc14399139ce437af2f131f9345675e4d848cfab5827818eeab7834374be4a0513f81f3df125a932c2bb4c24c834d798bcc80f9c4a8770b01f8e54620b72a4f0491edd391e635d48e71
e=0x10001
d=0x455e1c421b78f536ec24e4a797b5be78df09d8d9e3b7f4e2244138a7583e810adf6ad056bb59a91300c9ead5ed77ea6bafdebf7ab2d9ec200127901083c7ffca45e83f2c934358366a2b6207b96a0eae6df0476060c063c281512834a42350a3b56bc09f5cec1a6975257d7f12a58f6389060e49b41f05e88ea2b30b395f6391
p,q=getpq(n,e,d)
print("p=",p)
print("q=",q)
print(p*q==n)
0x06 dp&dq泄露
介绍
首先了解一下什么是dp、dq
dp=d%(p-1)
dq=d%(q-1)
这种参数是为了让解密的时候更快速产生的
场景
假设题目仅给出p,q,dp,dq,c,即不给公钥e
('p=', '0xf85d730bbf09033a75379e58a8465f8048b8516f8105ce2879ce774241305b6eb4ea506b61eb7e376d4fcd425c76e80cb748ebfaf3a852b5cf3119f028cc5971L')
('q=', '0xc1f34b4f826f91c5d68c5751c9af830bc770467a68699991be6e847c29c13170110ccd5e855710950abab2694b6ac730141152758acbeca0c5a51889cbe84d57L')
('dp=', '0xf7b885a246a59fa1b3fe88a2971cb1ee8b19c4a7f9c1a791b9845471320220803854a967a1a03820e297c0fc1aabc2e1c40228d50228766ebebc93c97577f511')
('dq=', '0x865fe807b8595067ff93d053cc269be6a75134a34e800b741cba39744501a31cffd31cdea6078267a0bd652aeaa39a49c73d9121fafdfa7e1131a764a12fdb95')
('c=', '0xae05e0c34e2ba4ca3536987cc2cfc3f1f7f53190164d0ac50b44832f0e7224c6fdeebd2c91e3991e7d179c26b1b997295dc9724925ba431f527fba212703a0d14a34ce133661ae0b6001ee326303d6ccdc27dbd94e0987fae25a84f197c1535bdac9094bfb3846b7ca696b2e5082bea7bff804da275772ca05dd51b185a4fc30L')
解密代码如下
InvQ=gmpy2.invert(q,p)
mp=pow(c,dp,p)
mq=pow(c,dq,q)
m=(((mp-mq)*InvQ)%p)*q+mq
print '{:x}'.format(m).decode('hex')
解题完整脚本
import gmpy2
import binascii
def decrypt(dp,dq,p,q,c):
InvQ = gmpy2.invert(q,p)
mp = pow(c,dp,p)
mq = pow(c,dq,q)
m=(((mp-mq)*InvQ)%p)*q+mq
print (binascii.unhexlify(hex(m)[2:]))
p=0xf85d730bbf09033