1. 程式人生 > >區塊鏈中的密碼學之非對稱密碼RSA算法(十)

區塊鏈中的密碼學之非對稱密碼RSA算法(十)

非對稱 獲得 pre inverse ive 自己的 gin for 逆運算

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

分兩種情況:

  1. (m, n)=1,由Euler定理m^φ(n) modn=1, 因此 m^kφ(n)modn=1,於是m^kφ(n)+1modn=m,即c^d mod n=m
  2. (m, n) =?1,設m=tp, 0<t<q。
    因為(tp, q)=1,由Euler定理得m^φ(n)modq=1。 所以存在整數r,滿足m^φ(n)=1+rq。 等式兩邊同乘以m,得m^kφ(n)+1=m+rtpq 因此,c^dmodn=m^φ(n)+1modn=(m+rtpq)modn=m

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算法(十)