區塊鏈中的密碼學之非對稱密碼RSA算法(十)
1. 前言
RSA密碼是1978年美國麻省理工學院三位密碼學者R.L.Rivest、A.Shamir和L.Adleman提出的一種基於大合數因子分解困難性的公開密鑰密碼。由於RSA密碼既可用於加密,又可用於數字簽名,通俗易懂,因此RSA密碼已成為目前應用最廣泛的公開密鑰密碼。
2. RSA的密鑰生成過程
1.隨機地選擇兩個大素數p和q,而且保密;
2.計算n=pq,將n公開;
3.計算φ(n)=(p-1)(q-1),對φ(n)保密;
4.隨機地選取一個正整數e,1<e<φ(n)且(e,φ(n))=1,將e公開;
5.根據ed=1(mod φ(n)),求出d,並對d保密;
6.加密運算:c=p^e(mod n);
7.解密運算:p=c^d(mod n)。
註意:在加密運算和解密運算中,M和C的值都必須小於n,也就是說,如果明文(或密文)太大,必須進行分組加密(或解密)。
比如愛麗絲選擇了61和53。(實際應用中,這兩個質數越大,就越難破解。)
愛麗絲就把61和53相乘:n= 61×53 = 3233;n的長度就是密鑰長度。3233寫成二進制是110010100001,一共有12位,所以這個密鑰就是12位。實際應用中,RSA密鑰一般是1024位,重要場合則為2048位。
根據公式:φ(n)= (p-1)(q-1),愛麗絲算出φ(3233)等於60×52,即3120。
愛麗絲就在1到3120之間,隨機選擇了17。(實際應用中,常常選擇65537。)
計算ed≡ 1 (mod φ(n))帶入e=17,求解方程組:17x+ 3120y= 1,這個方程可以用"擴展歐幾裏得算法"求解,得到(x,y)=(2753,-15)其中私鑰d=2753
3. RSA解密正確性證明
命題:解密者使用自己的私鑰d可以恢復正確的明文m。
證明:由加密過程c=m^e modn,所以存在某整數k,滿足 c^d modn=(m^e)^dmodn =m^kφ(n)modn
分兩種情況:
- (m, n)=1,由Euler定理m^φ(n) modn=1, 因此 m^kφ(n)modn=1,於是m^kφ(n)+1modn=m,即c^d mod n=m
- (m, n) =?1,設m=tp, 0<t<q。
4. RSA算法細節
實現RSA算法,主要需要實現以下幾個部分:
1.對大數的素數判定;
2.模逆運算;
3.模指運算。
4.1 對大數的素數判定
一個較小的數是否為素數,可以用試除法來判定,而如果這個數很大的話,試除法的效率就會變得很低下。也就是說,試除法不適用於對大數進行素數判定,所以對大數的素數判定一般采用素數的概率性檢驗算法,其中又以Miller算法最為常見。
使用素數的概率性檢驗算法判定一個數是否為素數,雖然相比試除法而言效率非常之高,但是對該數的判定結果並不準確。該算法通過循環使用Miller算法來提高判定結果的正確性。
素數的概率性檢驗算法的流程:對於奇整數n,在2~n-2之間隨機地選取k個互不相同的整數,循環使用Miller算法來檢驗n是否為素數。若結果為true,則認為n可能為素數,否則肯定n為合數。
一輪Miller算法判定大整數n不是素數的概率≤4^-1,所以,素數的概率性檢驗算法判定大整數n不是素數的概率≤4^-k(k為Miller算法的循環次數)。
### 4.1.1 Miller算法
若n為奇素數,則對?a∈[2,n-2],由於a與n互素,根據歐拉定理可得a^φ(n)=a^(n-1)=1(mod n)。
若n是奇素數,則不存在1(mod n)的非平凡平方根,即對於x^2=1(mod n)的解有且僅有±1。
若n是奇素數,則n-1是偶數。不妨令n=2^t*m+1(t≥1),則m為n-1的最大奇因子。根據上述兩點,不難得出,對?a∈[2,n-2],?τ∈[1,t]使得
Miller算法正是通過上述的逆否命題而設計出來的,其原理是:對?a∈[2,n-2],n是一個合數的充要條件是對?τ∈[1,t]使得
Miller算法的設計思路:令b=a^m(mod n),如果b=±1(mod n)則n可能是一個素數;否則,b=b^2(mod n),並判斷是否滿足b=-1(mod n)(滿足則n可能是一個素數),由此循環t-1次。如果都滿足b≠-1(mod n),則n一定是一個合數。
e.g.判定221是否為素數
n=221=2^2*55+1,所以m=55,t=2,取a=174,則174^55(mod 221)=47,174^110(mod 221)=220,所以n要麽是一個素數,要麽a=174是一個“強偽證”, 再取a=137,則137^55(mod 221)=188,137^110(mod 221)=205。所以n是一個合數。
4.2 模逆運算
模逆運算就是求滿足方程ax=1(mod m)的解x,而ab=1(mod m)有解的前提條件是(a,m)=1,即a和m互素。
對方程ax=1(mod m)的求解可以轉換為求解ax+my=1=(a,m),即轉換為擴展歐幾裏德算法。
e.g.求243^-1(mod 325)
325=1325+0243
243=0325+1243
82=325-243=1325+(-1)243
79=243-282=(-2)325+3*243
3=82-79=3325+(-4)243
1=79-263=(-80)325+107*243
所以243^-1(mod 325)=107
4.3 模指運算
模指運算就是對a^n(mod m)的計算。當指數n的值較大時,如果先求出b^n再去模m的話,效率會很低下。所以,對於指數n較大的情況一般采用反復平方乘算法。
反復平方乘算法
所以,反復平方乘算法的原理是將指數n轉化為2的冪之和的形式,即n=2^kek+2^(k-1)ek-1+…+2e1+e0,然後根據l1=a^2(mod m),l2=a^4(mod m)=l1^2(mod m),...,
最後根據a^n(mod m)=e0a·e1l1·...·eklk(mod m)求解。
e.g.求23^35(mod 101)
35=32+2+1
23^1(mod 101)=23
23^2(mod 101)=24
23^4(mod 101)=24^2(mod 101)=71
23^8(mod 101)=71^2(mod 101)=92
23^16(mod 101)=92^2(mod 101)=81
23^32(mod 101)=81^2(mod 101)=97
所以2335(mod 101)=97×24×23(mod 101)=14
5. 實際編程中存在的缺陷
5.1 缺陷1:使用相同的N。
多人共用同一模數n,各自選擇不同的e和d,這樣實現 當然簡單,但是不安全。消息以兩個不同的密鑰加密, 在共用同一個模下,若兩個密鑰互素(一般如此),則可以 恢復明文。
在實現過程中,部分程序員使用相同的N,更改e來達到生成新的公私鑰對的目的。比如,一開始選擇e=3,由於過於簡單更改其e=65537,但是N不變,可能導致該問題。
實驗模擬:
? 一、準備
? 攻擊者擁有公鑰n,e1私鑰d1
? 被攻擊者擁有公鑰n,e2私鑰d2
? 二、攻擊
? 攻擊者通過e1d1≡1(mod φ(n))枚舉φ(n)
? 通過φ(n)以及e2生成私鑰d2(類似私鑰生成過程)
設e1和e2是兩個互素的不同密鑰,共用模為n,對同一消息m加密得 c1=m^e1 mod n, c2=m^e2 mod n。分析者知道n, e1, e2, c1和c2。因為(e1, e2,)=1,由擴展Euclid算法可以求得整數r,s滿足re1+se2=1。從而可得 c1^r c2^s=m mod n。
5.2 缺陷2:e和d的值設置的過小。
采用小的e可以加快加密和驗證簽字的速度,且所需的存儲密鑰空間小,但若加密鑰e選擇得太小,則容易受到攻擊。
實驗場景:
假設在一個網域中,有四個以上的用戶。(假設4個)其中一個用戶用三個不用用戶的公鑰(e,n1),(e,n2)和(e,n3)加密同一段明文消息P,得到三個不同的密文C1,C2,C3。攻擊者可以由C1,C2,C3反推明文。
實驗模擬:
一、獲得C1,C2,C3
? 分別使用不同的RSA公私鑰對同一段明文P進行加密,公私鑰對中選擇e=3.並且將加密結果(C1,C2,C3)發送給攻擊者,攻擊者得到秘文後開始反推明文。
二、還原明文
? C1=P3mod n1
? C2=P3mod n2
? C3=P3mod n3
由中國剩余定理可求出P3從而可以求出明文P
中國剩余定理:令m=n1·n2·n3,
M1=n2·n3, M2=n1·n3, M3=n1·n2
Mi‘·Mi ≡1(mod mi) i=1,2,3
P3=M1‘·M1·C1+ M2‘·M2·C2+ M3‘·M3·C3從而求出P。
5.3 缺陷3:選擇密文攻擊
實驗模擬:
? 一、被攻擊者擁有公私鑰e,n,d,並且加密了一個消息m,加密後的消息c=m^e(modn)
? 二、攻擊者選擇隨機數s,計算m‘=c*s^e (mod n)
? 三、攻擊者將m‘交給被攻擊者,要求被攻擊者解密
? 四、攻擊者計算 c’=m‘^d(modn)
? 代入得c’=med sed(modn)=ms(mod n)
? 五、攻擊者拿到c‘後計算m=c‘s-1(modn)得到了原明文
所以,e不能太小,最常用的e值為3,17,65537(2^16+1),解密指數d需要滿足d>n^1/4
6. 基於java實現RSA的加解密過程
import java.math.BigInteger;
import java.util.Random;
/**
* 1. 隨機選擇兩個質數p和q(比如61和53),這兩個數不相等,且應該是同一個量級。
* (實際應用中,這兩個質數越大,就越難破解。)
* 2. 計算n的值(n=3233),n的長度即是密鑰的長度。
* 3233寫成二進制是110010100001,一共有12位,所以這個密鑰就是12位.
* 實際應用中,RSA密鑰一般是1024位,重要場合則為2048位。
* 3. 計算n的歐拉函數φ(n)。
* 根據公式:φ(n)= (p-1)(q-1),愛麗絲算出φ(3233)等於60×52,即3120。
* 4. 隨機選擇一個整數e,條件是1<e<φ(n),且e與φ(n) 互質。
* 愛麗絲就在1到3120之間,隨機選擇了17。(實際應用中,常常選擇65537。)
* 5. 計算e對於φ(n)的模反元素d。
* 計算ed≡ 1 (mod φ(n))帶入e=17,求解方程組:17x+ 3120y= 1
* 6. 將n和e封裝成公鑰,n和d封裝成私鑰。
*/
public class RSA {
private static BigInteger n; // large prime
private static BigInteger e; // public key
private static BigInteger d; // private key
private static BigInteger p; // prime
private static BigInteger q; // prime
private static BigInteger o; //means φ(n)
public static void main(String[] args) {
String plaintext = "rsa encrypt & decrypt test";
RSA rsa = new RSA();
rsa.giveKey();
BigInteger[] encrypt = rsa.encrypt(plaintext);
System.out.println("\nplaintext:" + plaintext + "\n\nencrpyt:");
for (int i = 0; i < encrypt.length; ++i) {
System.out.println(encrypt[i]);
}
String decrypt = rsa.decrypt(encrypt);
System.out.println("\ndecrypt:" + decrypt);
}
// RSA encryption,逐位進行加密
// RSA加密過程:加密後的消息p=m^e(mod n);
public BigInteger[] encrypt(String plaintext) {
BigInteger[] encrypt = new BigInteger[plaintext.length()];
BigInteger m, p;
for (int i = 0; i < plaintext.length(); ++i) {
m = BigInteger.valueOf(plaintext.charAt(i));
p = m.modPow(e, n);
encrypt[i] = p;
}
return encrypt;
}
// RSA decryption
// RSA解密過程:還原消息m=p^d(mod n);
public String decrypt(BigInteger[] encrypt) {
StringBuffer plaintext = new StringBuffer();
BigInteger m, p;
for (int i = 0; i < encrypt.length; ++i) {
p = encrypt[i];
m = p.modPow(d, n);
plaintext.append((char) m.intValue());
}
return plaintext.toString();
}
// give public key and private key
public void giveKey() {
// get p,q,n,e,b
producePQ();
n = p.multiply(q);
o = p.subtract(new BigInteger("1")).multiply(q.subtract(new BigInteger("1")));
produceEB(o);
System.out.println("n:" + n + "\np:" + p + "\nq:" + q + "\ne:" + e + "\nd:" + d);
}
// large prime p and q generation
public void producePQ() {
p = BigInteger.probablePrime(32, new Random());
q = BigInteger.probablePrime(32, new Random());
while (p.equals(q)) {
p = BigInteger.probablePrime(32, new Random());
q = BigInteger.probablePrime(32, new Random());
}
}
// produce public key e,private key b
public void produceEB(BigInteger eulerN) {
e = BigInteger.probablePrime((int) (Math.random() * 63 + 2), new Random());
while (e.compareTo(eulerN) != -1 | eulerN.divide(e).equals(0)) {
e = BigInteger.probablePrime((int) (Math.random() * 63 + 2), new Random());
}
//e = BigInteger.valueOf(65537);//default
d = e.modInverse(eulerN);
}
}
代碼執行結果如下所示:
更多密碼學源碼請參考:
https://github.com/Anapodoton/Encryption/blob/master/RSA/RSA.java
區塊鏈中的密碼學之非對稱密碼RSA算法(十)