1. 程式人生 > >數據加密--詳解 RSA加密算法 原理與實現

數據加密--詳解 RSA加密算法 原理與實現

pri mir 對稱加密 模運算 速度 探討 進制 成績 分析

RSA算法簡介

RSA是最流行的非對稱加密算法之一。也被稱為公鑰加密。它是由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)在1977年一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們三人姓氏開頭字母拼在一起組成的。

RSA是非對稱的,也就是用來加密的密鑰和用來解密的密鑰不是同一個。

和DES一樣的是,RSA也是分組加密算法,不同的是分組大小可以根據密鑰的大小而改變。如果加密的數據不是分組大小的整數倍,則會根據具體的應用方式增加額外的填充位。

RSA作為一種非對稱的加密算法,其中很重要的一特點是當數據在網絡中傳輸時,用來加密數據的密鑰並不需要也和數據一起傳送。因此,這就減少了密鑰泄露的可能性。RSA在不允許加密方解密數據時也很有用,加密的一方使用一個密鑰,稱為公鑰,解密的一方使用另一個密鑰,稱為私鑰,私鑰需要保持其私有性。

RSA被認為是非常安全的,不過計算速度要比DES慢很多。同DES一樣,其安全性也從未被證明過,但想攻破RSA算法涉及的大數(至少200位的大數)的因子分解是一個極其困難的問題。所以,由於缺乏解決大數的因子分解的有效方法,因此,可以推測出目前沒有有效的辦法可以破解RSA。

RSA算法基於的原理,基本上來說,加密和解密數據圍繞著模冪運算,這是取模計算中的一種。取模計算是整數計算中的一種常見形式。x mod n的結果就是x / n的余數。比如,40 mod 13 = 1,因為40 / 13 = 3,余數為1。模冪運算就是計算ab mod n的過程。

計算公鑰和私鑰

RSA中的公鑰和私鑰需要結合在一起工作。公鑰用來對數據塊加密,之後 ,只有對應的私鑰才能用來解密。生成密鑰時,需要遵循幾個步驟以確保公鑰和私鑰的這種關系能夠正常工作。這些步驟也確保沒有實際方法能夠從一個密鑰推出另一個。

開始前,首先要選擇兩個大的素數,記為p和q。根據當今求解大數因子的技術水平,這兩個數應該至少有200位,這們在實踐中才可以認為是安全的。

然後,開始計算n:

n = pq

接下來,選擇一個小的奇數e,它將成為公鑰的一部分。選擇e最需要考慮的重點是它與(p-1)(q-1)不能有相同的因子。換句話說,e與(p-1)(q-1)是互為素數關系的。比如,如果p=11而q=19,那麽n=11 X 19=209。這裏選擇e=17,因為(p-1)(q-1)=10 X 18 =180,而17和180沒有相同的因子。通常選擇3、17、65、537作為e的值。使用這些值不會對RSA的安全性造成影響,因為解密數據還需要用到私鑰。

一旦為e選擇了一個值,接下來開始計算相對應的值d,d將成為私鑰的一部分。d的值就是計算e的倒數對(p-1)(q-1)的取模結果,公式如下:

d = e-1 mod (p-1)(q-1)

這裏d和e是模乘法逆元的關系。

思考一下這個問題:當d為多少時可以滿足ed mod (p-1)(q-1) = 1 ?比如在等式 17d mod 180 = 1中,d的一個可能值是53。其他的可能值是233、413、593等。在實踐中,可以利用歐幾裏德算法來計算模乘法逆元。這裏就不再展開。

現在有了e和d的值,將(e,n)作為公鑰P將(d,n)作為私鑰S並保持其不可見。表示為:

P = (e,n) , S = (d,n)

加密方使用P來加密數據,解密方使用S來解密。為了防止就算有人知道了P也無法推算出S,必須保證p和q的值絕對不能暴露

P和S結合在一起提供的安全性來自於一個事實,那就是乘法是一種很好的單向函數。

單向函數是加密技術的基礎簡單的說,單向函數就是在一個方向上能夠很容易算出結果,但反向推導則是不切實際的。比如,在RSA算法中,計算p和q的成績是一種單向函數,因為盡管計算p和q的成績很容易,但將n反向因子分解為p和q則是極其耗時的。這裏,選擇的p和q的值要足夠大才可以。

計算P和S的步驟起源於歐拉函數中的一些有趣性質。特別是,這些性質允許對模冪運算做一些有用的操作。

歐拉函數記為φ(n),定義所有小於n的正整數裏和n互素的整數的個數

只有當兩個整數的唯一公因子為1時,才說這兩個整數是互素的。例如,φ(8)=4,因為一共只用4個比8小的整數是互素的,它們是1,3,5,7。

歐拉方程有兩個性質對RSA算法來說是特別重要的。

第一,當n是素數時,φ(n)=n-1。這是由於n的唯一因子是1和n,因此,n與之前的所有n-1個正整數都是互素的。

另一個有趣的性質是對於任意小於n且與n互素的正整數a,都有aφ(n) mod n = 1。例如,14 mod 8 = 1, 34 mod 8 = 1, 54 mod 8 = 1, 74 mod 8 = 1。對上述方程兩邊都乘以a,得到:

