killBase系列 -- 密碼學(一)

前言

最近一場面試,面試官問了我
對稱加密與非對稱加密的問題,雖然曾經看過一些內容,可是沒有系統的整理,因此當被問的時候,腦子裏一片空白,沒有回答上來。所以,在這裏從新梳理一下密碼學的知識點,夯實一下基礎。html

killBase系列 -- 密碼學(二) 傳送門java

密碼學

1、基礎

  1. 密碼學算法分類:程序員

    • 消息編碼:Base64面試

    • 消息摘要:MD類, SHA類,MAC算法

    • 對稱密碼:DES,3DES,AES編程

    • 非對稱密碼:RSA,DHsegmentfault

    • 數字簽名:RSASignature,DSASignature數組

  2. 五元組安全

1)明文:原始信息。
2)加密算法:以密鑰爲參數,對明文進行多種置換和轉換的規則和步驟,變換結果爲密文。
3)解密算法:加密算法的逆變換,以密文爲輸入、密鑰爲參數,變換結果爲明文。:
4)密鑰:加密與解密算法的參數,直接影響對明文進行變換的結果。
5)密文:對明文進行變換的結果。網絡

  1. Java編程中經常使用類 -- java.security 包

    1. 消息編碼:BASE64Encoder,BASE64Decoder -- java.util

    2. 消息摘要:MessageDigest

    3. 對稱密碼:KeyGenerator,SeretkeyFactory -- javax.crypto 包(提供給AES,DES,3DES,MD5,SHA1等 對稱 和 單向加密算法。),Cipher

    4. 非對稱密碼:KeyPairGenerator,KeyFactory -- java.security 包(提供給DSA,RSA, EC等 非對稱加密算法。),KeyPair,PublicKey,PrivateKey,Cipher

    5. 數字重命名:Signature

  2. 經常使用開源工具

    • Commons.Codec

    • Bouncy.Castle

2、Base64 算法

  1. Base64 基於64個字符編碼算法,以任意 8 位字節序列組合描述形式 , BASE加密後產生的字節位數是8的倍數,若是不夠位數以=符號填充。對此 Base64 算法有一套字符映射表。

  2. 使用方法:

// 獲取
    Base64.Encoder encoder = Base64.getEncoder();
    Base64.Decoder decoder = Base64.getDecoder();
// 加密
    public byte[] encode(byte[] src);
    * @param   src
    *          the byte array to encode
    * @param   dst
    *          the output byte array
    * @return  The number of bytes written to the output byte array
    public int encode(byte[] src,byte[] dst);
    public String encodeToString(byte[] src);
    public ByteBuffer encode(ButeBuffer buffer);
// 解密
    public byte[] decode(byte[] src);
    * @param   src
    *          the byte array to encode
    * @param   dst
    *          the output byte array
    * @return  The number of bytes written to the output byte array
    public int decode(byte[] src,byte[] dst);
    public byte[] decode(String src);
    public ByteBuffer decode(ButeBuffer buffer);

3、消息摘要

  1. 介紹:又稱爲 哈希算法。惟一對應一個消息或文體固定長度值,由一個單向的Hash加密函數對消息進行做用而產生。

  2. 分類: MD(Message Digest) 消息摘要算法,SHA(Secure Hash Algorithm) 安全散列算法, MAC(Message Authentication Code):消息認證算法

  3. 主要方法:

// xxx 能夠爲 md5,sha
MessageDigest.getInstance("xxx")

1. MD5算法

原理:
首先須要對信息進行填充,使其位長對512求餘的結果等於448。
所以,信息的位長(Bits Length)將被擴展至N*512+448,N爲一個非負整數,N能夠是零。
填充的方法以下,在信息的後面填充一個1和無數個0,直到知足上面的條件時才中止用0對信息的填充。
而後,在這個結果後面附加一個以64位二進制表示的填充前信息長度。
通過這兩步的處理,信息的位長=N512+448+64=(N+1)512,即長度剛好是512的整數倍
MD5以512位分組來處理輸入的信息,且每一分組又被劃分爲16個32位子分組,通過了一系列的處理後,算法的輸出由四個32位分組組成,將這四個32位分組級聯後將生成一個128位散列值。

