RSA算法
1 前置知识
1.1 扩展欧几里得算法
贝祖定理:如果有a、b是整数,那么一定存在整数x、y使得
a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)
也就是说,如果 a x + b y = m ax+by=m ax+by=m有解,那么m一定是gcd(a, b)的倍数,要想求出gcd(a, b)显然可以使用辗转相除法,下面是辗转相除法的Python代码:
def gcd(a, b):
if b == 0:
return a
else:
return gcd(b, a%b)
但是,对于 a x + b y = m ax+by=m ax+by=m,我们不但想知道其是否有解,并且想要求解出答案。所以,使用扩展欧几里得算法:
在到达递归边界的时候, b = 0 , a = g c d ( a , b ) b=0,a=gcd(a, b) b=0,a=gcd(a,b)这时可以观察出来一个式子的一个解: a ∗ 1 + b ∗ 0 = g c d ( a , b ) , x = 1 , y = 0 a*1+b*0=gcd(a, b), x=1, y=0 a∗1+b∗0=gcd(a,b),x=1,y=0,注意这时的a,b已经不是最开始的a,b,所以我们要反推开始的状态。因为这是递归算法,也就是说,如果我们知道了这一层和上一层的关系,一层一层的反推回去,就可以退回最初的情况。
假设当前我们在求的时a和b的最大公约数,而我们已经求出了下一个状态:b和a%b的最大公因数,并且求出了一组x1和y1使得
b
∗
x
1
+
(
a
%
b
)
∗
y
1
=
g
c
d
b*x_1+(a\%b)*y_1=gcd
b∗x1+(a%b)∗y1=gcd
首先我们知道
a
%
b
=
a
−
(
a
/
/
b
)
∗
b
a\%b=a-(a//b)*b
a%b=a−(a//b)∗b,带入上述式子:
b
∗
x
1
+
(
a
−
(
a
/
/
b
)
∗
b
)
∗
y
1
=
b
∗
x
1
+
a
∗
y
1
−
(
a
/
/
b
)
∗
b
)
∗
y
1
=
a
∗
y
1
+
b
∗
(
x
1
−
a
/
/
b
∗
y
1
)
=
g
c
d
b*x_1 + (a-(a//b)*b)*y_1 \\ =b*x_1 + a*y_1-(a//b)*b)*y_1 \\ =a*y_1 + b*(x_1- a//b*y_1) =gcd
b∗x1+(a−(a//b)∗b)∗y1=b∗x1+a∗y1−(a//b)∗b)∗y1=a∗y1+b∗(x1−a//b∗y1)=gcd
发现
x
=
y
1
,
y
=
x
1
−
a
/
/
b
∗
y
1
x=y_1, y = x_1-a//b*y_1
x=y1,y=x1−a//b∗y1,这样就得到了相邻状态的x和y的变化,就可以反推出x和y的值了。这样可以求解出答案,但是得到的x可能为负值或者x并不是最小正整数解。
对于
a
x
+
b
y
=
m
ax+by=m
ax+by=m,我们知道a,b的最小公倍数等于
(
a
b
)
/
g
c
d
(
a
,
b
)
(ab)/gcd(a, b)
(ab)/gcd(a,b) ,那么当
x
=
x
+
b
/
g
c
d
(
a
,
b
)
y
=
y
−
a
/
g
c
d
(
a
,
b
)
带入原式
=
>
a
(
x
+
b
/
g
c
d
(
a
,
b
)
)
+
b
(
y
−
a
/
g
c
d
(
a
,
b
)
)
x = x+b/gcd(a, b) \\ y = y-a/gcd(a,b) \\ 带入原式=> a(x+b/gcd(a, b))+b(y-a/gcd(a,b))
x=x+b/gcd(a,b)y=y−a/gcd(a,b)带入原式=>a(x+b/gcd(a,b))+b(y−a/gcd(a,b))
可以看出来原式的值不变,也就是说x、y的变化周期T为
b
/
g
c
d
(
a
,
b
)
、
a
/
g
c
d
(
a
,
b
)
b/gcd(a, b)、a/gcd(a,b)
b/gcd(a,b)、a/gcd(a,b),由此我们就可以使用x = (x+T)%T
得到最小正整数解。另外值得一提的是,在使用扩展欧几里得算法求解逆元时(如下述代码),由于输入的a、p为互质的两个数字,故
g
c
d
(
a
,
p
)
=
1
gcd(a, p)=1
gcd(a,p)=1,此时x的周期
T
=
p
/
g
c
d
(
a
,
p
)
=
p
T=p/gcd(a,p)=p
T=p/gcd(a,p)=p。
def exgcd(a, b):
if b == 0:
return 1, 0, a
else:
x, y, q = exgcd(b , a%b)
x, y = y, (x - (a//b)*y)
return x, y, q
# 使用扩展欧几里得算法求解逆元
def ModReverse(a,p):
x, y, q = exgcd(a,p)
if q != 1:
raise Exception("No solution.")
else:
return (x + p) % p #防止负数
2 密钥生成步骤
Alice要和Bob通信,以下是她通过RSA算法生成公钥和私钥的步骤:
-
选择两个不相等的质数p、q。
此处,Alice选择61和53。
在实际情况中,p和q越大就越难破解。
-
计算p、q乘积n的欧拉函数,因为p和q都是质数,所以 Φ ( n ) \Phi(n) Φ(n)可以通过下述公式计算:
Φ ( n ) = Φ ( p q ) = Φ ( p ) Φ ( q ) = ( p − 1 ) ( q − 1 ) \Phi(n) = \Phi(pq) = \Phi(p)\Phi(q) = (p-1)(q-1) Φ(n)=Φ(pq)=Φ(p)Φ(q)=(p−1)(q−1)
Alice计算出 Φ ( n ) = Φ ( 3233 ) = 3120 \Phi(n) = \Phi(3233) = 3120 Φ(n)=Φ(3233)=3120 -
在 1 < e < Φ ( n ) 1<e<\Phi(n) 1<e<Φ(n)之间随机选择整数e,并且e和 Φ ( n ) \Phi(n) Φ(n)互质。
Alice选择了17。
在实际应用中,常常选择65537。
-
计算e对于 Φ ( n ) \Phi(n) Φ(n)的逆元d,即求解
e d ≡ 1 ( m o d Φ ( n ) ) ed \equiv1(mod\Phi(n)) ed≡1(modΦ(n))
Alice使用扩展辗转相除法求解出逆元d=2753. -
将n和e封装为公钥,n和d封装为私钥。
对于Alice来说(3233,17)是公钥,而(3233,2753)是私钥。
在实际应用中,公钥和私钥都需要用ASN.1格式来表达
3 RSA算法的可靠性
从上述的步骤可以看出,“n和e封装为公钥,n和d封装为私钥。”,所以**“有无可能在已知n和e的情况下,推导出d?”**,就是对RSA算法可靠性的探讨。
我们知道,如果我们知道 Φ ( n ) \Phi(n) Φ(n)就能求解出d;而 Φ ( n ) = ( p − 1 ) ( q − 1 ) \Phi(n) = (p-1)(q-1) Φ(n)=(p−1)(q−1),也就是说得知p、q就可以计算d。也就是说,如果n可以被因数分解,那么d就可以被算出,也就意味着私钥被破解。但是,大整数的因数分解是一件很困难的事情,目前除暴力破解之外没有发现别的有效方法。
对极大整数做因数分解的难度决定了RSA算法的可靠性。换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。假如有人找到一种快速因数分解的算法,那么RSA的可靠性就会极度下降。但找到这样的算法的可能性是非常小的。今天只有短的RSA密钥才可能被暴力破解。到2008年为止,世界上还没有任何可靠的攻击RSA算法的方式。只要密钥长度足够长,用RSA加密的信息实际上是不能被解破的
4 RSA算法的加密和解密
4.1 RSA加密需要使用公钥(n ,e)
假设Bob要向Alice发送加密信息m,他就要使用Alice的公钥(n, e)对m进行加密,这里的m必须为整数(如果是字符的话可以取ascii值或者unicode值),且m必须小于n。
下式中c就是m加密后的值:
m
e
≡
c
(
m
o
d
n
)
m^e\equiv c(mod\ n)
me≡c(mod n)
假设Alice的公钥是(3233, 17),Bob的m假设为65,那么可以计算出
6 5 17 ≡ 2790 ( m o d 3233 ) 65^{17}\equiv 2790(mod\ 3233) 6517≡2790(mod 3233)
于是,c=2790,Bob就把2790发送给Alice。
4.2 RSA解密需要使用私钥(n, d)
Alice在得知Bob发送来的c之后,通过下面的等式就可以求解出m:
c
d
≡
m
(
m
o
d
n
)
c^d \equiv m(mod\ n)
cd≡m(mod n)
c=2790,私钥是(3233, 2753),那么Alice计算出:
279 0 2753 ≡ 65 ( m o d 3233 ) 2790^{2753}\equiv 65(mod\ 3233) 27902753≡65(mod 3233)
至此,Alice就可以知道Bob加密之前的原文是65.
5 题目
求明文:
openssl rsa -pubin -text -modulus -in pubkey.pem
运行结果:
Public-Key: (256 bit)
Exponent: 65537 (0x10001)
Modulus=C2636AE5C3D8E43FFB97AB09028F1AAC6C0BF6CD3D70EBCA281BFFE97FBE30DD
writing RSA key
-----BEGIN PUBLIC KEY-----
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhAMJjauXD2OQ/+5erCQKPGqxsC/bNPXDr
yigb/+l/vjDdAgMBAAE=
-----END PUBLIC KEY-----
pip3 install pycryptodome
在/Users/apple/Desktop/HelloWorld/Python_Main/venv/lib/python3.9/site-packages
下的crypto
的首字母改为大写,最后是/Users/apple/Desktop/HelloWorld/Python_Main/venv/lib/python3.9/site-packages/Crypto
。
# rsa_test.py
from Crypto.PublicKey import RSA
from Crypto.Util.number import inverse
def Gcd(a, b):
if b == 0:
return a
else:
return Gcd(b, a%b)
def Exgcd(a, b):
if b == 0:
return 1, 0, a
else:
x, y, q = Exgcd(b, a % b)
x, y = y, (x - (a // b) * y)
return x, y, q
def ModReverse(a,p):
x, y, q = Exgcd(a,p)
if q != 1:
raise Exception("No solution.")
else:
return (x + p) % p #防止负数
# arsa = RSA.generate(1024)
# arsa.p = p
# arsa.q = q
# arsa.e = e
# arsa.n = arsa.q * arsa.p
# arsa.d = d
n = int('C2636AE5C3D8E43FFB97AB09028F1AAC6C0BF6CD3D70EBCA281BFFE97FBE30DD', 16)
# http://factordb.com
p = 275127860351348928173285174381581152299
q = 319576316814478949870590164193048041239
e = 65537
Phi_n = (p-1)*(q-1)
d = ModReverse(e, Phi_n)
u = inverse(p, q)
private_key = open('private_key.pem', 'wb')
arsa = RSA.RsaKey(n=n, e=e, d=d, p=p, q=q, u=u)
private_key.write(arsa.exportKey())
private_key.close()
这个地方原本的代码是RSA.generate(1024)
,但是如果这样在对p赋值时,会报错:
Traceback (most recent call last):
File “/Users/apple/Desktop/HelloWorld/Python_Main/rsa_test.py”, line 35, in
arsa.p = p
AttributeError: can’t set attribute
在翻阅generate(1024)
的源码后,发现函数末尾是这样的:
if p > q: p, q = q, p u = p.inverse(q) return RsaKey(n=n, e=e, d=d, p=p, q=q, u=u)
发现相比较RSA算法中的参数,多出参数u,是经过inverse(q, p)
计算出来的,inverse()
函数可以从from Crypto.Util.number import inverse
引入,然后查看Rsakey
对象的__init__()
的输入参数:
class RsaKey(object): r"""Class defining an actual RSA key. Do not instantiate directly. Use :func:`generate`, :func:`construct` or :func:`import_key` instead. :ivar n: RSA modulus :vartype n: integer :ivar e: RSA public exponent :vartype e: integer :ivar d: RSA private exponent :vartype d: integer :ivar p: First factor of the RSA modulus :vartype p: integer :ivar q: Second factor of the RSA modulus :vartype q: integer :ivar u: Chinese remainder component (:math:`p^{-1} \text{mod } q`) :vartype u: integer :undocumented: exportKey, publickey """ def __init__(self, **kwargs): """Build an RSA key. :Keywords: n : integer The modulus. e : integer The public exponent. d : integer The private exponent. Only required for private keys. p : integer The first factor of the modulus. Only required for private keys. q : integer The second factor of the modulus. Only required for private keys. u : integer The CRT coefficient (inverse of p modulo q). Only required for private keys. """ input_set = set(kwargs.keys()) public_set = set(('n', 'e')) private_set = public_set | set(('p', 'q', 'd', 'u')) if input_set not in (private_set, public_set): raise ValueError("Some RSA components are missing") for component, value in kwargs.items(): setattr(self, "_" + component, value) if input_set == private_set: self._dp = self._d % (self._p - 1) # = (e⁻¹) mod (p-1) self._dq = self._d % (self._q - 1) # = (e⁻¹) mod (q-1)
发现可以不使用generate()
,直接计算出相应的参数,初始化RsaKey
对象,所以写出上述rsa_test.py
。
openssl rsautl -decrypt -in flag.enc -inkey private_key.pem
PCTF{256b_i5_m3dium}