写在前面:本代码只需调用random库,关于逆元、素数、模幂等的求解均为自编函数。
1. RSA算法描述
1.1 密钥的产生
(1)选两个保密的大素数
p
p
p和
q
q
q;
(2)计算
n
=
p
×
q
,
φ
(
n
)
=
(
p
−
1
)
(
q
−
1
)
n=p\times q,\ \varphi \left( n \right) =\left( p-1 \right) \left( q-1 \right)
n=p×q, φ(n)=(p−1)(q−1),其中
φ
(
n
)
\varphi \left( n \right)
φ(n)是
n
n
n的欧拉函数;
(3)选一整数
e
e
e,满足
1
<
e
<
φ
(
n
)
1<e<\varphi \left( n \right)
1<e<φ(n),且
gcd
(
φ
(
n
)
,
e
)
=
1
\text{gcd}\left( \varphi \left( n \right) ,e \right) =1
gcd(φ(n),e)=1;
(4)计算
d
d
d,满足
d
⋅
e
≡
1
mod
φ
(
n
)
d\cdot e\ \equiv \ 1\ \text{mod\ }\varphi \left( n \right)
d⋅e ≡ 1 mod φ(n)
即
d
d
d是
e
e
e在模
φ
(
n
)
\varphi \left( n \right)
φ(n)下的乘法逆元,因
e
e
e与
φ
(
n
)
\varphi \left( n \right)
φ(n)互素,由模运算可知,它的乘法逆元一定存在。
(5)以
{
e
,
n
}
\{e,n\}
{e,n}为公钥,
{
d
,
n
}
\{d,n\}
{d,n}为私钥。
1.2 加密
加密时首先将明文比特串分组,使得每个分组对应的十进制数小于
n
n
n,即分组长度小于
log
2
n
\log _2n
log2n。然后对每个明文分组
m
m
m,作加密运算:
c
≡
m
e
mod
n
c\equiv m^e\ \text{mod\ }n
c≡me mod n
1.3 解密
对密文分组的解密运算为:
m
≡
c
d
mod
n
m\equiv c^d\ \text{mod\ }n
m≡cd mod n
1.4 RSA算法中解密过程的正确性证明

2. 辅助模块算法
2.1 模重复平方算法
RSA的加密和解密过程都为求一个整数的整数次幂,再取模。如果按其含义直接计算,则中间结果非常大,有可能超出计算机所允许的整数取值范围。而用模运算的性质:
(
a
×
b
)
mod
n
=
[
(
a
mod
n
)
×
(
b
mod
n
)
]
mod
n
\left( a\times b \right) \ \text{mod\ }n=\left[ \left( a\text{mod\ }n \right) \times \left( b\text{mod\ }n \right) \right] \text{mod\ }n
(a×b) mod n=[(amod n)×(bmod n)]mod n
就可减小中间结果。再者,考虑如何提高加密和解密运算中指数运算的有效性。
本文采用模重复平方算法求解形如
b
n
mod
m
b^n\text{mod\ }m
bnmod m的式子:
一般,求
b
n
b^n
bn可如下进行,其中,
b
b
b、
n
n
n是正整数:
将
n
n
n表示为二进制:
n
k
−
1
,
n
k
−
2
,
.
.
.
n
1
,
n
0
\boldsymbol{n}_{k-1},\boldsymbol{n}_{k-2},...\boldsymbol{n}_1,\boldsymbol{n}_0
nk−1,nk−2,...n1,n0,其中
n
i
∈
{
0
,
1
}
,
i
=
0
,
1
,
.
.
.
,
k
−
1
n_i\in \{0,1\},i=0,1,...,k-1
ni∈{0,1},i=0,1,...,k−1。则
n
=
n
0
+
n
1
2
+
⋯
+
n
k
−
1
2
k
−
1
n=n_0+n_12+\cdots +n_{k-1}2^{k-1}
n=n0+n12+⋯+nk−12k−1
则
b
n
mod
m
b^n\text{mod\ }m
bnmod m的计算可归纳为
b
n
≡
b
n
0
(
b
2
)
n
1
⋯
⋯
(
b
2
k
−
2
)
n
k
−
2
⋅
(
b
2
k
−
1
)
n
k
−
1
(
mod
m
)
b^n\equiv b^{n_0}\left( b^2 \right) ^{n_1}\cdots \cdots \left( b^{2^{k-2}} \right) ^{n_{k-2}}\cdot \left( b^{2^{k-1}} \right) ^{n_{k-1}}\left( \text{mod\ }m \right)
bn≡bn0(b2)n1⋯⋯(b2k−2)nk−2⋅(b2k−1)nk−1(mod m)
我们最多作
2
[
log
2
n
]
2\left[ \log _2n \right]
2[log2n]次乘法,这个计算方法叫做“模重复平方算法”。
故该模块的代码为:
def fastExpMod(b,n,m):
'''
return : b^n mod m
'''
result = 1
while n != 0:
if (n & 1) == 1: #按位与&操作
result = (result * b) % m
b = (b*b) % m
n = n >> 1 #位数右移>>操作
return result
2.2 欧几里得算法
在密钥的生成过程中,需要求解 e e e的模逆运算,利用欧几里得算法可以求解,具体原理本文不再赘述,只给出算法步骤如下:

故该模块的代码为:
def Euclid(a,b):
'''
欧几里得算法 ax + by = gcd(a,b)
Return : [x , y , gcd(a,b)]
注:a与b互素时,x为a模b的逆元
'''
X = [1,0,a]
Y = [0,1,b]
while Y[2] !=0 :
Q = X[2]//Y[2]
NEW_Y = [i*Q for i in Y]
T = list(map(lambda x: x[0]-x[1], zip(X, NEW_Y))) # X和NEW_Y做相减操作
X = Y.copy()
Y = T.copy()
return X
2.3 素性检验算法
素性检验算法种类较多,本文采用费马素性检测算法判断随机生成的大整数是不是素数。读者可以选择其他类型的素性检验算法,例如Miller-Rabin(n)等。
Fermat小定理:
给定素数
p
,
a
∈
Z
p,a\in Z
p,a∈Z,则有
a
p
−
1
≡
1
(
mod
p
)
a^{p-1}\equiv 1\left( \text{mod\ }p \right)
ap−1≡1(mod p)。
Fermat小定理推论:
如果有一个整数
a
a
a,
(
a
,
m
)
=
1
\left( a,m \right) =1
(a,m)=1,使得
a
m
−
1
≡
1
(
mod
m
)
a^{m-1}\equiv 1\left( \text{mod\ }m \right)
am−1≡1(mod m),则
m
m
m至少有
1
/
2
1/2
1/2的概率为素数。
根据Fermat小定理及其推论,给定任意一个大整数 m m m以及安全参数 k k k,我们便可以判断该大整数的素性。Fermat素性检验算法步骤具体如下:
Step1:若
m
m
m为偶数,跳出程序,得出结论
m
m
m为合数;否则,继续执行Step2;
Step2:随机选取整数
a
a
a,使得
2
≤
a
≤
m
−
2
2\le a\le m-2
2≤a≤m−2;
Step3:计算
a
a
a与
m
m
m的最大公因数
g
=
(
a
,
m
)
g=\left( a,m \right)
g=(a,m),如果
g
=
1
g=1
g=1,继续执行;否则跳出,认为
m
m
m为合数;
Step4:计算
r
=
a
m
−
1
(
mod
m
)
r=a^{m-1}\left( \text{mod\ }m \right)
r=am−1(mod m),如果
r
=
1
r=1
r=1,
m
m
m可能为素数跳转执行Step2;否则跳出,
m
m
m为合数;
Step5:重复上述Step2-4过程
k
k
k次,如果每次得到
m
m
m均可能为素数,则 为素数的概率为
1
−
1
2
k
1-\frac{1}{2^k}
1−2k1。
故该模块的代码为:
def fermatPrimeTest(m,k):
'''
费马素性检验算法
m : 给定整数
k : 安全参数,重复K次
'''
if m % 2==0:
return False
for i in range(k):
a = random.randint(2,m-2)
g = Euclid(a, m) #欧几里得见2.2
if g[2] == 1:
r = fastExpMod(a,m-1,m) #模重复平方算法见2.1
if r ==1:
continue
else:
return False
else:
return False
return True
3. 源代码
'''
RSA加解密算法
2020.11.28
1.模平方算法 2.欧几里得算法 3.费马素性检测算法
'''
import random
def fastExpMod(b,n,m):
'''
return : b^n mod m
'''
result = 1
while n != 0:
if (n & 1) == 1: #按位与操作
result = (result * b) % m
b = (b*b) % m
n = n >> 1 #位数右移操作
return result
def Euclid(a,b):
'''
欧几里得算法 ax + by = gcd(a,b)
Return : [x , y , gcd(a,b)]
'''
X = [1,0,a]
Y = [0,1,b]
while Y[2] !=0 :
Q = X[2]//Y[2]
NEW_Y = [i*Q for i in Y]
T = list(map(lambda x: x[0]-x[1], zip(X, NEW_Y)))
X = Y.copy()
Y = T.copy()
return X
def fermatPrimeTest(m,k):
'''
费马素性检验算法
m : 给定整数
k : 安全参数,重复K次
'''
if m % 2==0:
return False
for i in range(k):
a = random.randint(2,m-2)
g = Euclid(a, m)
if g[2] == 1:
r = fastExpMod(a,m-1,m)
if r ==1:
continue
else:
return False
else:
return False
return True
def findPrime(lower,upper):
'''
return : 一个位于upper和lower之间的素数
'''
while True:
n = random.randint(lower, upper)
if fermatPrimeTest(n,6) == True :
return n
def selectE(fn):
'''
fn : euler function
Return : e
'''
while True:
e = random.randint(1, fn)
temp = Euclid(e,fn)
if temp[2] == 1:
return e
def keyGenerate(lower,upper):
'''
给定两个素数p和q生成的区间
return : e,n,d
'''
p = findPrime(lower, upper)
q = findPrime(lower, upper)
print("p:"+str(p)+" q:"+str(q))
# print("q:"+str(q))
n = p*q
fn = (p-1)*(q-1)
e = selectE(fn)
temp = Euclid(e, fn) # 欧几里得算法求逆元
d = temp[0]
if d < 0: # 由于e和fn互素故一定存在逆元
d = d + fn # 保证d为正数
return e,n,d
def start():
e,n,d = keyGenerate(1000,10000) # 密钥生成
#更改keyGenerate函数的两个参数,可以改变生成素数的位数大小。
print("public key (e,n):",end="")
print("("+str(e)+" , "+str(n)+")\n")
print("private key d: "+str(d)+"\n")
m = random.randint(1, n) # m < n m为明文
print("Plaintext: "+str(m))
c = fastExpMod(m, e, n) # 加密 c为密文 m^e mod n
print("\nEncryption of PlainText: "+str(c))
x = fastExpMod(c, d, n) # 解密 c^d mod n
print("\nDecryption of CipherText: "+str(x))
if x == m:
print("\nThe plaintext and ciphertext are the same.")
if __name__ =="__main__":
start()
结果测试:
测试一(设定p,q是位于 2 511 ∼ 2 512 2^{511}\sim 2^{512} 2511∼2512之间的大素数)

测试二 (设定p,q位于1000到10000之间)

参考文献
另:RSA算法本身被证明是安全的,但若RSA的参数选取不当,会带来很多的安全隐患。例如共模攻击、低加密指数攻击、费马分解法、因数碰撞攻击等,读者可以参考2016年全国大学生密码技术竞赛(RSA 加密体制破译)赛题三,本文不再赘述。