代碼實現

public class MD5Util {
    /*** 
     * MD5加密 生成32位md5碼
     * @param 待加密字符串
     * @return 返回32位md5碼
     */
    public static String md5Encode(String inStr) throws Exception {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }

        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        // 轉化爲 16 進制
        // 原理 : byte 爲 8 字節。 0xff --> 11111111
        // byte&0xff 若是小於16 則小於00010000 
        // 因此由 toHexString() 只能轉化爲 1 位,因此要在前面加上 ‘0’。再加上實際的值。
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
}

2. SHA 算法

原理:接收一段明文,而後以一種不可逆的方式將它轉換成一段(一般更小)密文,也能夠簡單的理解爲取一串輸入碼(稱爲預映射或信息),並把它們轉化爲長度較短、位數固定的輸出序列即散列值(也稱爲信息摘要或信息認證代碼)的過程。

特色:該算法輸入報文的長度不限,產生的輸出是一個160位的報文摘要。輸入是按 512 位的分組進行處理的。

做用:經過散列算法可實現數字簽名實現,數字簽名的原理是將要傳送的明文經過一種函數運算(Hash)轉換成報文摘要(不一樣的明文對應不一樣的報文摘要),報文摘要加密後與明文一塊兒傳送給接受方,接受方將接受的明文產生新的報文摘要與發送方的發來報文摘要解密比較,比較結果一致表示明文未被改動,若是不一致表示明文已被篡改。

代碼實現

public class SHAUtil {
    /*** 
     * SHA加密 生成40位SHA碼
     * @param 待加密字符串
     * @return 返回40位SHA碼
     */
    public static String shaEncode(String inStr) throws Exception {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }

        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = sha.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) { 
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

3. HMAC 算法

原理:用公開函數和密鑰產生一個固定長度的值做爲認證標識,用這個 標識鑑別消息的完整性。使用一個密鑰生成一個固定大小的小數據塊,即MAC,並將其加入到消息中,而後傳輸。接收方利用與發送方共享的密鑰進行鑑別認證 等。

代碼實現

// 構建密鑰
    public static byte[] getSecretKey(){
            // 初始化
            KeyGenerator keyGen = null;
            try {
                    keyGen = KeyGenerator.getInstance("HmacMD5");
            } catch (NoSuchAlgorithmException e1) {
                    e1.printStackTrace();
            }
            // 產生密鑰
            SecretKey secretKey1 = keyGen.generateKey();
            // 獲得密鑰字節數組
            byte[] key = secretKey1.getEncoded();
            return key;
    }
    // 執行消息摘要
    public static void doHMAC(byte[] data,String key){
            // 從字節數組還原
            SecretKey secretKey2 = new SecretKeySpec(key,"HmacMD5");
            try {
                    // 實例化 Mac
                    Mac mac = Mac.getInstance("HmacMD5");
                    // 密鑰初始化 Mac
                    mac.init(secretKey2);
                    // 執行消息摘要
                    byte[] result = mac.doFinal(data);
            } catch (InvalidKeyException e) {
                    e.printStackTrace();
            } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
            }
    }

4. SHA 與 MD5比較

1)對強行攻擊的安全性:最顯著和最重要的區別是SHA-1摘要比MD5摘要長32 位。使用強行技術,產生任何一個報文使其摘要等於給定報摘要的難度對MD5是2^128數量級的操做,而對SHA-1則是2^160數量級的操做。這樣,SHA-1對強行攻擊有更大的強度。
2)對密碼分析的安全性:因爲MD5的設計,易受密碼分析的攻擊,SHA-1顯得不易受這樣的攻擊。
3)速度:在相同的硬件上,SHA-1的運行速度比MD5慢。

