RSA加解密演算法及python程式碼實現
寫在前面:本程式碼只需呼叫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)
(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
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
c
≡
m
e
mod
n
c\equiv m^e\ \text{mod\ }n
c≡memodn
1.3 解密
對密文分組的解密運算為:
m
≡
c
d
mod
n
m\equiv c^d\ \text{mod\ }n
m≡cdmodn
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)modn=[(amodn)×(bmodn)]modn
就可減小中間結果。再者,考慮如何提高加密和解密運算中指數運算的有效性。
本文采用模重複平方演算法求解形如
b
n
mod
m
b^n\text{mod\ }m
bnmodm的式子:
一般,求
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
bnmodm的計算可歸納為
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(modm)
我們最多作
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(modp)。
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(modm),則
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(modm),如果
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 加密體制破譯)賽題三,本文不再贅述。