一文搞懂RSA算法原理及简单实现
前言
RSA算法是最重要的算法之一,它是一種非對稱加密,是目前最有影響力的加密方式之一。這篇文章我們通過實現一種簡單的RSA加密來探究它的原理。
計算公鑰和私鑰
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并保持其不可見。
如何計算d?
上面p、q、e需要預設三個素數,n很容易求出來,但是d的計算就涉及到模的運算了
什么是模、取模和模運算?
取模:
https://baike.baidu.com/item/%E5%8F%96%E6%A8%A1%E8%BF%90%E7%AE%97/10739384?fr=aladdin
模運算:
https://baike.baidu.com/item/%E6%A8%A1%E8%BF%90%E7%AE%97/4376110
具體就不細說了,但是要注意取模和取余的區別
這里d = e-1 mod (p-1)(q-1)
簡化為:
d = e-1 % m
這是乘法逆元的問題。我們對上面的進行處理
d * e = e-1 % m * e
(d * e) % m = (e-1 % m * e) %m
根據模運算的結合率
(a%p * b)%p=(a * b)%p
(d * e) % m = (e-1 % m * e) %m = (e-1 * e) % m = 1 % m
所以我們最后得到
(d * e) % m = 1 % m
這里由于n說我們自己定義的,一定是正數,所以1%n=1
所以最后變為計算
(d * e) % m = 1
并且e和d一定有一組解滿足他們都小于m。我們只需求這組解即可。
根據費馬小定理,如果a和b互質,則
ab-1 % b = 1
那么已經要求e與m互質,所以
(e * em-2) % m = 1
所以d的一個解是em-2,但是這個很可能比m大,則可以表示為m + k,那么
(e * (m + k)) % m = 1
根據模的加法運算規則
(em % m + e*k % m) % m = 1
因為em % m一定是0,所以上面的可以轉為
e*k % m = 1
如果k還大于m,則重復上面的步驟直到k小于m。這時k就是d。
因為e小于m,所以d一定有一個小于m的解使 (d * e) % m = 1成立
代碼
簡單的算法是遍歷找到d,代碼:
var q = 13; var p = 17; var n = q * p; var e = 7; var tmp = (q - 1) * (p - 1); var d; for (d = 1; ; d++) {if (e * d % tmp === 1) {break} }這里還需要進行優化,因為一般n都是超大數,而e則比較小,所以d也會很大,這里就需要大量的循環,優化后如下:
var d;for (var j = 1; j < e; j++) {if ((tmp * j + 1) % e === 0) {d = (tmp * j + 1) / e;break;}}因為 (d * e) % m = 1也就是
d * e = k * m + 1
而且d也需要小于m,所以k一定小于e,而e是比較小的值,所以我們將循環改成k即可減少大量的計算。
加密和解密數據分組
要使用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
計算過程及優化
這里加密解密的算法一樣,只不過key值不同而已,涉及的是模的冪運算
以加密為例
Ci = Mie mod n
因為需要考慮大數的問題,所以模的冪運算不能直接運算,比如如果我先直接計算Mie,由于Mi有可能是很大的數,這樣它的e次冪就會是一個超級數字,計算機無法計算和存儲
所以這里我們就需要對模冪運算進行優化,就涉及到了蒙哥馬利算法
參考
https://blog.csdn.net/zgzczzw/article/details/52712980
https://blog.csdn.net/linraise/article/details/17490769)
蒙哥馬利算法比較復雜,包含三個算法進行優化。
我們先設計一個簡單的算法,先將模冪運算轉化為模乘運算
關于模運算,有如下幾個公式:
結合律(a%p*b)%p=(a*b)%p同理((a*b) % p * c)% p = (a*b*c) % p 四則運算(a * b) % p = (a % p * b % p) % p (a^b) % p = ((a % p)^b) % p我們先利用上面兩個結合律,所以:
a2%n = (a * a) %n = (a%n * a) %n
因為a%n取模一定比a小,所以a%n*a就要比a2小很多,類推
a3%n = (a2 * a) %n = ((a2%n)*a)%n
得出
an%n = ((an-1%n)*a)%n
這是一個簡單遞歸算法,通過這個算法,每次乘完都會做一個取模運算,運算的數據就會小很多。
代碼:
function encode(x, e, n) {var result = x % n;for (var i = 1; i < e ; i++) {result = (result * x) % n;}return result }function decode(x, d, n) {var result = x % n;for (var i = 1; i < d ; i++) {result = (result * x) % n;}return result }當然,上面僅僅是簡單例子,因為如果冪數較大比如d就會是一個超大數,這樣循環次數就會很多,計算時間很長。
根據模的運算法則:
(a % p * b) % p = (a * b) % p
(a * b) % p = (a % p * b % p) % p
我們可以得出,當指數(假設為e)是偶數時
se % n = ((se/2 % n) * (se/2 % n)) % n
當為奇數時則可以先轉成偶數
se % n = ((se - 1 % n) * e) % n
這樣就可以用二分法和位運算來優化算法。如下:
function modpow(x, p, m) {if(p === 1){return mod(x, m)}var mid;if((p & 1) === 0){mid = (p >> 1);var tmp1 = modpow(x, mid, m);return mod(tmp1 * tmp1, m);}else{return mod(modpow(x, p - 1, m) * x, m)} }1、利用遞歸二分。因為開方所以每個節點的兩個子節點都相等,所以計算其中一個就可以,這樣我們只需計算二叉樹的一條路徑就可以了,整體復雜度只有O(log2n)。比如2n只需要計算n + x次(最多壞情況每次都是奇數則是2n),比2n次計算節省大量的時間,而且數據越大節省時間越多。
2、位運算。在判斷奇偶數時,沒有使用除法,因為除法運算復雜度很大,耗時比其他運算長很多。這里使用位運算,只需判斷最低位是否為0即可,而除2運算則可以用右移一位代替。因為計算機中位運算最快,所以這樣會節省大量的時間。
正確性驗證
加密我們可以理解,因為運算中有模參與,所以不可逆。但是加密后為什么通過私鑰就可以解密,解密一定正確么?
首先加密
k = se % n
然后對k解密
r = ((se % n)d) %n
根據(a^b) % p = ((a % p)^b) % p可得
r = (se)d % n = se*d % n
通過之前的計算可知 (d * e) % m = 1,而m是(q-1)(p-1),所以
d * e = k(q-1)(p-1) + 1 (k未知)
而且n=pq,所以
r = sk(q-1)(p-1) + 1 % (pq)
根據費馬小定理,如果a和b互質,則
ab-1 = 1 mod b
所以考慮兩種情況:
1、s與n即pq互質
因為p和q是兩個大質數,所以s與n互質就相當于s分別于p和q互質
所以根據費馬小定理
sp-1 = 1 mod p
即
sp-1 % p = 1
所以根據冪模的運算法則(a^b) % p = ((a % p)^b) % p
sk(q-1)(p-1) % p = (sp-1 % p)k(q-1) % p
因為sp-1 % p = 1,所以
sk(q-1)(p-1) % p = 1k(q-1) % p = 1
同理可以得出
sk(q-1)(p-1) % q = 1
所以sk(q-1)(p-1) - 1可以被p和q都整除,得出
sk(q-1)(p-1) % (pq) = 1
回到之前
r = sk(q-1)(p-1) + 1 % (pq) = (sk(q-1)(p-1) * s) % (pq)
根據模的結合率(a%p * b)%p=(a * b)%p
r = ( (sk(q-1)(p-1) % (pq)) * s ) % (pq)
上面推出sk(q-1)(p-1) % (pq) = 1,所以
r = s % (pq)
因為pq = n所以最終
r = s % n
根據上面RSA算法要求,可知s一定是小于n的數,所以s對n取模結果也是s
所以 r = s 驗證了RSA的正確性。
2、s于n不互質
總結
以上是生活随笔為你收集整理的一文搞懂RSA算法原理及简单实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅析在公众号中使用弛声sdk为什么上传解
- 下一篇: 如何实现一套可切换的声网+阿里的直播引擎