4、對稱加密

  1. 定義:在對稱加密算法中,數據發信方將明文(原始數據)和加密密鑰(mi yue)一塊兒通過特殊加密算法處理後,使其變成複雜的加密密文發送出去。

收信方收到密文後,若想解讀原文,則須要使用加密用過的密鑰相同算法逆算法對密文進行解密,才能使其恢復成可讀明文。
在對稱加密算法中,使用的密鑰只有一個,發收信雙方都使用這個密鑰對數據進行加密和解密,這就要求解密方事先必須知道加密密鑰。

  1. 優缺點

    • 優勢:算法公開、計算量小、加密速度快、加密效率高。

    • 缺點:
      (1)交易雙方都使用一樣鑰匙,安全性得不到保證。
      (2)每對用戶每次使用對稱加密算法時,都須要使用其餘人不知道的唯一鑰匙,這會使得發收信雙方所擁有的鑰匙數量呈幾何級數增加,
      密鑰管理成爲用戶的負擔。對稱加密算法在分佈式網絡系統上使用較爲困難,主要是由於密鑰管理困難,使用成本較高。

  2. 經常使用的對稱加密算法。

DES(Data Encryption Standard):數據加密標準,速度較快,適用於加密大量數據的場合。
3DES(Triple DES):是基於DES,對一塊數據用三個不一樣的密鑰進行三次加密,強度更高。
AES(Advanced Encryption Standard):高級加密標準,是下一代的加密算法標準,速度快,安全級別最高

  1. 對稱密碼經常使用的數學運算

    • 移位和循環移位

  移位就是將一段數碼按照規定的位數總體性地左移或右移。循環右移就是當右移時,把數碼的最後的位移到數碼的最前頭,循環左移正相反。例如,對十進制數碼12345678循環右移1位(十進制位)的結果爲81234567,而循環左移1位的結果則爲23456781。

  • 置換

  就是將數碼中的某一位的值根據置換表的規定,用另外一位代替。它不像移位操做那樣整齊有序,看上去雜亂無章。這正是加密所需,被常常應用。

  • 擴展

  就是將一段數碼擴展成比原來位數更長的數碼。擴展方法有多種,例如,能夠用置換的方法,以擴展置換表來規定擴展後的數碼每一位的替代值。

  • 壓縮

  就是將一段數碼壓縮成比原來位數更短的數碼。壓縮方法有多種,例如,也能夠用置換的方法,以表來規定壓縮後的數碼每一位的替代值。

  • 異或

  這是一種二進制布爾代數運算。異或的數學符號爲⊕ ,它的運算法則以下:

1⊕ 1 = 0 
0⊕ 0 = 0 
1⊕ 0 = 1 
0⊕ 1 = 1

  也能夠簡單地理解爲,參與異或運算的兩數位如相等,則結果爲0,不等則爲1。

  • 迭代

  迭代就是屢次重複相同的運算,這在密碼算法中常用,以使得造成的密文更加難以破解。

  1. 分組加密

參考 分組加密的四種模式
ECB模式 -- 電子密碼本模式
CBC模式 -- 密碼分組連接模式
CFB模式 -- 密文反饋模式
OFB模式 -- 輸出反饋模式
CTR模式 -- 計數器模式

  1. 經常使用的填充方式

在Java進行DES、3DES和AES三種對稱加密算法時,常採用的是NoPadding(不填充)、Zeros填充(0填充)、PKCS5Padding填充。

  • ZerosPadding

    所有填充爲0的字節,結果以下:
    F1 F2 F3 F4 F5 F6 F7 F8 //第一塊
    F9 00 00 00 00 00 00 00 //第二塊

  • PKCS5Padding

    每一個填充的字節都記錄了填充的總字節數,結果以下:
    F1 F2 F3 F4 F5 F6 F7 F8 //第一塊
    F9 07 07 07 07 07 07 07 //第二塊

注: 若是

1. DES(Data Encryption Standard)

一、 介紹:

DES算法的入口參數有三個:Key、Data、Mode
Key爲8個字節共64位,其中密鑰 56 位,校驗位 8 位(每組的 第8位都被用做奇偶校驗),是DES算法的工做密鑰;
Data也爲8個字節64位,是要被加密或被解密的數據;
Mode爲DES的工做方式,有兩種:加密或解密。

二、 加密過程:

簡略版:

  • 首先要生成一套加密密鑰,從用戶處取得一個64位長的密碼口令,而後經過等分、移位、選取和迭代造成一套16個加密密鑰,分別供每一輪運算中使用。
    過程 1,2

  • DES對64位(bit)的明文分組M進行操做,M通過一個初始置換IP,置換成m0。將m0明文分紅左半部分和右半部分m0 = (L0,R0),各32位長。而後進行16輪徹底相同的運算(迭代),這些運算被稱爲函數f,在每一輪運算過程當中數據與相應的密鑰結合。
    過程 4

  • 在每一輪中,密鑰位移位,而後再從密鑰的56位中選出48位。經過一個擴展置換將數據的右半部分擴展成48位,並經過一個異或操做替代成新的48位數據,再將其壓縮置換成32位。這四步運算構成了函數f。而後,經過另外一個異或運算,函數f的輸出與左半部分結合,其結果成爲新的右半部分,原來的右半部分紅爲新的左半部分。將該操做重複16次。
    過程 3 ,5 ,6 ,7 , 8 , 9

  • 通過16輪迭代後,左,右半部分合在一塊兒通過一個逆置換(數據整理),恢復原先的順序,這樣就完成了加密過程。
    過程 10.

詳細版請見 附錄

三、 解密過程

  加密和解密使用相同的算法!
  DES加密和解密惟一的不一樣是密鑰的次序相反。若是各輪加密密鑰分別是K1,K2,K3…K16,那麼解密密鑰就是K16,K15,K14…K1。這也就是DES被稱爲對稱算法的理由吧。

四、流程如圖:

五、注意:

DES算法中只用到64位密鑰中的其中56位,而第八、1六、2四、......64位8個位並未參與DES運算

六、3DES

3DES(或稱爲Triple DES)

原理:

使用3條56位的密鑰對 數據進行三次加密。
七、Java 實現

相關的類

// 生成密鑰
KeyGenerator,SecretKeyFactory
// 密鑰
SecretKey , SecretKeySpec
// 密碼
Cipher

這裏重點講一下 Cipher 類

  1. 首先要設置參數

Cipher.getInstance(加解密算法,加解密模式,填充模式)

  1. 初始化

Cipher.init(加解密模式 -- Cypher.ENCRIPT/DECRYPT,密鑰)

  1. 完成加解密

Cipher.doFinal(bytes) -- 將bytes 內容 加密/解密 而後返回。

這裏使用 SecretKeyFactory的密鑰 選擇CBC模式 進行加解密。

public class DESCryptography {  
  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
          
        String content="aaaaaaaabbbbbbbbaaaaaaaa";  
        String key="01234567";  
      