(a)(aφ(n) mod n)=a,或者aφ(n)+1 mod n = a

因此,可以得到15 mod 8 = 1, 35 mod 8 = 3, 55 mod 8 = 5, 75 mod 8 = 7。

調整之後得到的等式非常強大。因為對於某些等式c = me mod n,該等於可以讓我們找出一個d的值,使得cd mod n = m。

這就是RSA算法中允許加密數據,之後再解密回原文的恒等式。可以按照如下方式表示:

cd mod n = (me)d mod n = med mod n = mφ(n)+1 mod n = m mod n

歐拉函數和指數間的關系保證了加密的任意數據都能夠唯一地解密回來。為了找出d的值,解方程d = e-1 φ(n) +1。不巧的是,對於方程d = e-1φ(n)+1不一定總是有整數解。為了解決這種問題,轉而計算

d mod φ(n)的值。換句話說,d = (e-1 φ(n) + 1) mod φ(n),可以簡化為:

d = e-1 mod φ(n)

我們可以得到這樣的簡化形式,因為(φ(n)+1) mod φ(n) = (φ(n)+1) - φ(n) = 1。可以用任意的正整數替代φ(n)來證明等式成立。註意這個方程式同之前計算密鑰的過程中得出d的推導式之間的相似之處。這提供了一種通過e和n來計算d的方法。當然了,e和n是公開的,對於攻擊者來說是事先可知的,因此就有人問,這難道不是給了攻擊者相同的機會來計算出私鑰嗎?討論到這裏,是時候來探討一下RSA算法安全性保障的由來了。

RSA算法的安全性保障來自一個重要的事實,那就是歐拉函數是乘法性質的。這意味著如果p和q是互素的,那麽有φ(pq)=φ(p)φ(q)。因此,如果有兩個素數p和q,且n=p*q,則φ(n)=(p-1)(q-1),而且最重要的是:

d = e-1 mod (p-1)(q-1)

因此,盡管攻擊者可能知道了e和n的值,為了計算出d必須知道φ(n),而這又必須同時得到p和q的值才能辦到。由於p和q是不可知的,因此攻擊者只能計算n的因子,只要給出的p和q的值足夠大,這就是一個相當耗費時間的過程。

加密和解密數據分組

要使用RSA算法對數據進行加密和解密,首先要確定分組的大小。為了實現這一步,必須確保該分組可以保存的最大數值要小於n的位數。比如,如果p和q都是200位數字的素數,則n的結果將小於400位。因而,所選擇的分組所能保存的最大值應該要以是接近於400。在實踐中,通常選擇的位數都是比n小的2的整數次冪。比如,如果n是209,要選擇的分組大小就是7位,因為27 = 128比209小,但28 = 256又大於209。

要從緩沖區M中加密第(i)組明文Mi ,使用公鑰(e,n)來獲取M的數值,對其求e次冪,然後再對n取模。這將產生一組密文Ci對n的取模操作確保了Ci將和明文的分組大小保持一致。因而,要加密明文分組有:

Ci = Mie mod n

之前提到過,歐拉函數是采用冪模運算來加密數據的基礎,根據歐拉函數及其推導式,能夠將密文解密回原文。

要對緩沖區中C中的第(i)組密文進行解密,使用私鑰(d,n)來獲取Ci的數值部分,對其求d次冪,然後再對n取模。這將產生一組明文Mi。因此,要解密密文分組有:

Mi = Cid mod n

RSA的接口定義

rsa_encipher


void rsa_encipher(Huge plaintext, Huge *ciphertext, RsaPubKey pubkey);

返回值:無

描述采用RSA算法來加密由plaintext所指定的明文分組

pubkey是所指定的公鑰(e,n),其類型為結構體RsaPubKey。

ciphertext是返回的同plaintext同樣大小的密文分組。由調用者負責管理ciphertext所關聯的存儲空間。要加密大段的數據,可以按照前面介紹的分組加密模式來調用rsa_encipher。

復雜度:O(1)

rsa_decipher


void rsa_decipher(Huge ciphertext, Huge *plaintext, RsaPriKey prikey)

返回值:無

描述:采用RSA算法來解密由ciphertext所指定的密文分組。

prikey是所指定的私鑰(d,n),其類型為結構體RsaPriKey。

plaintext是返回的同ciphertext同樣大小的明文分組。由調用者負責管理plaintext所關聯的存儲空間。要解密大段的數據,可以按照前面介紹的分組加密模式來調用rsa_decipher。

復雜度:O(1)

RSA算法的實現與分析

因為RSA加密算法需要的只不過是計算ab mod n,所以基本的實現是比較簡單的。關鍵的是計算冪模的函數。

但是,要使RSA的安全性得到保障,必須使用很大的整數,這就使得事情變得復雜了。特別是,所有的計算中使用到的整數位數必須是密鑰位數的2倍(稍後將在冪模計算中看到)。因此,如果密鑰是一個200位的整數,就需要一個抽象數據類型來支持至少400位的整數。

