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位,這們在實踐中才能夠認爲是安全的。spa
而後,開始計算n:code
n = pqblog
接下來,選擇一個小的奇數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沒有相同的因子。一般選擇三、1七、6五、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。其餘的可能值是23三、41三、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_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加密算法須要的只不過是計算ab mod n,因此基本的實現是比較簡單的。關鍵的是計算冪模的函數。
可是,要使RSA的安全性獲得保障,必須使用很大的整數,這就使得事情變得複雜了。特別是,全部的計算中使用到的整數位數必須是密鑰位數的2倍(稍後將在冪模計算中看到)。所以,若是密鑰是一個200位的整數,就須要一個抽象數據類型來支持至少400位的整數。
關於大數運算已經有一些可用的函數庫。這裏再也不提供具體的實現。在數據加密頭文件的示例中,定義了數據類型Huge,在安全的實現中,能夠爲Huge類型指定typedef別名以支持所選擇的大整數抽象數據類型。其餘的需求就只剩下替換整數計算中的運算符爲Huge類型所支持的操做。爲了達到說明的目的,在這裏的實現中Huge類型用typedef定義爲unsigned long,這種C語言內置的類型一般只能提供10位十進制數的支持。這意味着稍後的實現示例給出的實現只能支持最多5位整數的密鑰。所以,雖然示例中的實現是可用的,但若是不把Huge重定義爲大數類型,這個實現就不是安全的。
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算法將密文分組進行解密。
該操做經過調用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; }