        System.out.println("加密前:"+byteToHexString(content.getBytes()));  
        byte[] encrypted=DES_CBC_Encrypt(content.getBytes(), key.getBytes());  
        System.out.println("加密後:"+byteToHexString(encrypted));  
        byte[] decrypted=DES_CBC_Decrypt(encrypted, key.getBytes());  
        System.out.println("解密後:"+byteToHexString(decrypted));  
    }  
  
    public static byte[] DES_CBC_Encrypt(byte[] content, byte[] keyBytes){        
        try {  
            DESKeySpec keySpec=new DESKeySpec(keyBytes);  
            SecretKeyFactory keyFactory=SecretKeyFactory.getInstance("DES");              
            SecretKey key=keyFactory.generateSecret(keySpec);         
              
            Cipher cipher=Cipher.getInstance("DES/CBC/PKCS5Padding");  
            cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(keySpec.getKey()));             
            byte[] result=cipher.doFinal(content);  
            return result;  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            System.out.println("exception:"+e.toString());  
        }  
        return null;  
    }  
      
    public static byte[] DES_CBC_Decrypt(byte[] content, byte[] keyBytes){        
        try {  
            DESKeySpec keySpec=new DESKeySpec(keyBytes);  
            SecretKeyFactory keyFactory=SecretKeyFactory.getInstance("DES");  
            SecretKey key=keyFactory.generateSecret(keySpec);  
              
            Cipher cipher=Cipher.getInstance("DES/CBC/PKCS5Padding");  
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(keyBytes));  
            byte[] result=cipher.doFinal(content);  
            return result;  
        } catch (Exception e) {  
            // TODO Auto-generated catch block  
            System.out.println("exception:"+e.toString());  
        }  
        return null;  
    }  
      
    public static String byteToHexString(byte[] bytes) {  
        StringBuffer sb = new StringBuffer(bytes.length);  
        String sTemp;  
        for (int i = 0; i < bytes.length; i++) {  
            sTemp = Integer.toHexString(0xFF & bytes[i]);  
            if (sTemp.length() < 2)  
                sb.append(0);  
            sb.append(sTemp.toUpperCase());  
        }  
        return sb.toString();  
    }  
        
     private static byte toByte(char c) {  
        byte b = (byte) "0123456789ABCDEF".indexOf(c);  
        return b;  
     }  
}

2. AES(Advanced Encryption Standard)

有時間 再寫。。。 看了一天的 加密 ,累死。。。

5、非對稱加密

1. 基礎

定義:須要兩個密鑰,一個是公開密鑰,另外一個是私有密鑰;一個用做加密的時候,另外一個則用做解密。
使用其中一個密鑰把明文加密後所得的密文,只能用相對應的另外一個密鑰才能解密獲得本來的明文;甚至連最初用來加密的密鑰也不能用做解密。
因爲加密和解密須要兩個不一樣的密鑰,故被稱爲非對稱加密

數論知識:

非對稱加密運用了一部分數論知識,有興趣的本身去看下。。。 這裏提供一下連接。
阮一峯大神寫了一部分,能夠幫助理解

1、互質關係:

若是兩個正整數,除了1之外,沒有其餘公因子,咱們就稱這兩個數是互質關係(coprime)。好比,15和32沒有公因子,因此它們是互質關係。這說明,不是質數也能夠構成互質關係。
2、歐拉函數
3、歐拉定理)
4、模反元素(模逆元)
5、擴展歐幾里得算法

2. RSA 算法

2.1 過程
  1. 隨機選擇兩個不相等的質數 p 和 q

    1. = 61, q = 53

  2. 計算 p 和 q 的乘積 n

    1. = 61*53 = 3233

  3. 計算 n 的歐拉函數 φ(n)
    φ(n) = (p-1)(q-1) = 60 * 52 = 3120

  4. 隨機選擇一個整數 e , 條件是 1 < e < φ(n) , 且 e 與 φ(n) 互質

    1. = 17 ( 實際應用中,經常選擇 65537 )

  5. 計算 e 對於 φ(n) 的模反元素 d

    所謂"模反元素"就是指有一個整數d,可使得ed被φ(n)除的餘數爲1。
      ed ≡ 1 (mod φ(n))

  ed - 1 = kφ(n)

因而,找到模反元素d,實質上就是對下面這個二元一次方程求解。
  ex + φ(n)y = 1
已知 e=17, φ(n)=3120,
  17x + 3120y = 1
這個方程能夠用"擴展歐幾里得算法"求解,此處省略具體過程。總之,愛麗絲算出一組整數解爲 (x,y)=(2753,-15),即 d=2753。
至此全部計算完成。
6. 將 n 和 e 封裝成公鑰, n 和 d 封裝成私鑰
 公鑰 (3233,17), 私鑰 (3233,2753)
