CTF中的RSA 算法
1.质数(素数)是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
2.合数是指比1大但不是素数的数
3.约数(因数)整数a除以整数b(b≠0) 除得的商正好是整数而没有余数,我们就说a能被b整除,或b能整除a。a称为 b的倍数,b称为a的约数
4.互质数:如果两个整数a,b的最大公因数(greatest common divisor)为1,即gcb(a,b)=1,那么称a,b两数互质
5.欧拉函数是指设m为正整数,则1,2,3,4.......,m中与m互素的整数的个数记为φ(m),叫做欧拉函
RSA加解密涉及变量
N(n):模数(modulus)
p 和 q :N的两个因子(factor)
e 和 d:(密钥) 互为模反数的两个指数(exponent)
c 和 m:分别是密文和明文,这里一般指的是一个十进制的数还有一个就是n的欧拉函数值
欧拉函数值:r
pow(x, y, z):效果等效pow(x, y)1 % z, 先计算x的y次方,如果存在另一个参数z,需要再对结果进行取模。
RSA 密钥流程
1.选择两个大的参数,计算出模数 N = p * q
2.计算欧拉函数 φ = (p-1) * (q-1),然后选择一个e(1<e<φ),并且e和φ互质(互质:公约数只有1的两个整数)
3.选一个整数e,满足条件1<e<φ(m),且gcd(φ(m),e)=1。
4.取e的模反数d,计算方法为:e * d ≡ 1 (mod φ) (模反元素:如果两个正整数e和n互质,那么一定可以找到整数d,使得 e * d - 1 被n整除,或者说e * d被n除的余数是1。这时,d就叫做e的“模反元素”。欧拉定理可以用来证明模反元素必然存在。两个整数a,b,它们除以整数M所得的余数相等:a ≡ b(mod m),比如说5除3余数为2,11除3余数也为2,于是可写成11 ≡ 5(mod 3)。)
5.对明文m进行加密:c = pow(m, e, N),可以得到密文c。
6.对密文c进行解密:m = pow(c, d, N),可以得到明文m。
7.以{e,n}为公开密钥,{d,n}为秘密密钥。
对于RSA加密算法,公钥{e,n}为公钥,可以任意公开,破解RSA最直接(亦或是暴力)的方法就是分解整数N,然后计算欧拉函数φ(n)=(p-1) * (q-1),再通过d * e ≡ 1 mod φ(N),即可计算出 d,然后就可以使用私钥{d,n}通过m = pow(c,d,N)解密明文。
常见攻击方法
已知p
、q
、e
或者已知n
、e
求出d
import gmpy2
p = gmpy2.mpz(18443)#初始化大整数
q = gmpy2.mpz(49891)
e = gmpy2.mpz(19)
phi_n = (p-1)*(q-1)
d = gmpy2.invert(e,phi_n) # invert(e,r)返回d使得e * d == 1 mod r,如果不存在d,则返回0
print("p={0},q={1},e={2}".format(p,q,e))
print("d is:\n%s"%d)
注:gmpy2:开源的高精度算数运算库https://blog.csdn.net/x_yhy/article/details/83903367
分解N
得到p
q
可以通过在线网站http://www.factordb.com/index.php
已经求出d
、n
、c
,然后可以求出相应的明文m
#求明文
import gmpy2
n = 920139713 #模数
d = 96849619 #密钥
c = """
704796792
752211152
274704164
... #密文
"""
result = ""
c_list = c.split()
#print(c_list)
for i in c_list:
result += chr(pow(int(i),d,n))
print(result)
已知c
、e
、n
求m
结合以上两种方法,在知道n
的前提下可求·p
、q
,利用p
、q
、e
可以求出d
,,从而因为已知d
、n
、c
,求出相应的明文m
利用n的公约数
当题目给出若干个模数n1,n2......,且模数很大。如果两次加密的n1
和n2
具有相同的素因子,那么我们可以利用欧几里德算法
直接分解n1
和n2
.从而计算出两个n
的最大公约数p
:
素因子的定义:对于一个数n来说,将它的因子拆到若干个素数相乘,这些素数被称为n的素因子。
比如 12可以被拆为2 6
6不是质数,可以继续拆为2*3
所以最后12的素因子就是 2, 3(不计重复元素)
识别此类题目,通常会发现题目给了若干个n,均不相同,并且都是2048bit,4096bit级别,无法直接分解http://www.factordb.com/index.php,并且明文都没什么联系,e也一般取65537。
#-*-coding:utf-8-*-
'''
求两个数的最大公约数
算法参考:https://zhidao.baidu.com/question/36550887.html
by:reborn
'''
import gmpy2
n1=
n2=
def gys1(n1,n2): #辗转相除法(欧几里德算法)
if n1<n2:
n1,n2=n2,n1
while n2!=0:
temp=n1%n2
n1=n2
n2=temp
return n1
def gys2(n1,n2): #更相减损法
while n1!=n2:
if n1<n2:
n1,n2=n2,n1
temp=n1-n2
n1=temp
return n1
p=gys2(n1,n2)
print ("p=",p)
#求q1,q2
q1=n1//p
q2=n2//p
print("q1=",q1)
print("q2=",q2)
#求d_1,d_2
p0 = gmpy2.mpz(p)#初始化大整数
q_1 = gmpy2.mpz(q1)
q_2 = gmpy2.mpz(q2)
e = gmpy2.mpz(65537)
r_1 = (p0-1)*(q_1-1)
r_2 = (p0-1)*(q_2-1)
d_1 = gmpy2.invert(e,r_1) # invert(e,r)返回d使得e * d == 1 mod r,如果不存在d,则返回0
d_2 = gmpy2.invert(e,r_2)
print("d_1=",d_1)
print("d_2=",d_2)
# 求c1,c2
c1=
c2=
m1 = pow(c1, d_1, n1)
m2 = pow(c2, d_2, n2)
print("m1=",m1)
print("m2=",m2)
根据欧几里德算法
算出的p
之后,再用n
除以p
即可求出q
,由此可以得到的参数有p
、q
、n
、e
,再使用常规方法计算出d
,即可破解密文。
m = pow(c, d, N),可以得到明文m
共模攻击
如果在RSA的使用中使用了相同的模n对相同的明文m进行了加密,那么就可以在不分解n的情况下还原出明文m的值。
c1 = m^e1 mod n
c2 = m^e2 mod n
识别:非常简单,若干次加密,每次n都一样,明文根据题意也一样即可。
已知私钥文件、c求m
题目中给出了私钥文件private.pem和flag.enc
pem文件通常是包含了-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----,是 Base64 编码的二进制内容
使用私钥解密密文的方式
使用openssl工具
利用如下命令:
rsautl -decrypt -in flag.enc(密文名称) -inkey private.pem
已知公钥文件、c求m
题目中给出了public.pem和密文flag.enc
openssl rsa -pubin -text -modulus -in warmup -in pubkey.pem
[提取出pubkey.pem中的参数]
得到n,化为十进制
将n分解为P,q
python rsatool.py -o private.pem -e 65537 -p 275127860351348928173285174381581152299 -q 319576316814478949870590164193048041239
[使用rsatool生成私钥文件: private.pem]
openssl rsautl -decrypt -in flag.enc -inkey private.pem
低加密指数攻击
在RSA中e也称为加密指数。由于e是可以随意选取的,选取小一点的e可以缩短加密时间,但是选取不当的话,就会造成安全问题。
e=3时的小明文攻击
当e=3时,如果明文过小,导致明文的三次方仍然小于n,那么通过直接对密文三次开方,即可得到明文。
(1)m3<n,也就是说m3=c;
(2)m3>n,即(m3+kn)mod n=c(爆破k,不知道k取什么值)。
- 第一种情况 根据
c = pow(m, e, N)
可知:
当e=3时,如果明文过小,导致明文的三次方仍然小于n,那么通过直接对密文三次开方,即可得到明文。
- 第二种情况 如果明文的三次方比n大,但是不够大,那么设k,有: c=(m^3+kn)mod n
爆破k,如果(c-kn)能开三次根式,那么可以直接得到明文。
识别:
推荐在e=3的时候首先尝试这种方法。
openssl rsa -pubin -in pubkey.pem (读取公钥内容)
openssl rsa -pubin in pubkey.pem -text(以文本格式输出公钥内容),从这一步可以知道e的值
从而判断为低加密指数攻击
低加密指数广播攻击
低加密指数广播攻击,即如果选取的加密指数较低,并且使用了相同的加密指数给一个接收者发送了相同的信息(或者给一群接收者发送了相同的信息),那么可以进行广播攻击得到明文。
假如我们需要将一份明文进行多份加密,但是每份使用不同的密钥,密钥中的模数n不同但指数e相同且很小,我们只要拿到多份密文和对应的n就可以利用中国剩余定理进行解密。
适用
只要满足以下情况,我们就可以考虑实用低加密指数广播攻击:
1.加密指数e非常小
2.一份明文使用不同的模数n,相同的加密指数e进行多次加密
3.可以拿到每一份加密后的密文和对应的模数n、加密指数e
低加密指数广播攻击脚本
# coding:utf8
from struct import pack,unpack
import zlib
import gmpy
def my_parse_number(number):
string = "%x" % number
#if len(string) != 64:
# return ""
erg = []
while string != '':
erg = erg + [chr(int(string[:2], 16))]
string = string[2:]
return ''.join(erg)
def extended_gcd(a, b):
x,y = 0, 1
lastx, lasty = 1, 0
while b:
a, (q, b) = b, divmod(a,b)
x, lastx = lastx-q*x, x
y, lasty = lasty-q*y, y
return (lastx, lasty, a)
def chinese_remainder_theorem(items):
N = 1
for a, n in items:
N *= n
result = 0
for a, n in items:
m = N/n
r, s, d = extended_gcd(n, m)
if d != 1:
N=N/n
continue
#raise "Input not pairwise co-prime"
result += a*s*m
return result % N, N
//中国剩余定理 , 输入多组c和多组n,以及较小的指数e
sessions=[
{"c": , "e": , "n": },
{"c": , "e": , "n": },
{"c": , "e": , "n": }
]
data = []
for session in sessions:
e=session['e']
n=session['n']
msg=session['c']
data = data + [(msg, n)]
print "Please wait, performing CRT"
x, n = chinese_remainder_theorem(data)
e=session['e']
realnum = gmpy.mpz(x).root(e)[0].digits()
print my_parse_number(int(realnum))
n可以分解为多个素数
使用公钥加密和使用私钥解密流程(中国剩余定理):
准备
首先,我们需要在在生成私钥公钥时,多生成几个数:
我们的d是e对phi(n)的逆元,我们现在需要另外2个逆元(分别是对(p-1)和(q-1)的),既
1:计算dp,使得dp * e = 1 mod(p-1)
2:计算dq,使得dq * e = 1 mod(q-1)
此外需要第三个元素,既q对p的逆元
3:计算qInv,使得qInv * q = 1 mod p
1 2 3 都作为私钥的一部分。
dp = d mod p-1
dq = d mod q-1
计算:
使用公钥加密:
若要加密明文m,则需要计算c = m^e mod n,c为密文。
使用私钥解密:
1:m1=c^dp mod p
2:m2=c^dq mod q
3:h= (qInv((m1 - m2)mod p)) mod p
4:m = m2 + hq
m就是明文。
例:n=17947
e=3
c=8363
m=???
import gmpy2
n=17947
p=137
q=131
e=3
c=8363
dp=gmpy2.invert(e,p-1)
dq=gmpy2.invert(e,q-1)
m1=pow(c,dp,p)
m2=pow(c,dq,q)
qInv=gmpy2.invert(q,p)
h=(qInv*((m1-m2)% p)) % p
m=m2+h*q
print(m)
多素数
例:n=p1p2p3=2279269
p1=137
p2=131
p3=127
e=19
预先计算:
dp = 19^-1 mod 137-1 = 43
dq = 19^-1 mod 131-1 = 89
dr = 19^-1 mod 127-1 = 73
若要解密密文 768924,则先计算
1:m1=768924^43 mod 137 = 102
2:m2=768924^89 mod 131 = 120
3:m3=768924^73 mod 127 = 5
等式1与等式2连列方程组计算:
qInv = 114
h = (qInv*((m1 - m2)mod p)) mod p = 3
m12 = m2 + h*q = 120 + 3*131 = 513
所以等式1与等式2的通用解为:513+k1*(131*137)
所以结合等式3问题可以变为:
m1=513 p=17947
m2=5 q=127
qInv*q≡ 1 mod p ——>qInv=10316
h = (10316*((513 - 5)mod 17947)) mod 17947 =4
m = 5 + 4*127 = 513
......
jiaoben
import gmpy2
n=2279269
p1=137
p2=131
p3=127
e=19
c=768924
dp1=gmpy2.invert(e,p1-1)
dp2=gmpy2.invert(e,p2-1)
dp3=gmpy2.invert(e,p3-1)
m1=pow(c,dp1,p1)
m2=pow(c,dp2,p2)
m3=pow(c,dp3,p3)
qInv1=gmpy2.invert(p2,p1)
h1=(qInv1*((m1-m2) % p1)) % p1
m4=m2+h1*p2
p4=p1*p2
qInv2=gmpy2.invert(p3,p4)
h2=(qInv2*((m4-m3)% p4)) % p4
m=m3+h2*p3
print(m)
dp、dq泄露
dp = d mod p-1
dq = d mod q-1
这种参数是为了让解密的时候更快速而产生的
已知p,q,dp,dq,c求m
import gmpy2
import binascii
import libnum
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:]))
print(libnum.n2s(m))
p =
q =
dp =
dq =
c =
decrypt(dp,dq,p,q,c)
已知e,n,dp,c求m
import gmpy2
import libnum
import binascii
def getd(n,e,dp):
for i in range(1,e): #在范围(1,e)之间进行遍历
if (dp*e-1)%i == 0:
if n%(((dp*e-1)/i)+1)==0: #存在p,使得n能被p整除
p=((dp*e-1)/i)+1
q=n/(((dp*e-1)/i)+1)
phi = (p-1)*(q-1) #欧拉定理
d = gmpy2.invert(e,phi)%phi #求模逆
return d
e =
n =
dp =
c =
d=getd(n,e,dp)
m=pow(c,d,n) #快速求幂取模运算
print(binascii.unhexlify(hex(m)[2:])) #16进制转文本
print(libnum.n2s(m))
https://blog.csdn.net/qq_42939527/article/details/105202716
已知n,r求p,q
核心是通过n和r解出p和q
1.二分法,求得p,q
2.RSATool2v17中,输入p,q,r,e,得到d (脚本也可)
3.通过m=pow(c,d,n)
注意:有时题目有要求,解出的可能是m乘上某一个参数,这是需要仔细审题
4.转字符,得到flag
脚本
import gmpy2
import numpy as np
np.set_printoptions(suppress=True)
n=gmpy2.mpz(14057332139537395701238463644827948204030576528558543283405966933509944444681257521108769303999679955371474546213196051386802936343092965202519504111238572269823072199...)
r=gmpy2.mpz(14057332139537395701238463644827948204030576528558543283405966933509944444681257521108769303999679955371474546213196051386802936343092965202519504111238572269823072199...)
c1=n-r+1
print (c1)
l=c1/2
r=c1
#p=(l+r)/2
#y=p*(c1-p)
while l<r:
p=(l+r)/2
y=p*(c1-p)
if y==n:
print (p)
break
if y>n:
print (y>n)
l=p
else:
print (y<n)
r=p
print ("done")
q=c1-p
print q
/*
if p>q:
p,q=q,p
factor2 = 2021 * p + 2020 * q
if factor2 < 0:
factor2 = (-1) * factor2
_P=sympy.nextprime(factor2)
*/