關於大數運算已經有一些可用的函數庫。這裏不再提供具體的實現。在數據加密頭文件的示例中,定義了數據類型Huge,在安全的實現中,可以為Huge類型指定typedef別名以支持所選擇的大整數抽象數據類型。其他的需求就只剩下替換整數計算中的運算符為Huge類型所支持的操作。為了達到說明的目的,在這裏的實現中Huge類型用typedef定義為unsigned long,這種C語言內置的類型通常只能提供10位十進制數的支持。這意味著稍後的實現示例給出的實現只能支持最多5位整數的密鑰。因此,雖然示例中的實現是可用的,但如果不把Huge重定義為大數類型,這個實現就不是安全的。

rsa_encipher

rsa_encipher函數采用RSA算法將明文分組加密。

通過調用函數modexp來計算ab mod n的值,這裏a代表明文分組,b和n代表公鑰的e和n成員。為了提高執行效率,modexp使用稱為二進制平方-乘的算法來計算模冪。

二進制平方-乘算法避免了當a和b都很大時計算ab時會出現的超大中間值。比如,假設當a、b和n都是包含200位數字的超大整數,計算ab mod n,其結果是一個40 000位的整數對200位的整數取模!因為最終得到的結果就是一個200位的整數,那麽這裏的目的就是要避免出現40 000位的整數的中間值。

用二進制平方-乘算法計算ab mod n,主要是多個開平方運算的結果(如下圖)。首先,將b表示為二進制形式,然後從右邊的位開始處理對於b中的每一位,求出a的平方對n取模的結果,並將這個結果賦給a每當遇到b中為1的位時,就將當前的a值乘上另一個寄存器y(初始值為1),並將結果再賦給y一旦叠代至b的最高有效位,y的值就是ab mod n的結果。在整個計算過程中,產生的最大值是a2。因此,如果a是一個包含200位數字的整數,就不用處理大於400位的數字了。這相對於前面提到過的包含40 000位數字的整數來說已經是很大的優化了。下圖中的陰影部分展示了計算511 mod 53 = 48 828 125 mod 53 = 20的過程。在這個計算過程中,相比511 = 48 828 125,所處理的最大數值只有422 = 1764。

技術分享圖片

圖示:采用二進制平方-乘算法來計算模冪

rsa_encipher的時間復雜度為O(1),因為加密一個明文分組的所有步驟都能在恒定的時間內完成。由於分組的大小是固定的,因此modexp中的循環執行時間也是恒定的。

rsa_decipher

rsa_decipher函數采用RSA算法將密文分組進行解密

該操作通過調用modexp來解密。modexp計算ab mod n的結果,這裏a是密文分組,b和n代表私鑰成員d和n。處理過程同rsa_decipher中描述的一樣。

rsa_decipher的時間復雜度為O(1),因為解密密文分組的所有步驟都可以在恒定的時間內完成。由於分組大小固定,因此,modexp中的循環執行時間也是恒定的。

示例:數據加密的頭文件代碼

/*encrypt.h*/
#ifndef ENCRYPT_H
#define ENCRYPT_H

/*在一個安全實現中,Huge 最少要400位10進制數字*/
typedef unsigned long Huge; 

/*為RSA公鑰定義一個數據結構*/
typedef struct RsaPubKey_
{
    Huge e;
    Huge n;
}RsaPubkey;

/*為RSA私鑰定義一個數據結構*/
typedef struct RsaPriKey_
{
    Huge d;
    Huge n;
}RsaPriKey;

/*函數聲明*/
void des_encipher(const unsigned char *plaintext, unsigned char *ciphertext, const unsigned char *key);
void des_decipher(const unsigned char *ciphertext, unsigned char *plaintext, const unsigned char *key);
void rsa_encipher(Huge plaintext, Huge *ciphertext, RsaPubKey pubkey);
void rsa_decipher(Huge ciphertext,Huge *plaintext, RsaPriKey prikey);

#endif // ENCRYPT_H

示例:RSA算法的實現

/*rsa.c RSA算法實現*/
#include "encrypt.h"

/*modexp 二進制平方乘算法函數*/
static Huge modexp(Huge a, Huge b, Huge n)
{
    Huge y;
    
    /*使用二進制平方乘法計算 pow(a,b) % n*/
    y=1;
    
    while(b != 0)
    {
        /*對於b中的每個1,累加y*/
        
        if(b & 1)
            y = (y*a) % n;
        
        /*對於b中的每一位,計算a的平方*/
        a = (a*a) % n;
        
        /*準備b中的下一位*/
        b = b>>1;
    }
    
    return y;
}

/*rsa_encipher RSA算法加密*/
void rsa_encipher(Huge plaintext, Huge *ciphertext, RsaPubKey pubkey)
{
    *ciphertext = modexp(plaintext, pubkey.e, pubkey.n);
    return;
}

/*rsa_decipher RSA算法解密*/
void rsa_decipher(Huge ciphertext, Huge *plaintext, RsaPriKey prikey)
{
    *plaintext = modexp(ciphertext, prikey.d, prikdy.n);
    return;
}

數據加密--詳解 RSA加密算法 原理與實現