7. 加密與解密
 - 加密用 (n , e)
     加密信息 -- **明文**爲 m , **m 小於 n**
     $m^e$ ≡ c (mod n) 
     公鑰是 (3233,17), m 假設爲 65
     $65^{17}$ ≡ 2790(mod 3233)
     因此 c = 2790
 - 解密用 (n , d)
     **密文** 爲 c
     $c^d$ ≡ m(mod n)
     $2790^{2753}$ ≡ 65 (mod 3233)
     因此 m = 65
8. 私鑰解密的證實 -- 有興趣的同窗本身去找資料看下,也是數論的知識。        

##### 2.2 RSA 算法的可靠性 與 破解
以上密鑰的生成步驟,出現了六個數字
>  p, q, n, φ(n), e, d
公鑰爲 n, e 
若是想要獲得 d,須要進行如下逆推

  (1)ed≡1 (mod φ(n))。只有知道e和φ(n),才能算出d。
  (2)φ(n)=(p-1)(q-1)。只有知道p和q,才能算出φ(n)。
  (3)n=pq。只有將n因數分解,才能算出p和q。

因此 若是將 n 進行 **因數分解**,就意味着私鑰被破解。 但是,大整數的因數分解,是一件很是困難的事情。目前,除了暴力破解,尚未發現別的有效方法。

** 注意:**這裏說大整數,不是 像上文 3233 這樣的數字,歷史上最大的已經進行因數分解的整數爲

  12301866845301177551304949
  58384962720772853569595334
  79219732245215172640050726
  36575187452021997864693899
  56474942774063845925192557
  32630345373154826850791702
  61221429134616704292143116
  02221240479274737794080665
  351419597459856902143413

它等於這樣兩個質數的乘積

  33478071698956898786044169
  84821269081770479498371376
  85689124313889828837938780
  02287614711652531743087737
  814467999489
    ×
  36746043666799590428244633
  79962795263227915816434308
  76426760322838157396665112
  79233373417143396810270092
  798736308917

**破解:** 這裏有一篇關於 RSA 破解的文章,有興趣的同窗能夠看一下。
[RSA計時攻擊](https://juejin.im/post/5937e8252f301e006b2c4e84)

##### 2.3 Java 實現
**使用到的類**: java.security

// 生成 公鑰,密鑰
KeyPairGenerator --> KeyPair , KeyFactory --> RSA XXX Spec
// 公鑰 密鑰
KeyPair
RSAPublicKeySpec --> RSAPublicKey
RSAPrivateKeySpec --> RSAPrivateKey
// 密碼
Cipher -- 1.Cipher.getInstance("RSA")

2.init(mode, key)
        3.cipher.doFinal()

public static void main(String[] args) throws Exception {

// TODO Auto-generated method stub  
    HashMap<String, Object> map = RSAUtils.getKeys();  
    //生成公鑰和私鑰  
    RSAPublicKey publicKey = (RSAPublicKey) map.get("public");  
    RSAPrivateKey privateKey = (RSAPrivateKey) map.get("private");  
      
    //模  
    String modulus = publicKey.getModulus().toString();  
    //公鑰指數  
    String public_exponent = publicKey.getPublicExponent().toString();  
    //私鑰指數  
    String private_exponent = privateKey.getPrivateExponent().toString();  
    //明文  
    String ming = "123456789";  
    //使用模和指數生成公鑰和私鑰  
    RSAPublicKey pubKey = RSAUtils.getPublicKey(modulus, public_exponent);  
    RSAPrivateKey priKey = RSAUtils.getPrivateKey(modulus, private_exponent);  
    //加密後的密文  
    String mi = RSAUtils.encryptByPublicKey(ming, pubKey);  
    System.err.println(mi);  
    //解密後的明文  
    ming = RSAUtils.decryptByPrivateKey(mi, priKey);  
    System.err.println(ming);  
}
**RSAUtils.java**

public class RSAUtils {

/** 
 * 生成公鑰和私鑰 
 * @throws NoSuchAlgorithmException  
 * 
 */  
public static HashMap<String, Object> getKeys() throws NoSuchAlgorithmException{  
    HashMap<String, Object> map = new HashMap<String, Object>();  
    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");  
    keyPairGen.initialize(1024);  
    KeyPair keyPair = keyPairGen.generateKeyPair();  
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();  
    map.put("public", publicKey);  
    map.put("private", privateKey);  
    return map;  
}  
/** 
 * 使用模和指數生成RSA公鑰 
 * 注意:【此代碼用了默認補位方式,爲RSA/None/PKCS1Padding,不一樣JDK默認的補位方式可能不一樣,如Android默認是RSA 
 * /None/NoPadding】 
 *  
 * @param modulus 
 *            模 
 * @param exponent 
 *            指數 
 * @return 
 */  
public static RSAPublicKey getPublicKey(String modulus, String exponent) {  
    try {  
        BigInteger b1 = new BigInteger(modulus);  
        BigInteger b2 = new BigInteger(exponent);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(b1, b2);  
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);  
    } catch (Exception e) {  
        e.printStackTrace();  
        return null;  
    }  
}  

/** 
 * 使用模和指數生成RSA私鑰 
 * 注意:【此代碼用了默認補位方式,爲RSA/None/PKCS1Padding,不一樣JDK默認的補位方式可能不一樣,如Android默認是RSA 
 * /None/NoPadding】 
 *  
 * @param modulus 
 *            模 
 * @param exponent 
 *            指數 
 * @return 
 */  
public static RSAPrivateKey getPrivateKey(String modulus, String exponent) {  
    try {  
        BigInteger b1 = new BigInteger(modulus);  
        BigInteger b2 = new BigInteger(exponent);  
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");  
        RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(b1, b2);  
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);  
    } catch (Exception e) {  
        e.printStackTrace();  
        return null;  
    }  
}  

