1. 程式人生 > 其它 >RSA加解密演算法及python程式碼實現

RSA加解密演算法及python程式碼實現

技術標籤:密碼學rsapython

寫在前面:本程式碼只需呼叫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)=(p1)(q1),其中 φ ( 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)
de1modφ(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 cmemodn

1.3 解密

  對密文分組的解密運算為:
m ≡ c d mod n m\equiv c^d\ \text{mod\ }n mcdmodn

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 nk1,nk2,...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,...,k1。則
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++nk12k1
  則 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) bnbn0(b2)n1(b2k2)nk2(b2k1)nk1(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,aZ,則有 a p − 1 ≡ 1 ( mod p ) a^{p-1}\equiv 1\left( \text{mod\ }p \right) ap11(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) am11(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 2am2
  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=am1(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} 12k1

  故該模組的程式碼為:

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} 25112512之間的大素數)

測試二 (設定p,q位於1000到10000之間)

參考文獻

RSA演算法python實現

模指數運算

另:RSA演算法本身被證明是安全的,但若RSA的引數選取不當,會帶來很多的安全隱患。例如共模攻擊、低加密指數攻擊、費馬分解法、因數碰撞攻擊等,讀者可以參考2016年全國大學生密碼技術競賽(RSA 加密體制破譯)賽題三,本文不再贅述。

RSA_Breaking Code

2016密碼挑戰賽(RSA 加密體制破譯)解題過程