/** 
 * 公鑰加密 
 *  
 * @param data 
 * @param publicKey 
 * @return 
 * @throws Exception 
 */  
public static String encryptByPublicKey(String data, RSAPublicKey publicKey)  
        throws Exception {  
    Cipher cipher = Cipher.getInstance("RSA");  
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);  
    // 模長  
    int key_len = publicKey.getModulus().bitLength() / 8;  
    // 加密數據長度 <= 模長-11  
    String[] datas = splitString(data, key_len - 11);  
    String mi = "";  
    //若是明文長度大於模長-11則要分組加密  
    for (String s : datas) {  
        mi += bcd2Str(cipher.doFinal(s.getBytes()));  
    }  
    return mi;  
}  

/** 
 * 私鑰解密 
 *  
 * @param data 
 * @param privateKey 
 * @return 
 * @throws Exception 
 */  
public static String decryptByPrivateKey(String data, RSAPrivateKey privateKey)  
        throws Exception {  
    Cipher cipher = Cipher.getInstance("RSA");  
    cipher.init(Cipher.DECRYPT_MODE, privateKey);  
    //模長  
    int key_len = privateKey.getModulus().bitLength() / 8;  
    byte[] bytes = data.getBytes();  
    byte[] bcd = ASCII_To_BCD(bytes, bytes.length);  
    System.err.println(bcd.length);  
    //若是密文長度大於模長則要分組解密  
    String ming = "";  
    byte[][] arrays = splitArray(bcd, key_len);  
    for(byte[] arr : arrays){  
        ming += new String(cipher.doFinal(arr));  
    }  
    return ming;  
}  
/** 
 * ASCII碼轉BCD碼 
 *  
 */  
public static byte[] ASCII_To_BCD(byte[] ascii, int asc_len) {  
    byte[] bcd = new byte[asc_len / 2];  
    int j = 0;  
    for (int i = 0; i < (asc_len + 1) / 2; i++) {  
        bcd[i] = asc_to_bcd(ascii[j++]);  
        bcd[i] = (byte) (((j >= asc_len) ? 0x00 : asc_to_bcd(ascii[j++])) + (bcd[i] << 4));  
    }  
    return bcd;  
}  
public static byte asc_to_bcd(byte asc) {  
    byte bcd;  

    if ((asc >= '0') && (asc <= '9'))  
        bcd = (byte) (asc - '0');  
    else if ((asc >= 'A') && (asc <= 'F'))  
        bcd = (byte) (asc - 'A' + 10);  
    else if ((asc >= 'a') && (asc <= 'f'))  
        bcd = (byte) (asc - 'a' + 10);  
    else  
        bcd = (byte) (asc - 48);  
    return bcd;  
}  
/** 
 * BCD轉字符串 
 */  
public static String bcd2Str(byte[] bytes) {  
    char temp[] = new char[bytes.length * 2], val;  

    for (int i = 0; i < bytes.length; i++) {  
        val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f);  
        temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + '0');  

        val = (char) (bytes[i] & 0x0f);  
        temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + '0');  
    }  
    return new String(temp);  
}  
/** 
 * 拆分字符串 
 */  
public static String[] splitString(String string, int len) {  
    int x = string.length() / len;  
    int y = string.length() % len;  
    int z = 0;  
    if (y != 0) {  
        z = 1;  
    }  
    String[] strings = new String[x + z];  
    String str = "";  
    for (int i=0; i<x+z; i++) {  
        if (i==x+z-1 && y!=0) {  
            str = string.substring(i*len, i*len+y);  
        }else{  
            str = string.substring(i*len, i*len+len);  
        }  
        strings[i] = str;  
    }  
    return strings;  
}  
/** 
 *拆分數組  
 */  
public static byte[][] splitArray(byte[] data,int len){  
    int x = data.length / len;  
    int y = data.length % len;  
    int z = 0;  
    if(y!=0){  
        z = 1;  
    }  
    byte[][] arrays = new byte[x+z][];  
    byte[] arr;  
    for(int i=0; i<x+z; i++){  
        arr = new byte[len];  
        if(i==x+z-1 && y!=0){  
            System.arraycopy(data, i*len, arr, 0, y);  
        }else{  
            System.arraycopy(data, i*len, arr, 0, len);  
        }  
        arrays[i] = arr;  
    }  
    return arrays;  
}

}

##### 2.4 問題 
 > 公鑰(n,e) 只能 加密小於 n 的整數 m ,那麼若是要加密大於 n 的整數,怎麼辦?
 > 在 Java 中 進行 RSA 加密時,有 一個 錯誤爲 ArrayIndexOutOfBoundsException: too much data for RSA block
 > 該錯誤就是加密數據過長致使的。

這裏涉及到幾個知識點 -- **密鑰長度/密文長度/明文長度**
1. 明文長度
    明文長度(bytes) **<**= 密鑰長度(bytes)-11.
    若是 明文長度 大於 規定,則出現上述的問題,能夠按照下文中的解決方法處理
2. 密鑰長度
    下限是96bits(12bytes)
    上限未知。不過目前爲止,被破解的最長的密鑰長度 爲 768位,因此 1024 位基本安全, 2048 位絕對安全
3. 密文長度
    - 不分片加密 -- 密文長度 == 密鑰長度
    - 分片加密-- 密文長度 == 密鑰長度*分片數
        例如 明文 8 bytes , 密鑰 128 bits
        每片明文長度 = 128/8 - 11 = 5 bytes
        分片數 = 8/5 +1 = 2
        密文長度 = 128/8 * 2 = 32 bytes
    
**解決方法**
1. 分片加密 -- 是把長信息分割成若干段短消息,每段分別加密;
2. 先選擇一種"對稱性加密算法"(好比DES),用這種算法的密鑰加密信息,再用RSA公鑰加密DES密鑰。

未完待續。。。

## 結語
發現排版,好像是有問題的,閱讀效果不理想,能夠去個人[我的博客](https://3dot141.cn)中。

都看到這裏了,點個**關注**,點波**贊**再走,QAQ。
你的小手**輕點**,是我最大的動力哦。
> 一隻想當程序員的1米88**處女座**大可愛如此說道。
相關文章
相關標籤/搜索