killBase系列 -- 密碼學

前言

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

密碼學

1、基礎

  1. 密碼學算法分類:
    • 消息編碼:Base64
    • 消息摘要:MD類, SHA類,MAC
    • 對稱密碼:DES,3DES,AES
    • 非對稱密碼:RSA,DH
    • 數字簽名:RSASignature,DSASignature
  2. 五元組
    1)明文:原始信息。
    2)加密算法:以密鑰爲參數,對明文進行多種置換和轉換的規則和步驟,變換結果爲密文。
    3)解密算法:加密算法的逆變換,以密文爲輸入、密鑰爲參數,變換結果爲明文。:
    4)密鑰:加密與解密算法的參數,直接影響對明文進行變換的結果。
    5)密文:對明文進行變換的結果。
  3. 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
  4. 經常使用開源工具
    • 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)將被擴展至N512+448,N爲一個非負整數,N能夠是零。
    填充的方法以下,在信息的後面填充一個1和無數個0,直到知足上面的條件時才中止用0對信息的填充。
    而後,在這個結果後面附加一個以64位二進制表示的填充前信息長度。
    通過這兩步的處理,信息的位長=N
    512+448+64=(N+1)*512,即長度剛好是512的整數倍
    MD5以512位分組來處理輸入的信息,且每一分組又被劃分爲16個32位子分組,通過了一系列的處理後,算法的輸出由四個32位分組組成,將這四個32位分組級聯後將生成一個128位散列值。java

代碼實現程序員

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)轉換成報文摘要(不一樣的明文對應不一樣的報文摘要),報文摘要加密後與明文一塊兒傳送給接受方,接受方將接受的明文產生新的報文摘要與發送方的發來報文摘要解密比較,比較結果一致表示明文未被改動,若是不一致表示明文已被篡改。編程

代碼實現windows

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)一塊兒通過特殊加密算法處理後,使其變成複雜的加密密文發送出去。
    收信方收到密文後,若想解讀原文,則須要使用加密用過的密鑰相同算法逆算法對密文進行解密,才能使其恢復成可讀明文。
    在對稱加密算法中,使用的密鑰只有一個,發收信雙方都使用這個密鑰對數據進行加密和解密,這就要求解密方事先必須知道加密密鑰。

  2. 優缺點

    • 優勢:算法公開、計算量小、加密速度快、加密效率高。
    • 缺點:
      (1)交易雙方都使用一樣鑰匙,安全性得不到保證。
      (2)每對用戶每次使用對稱加密算法時,都須要使用其餘人不知道的唯一鑰匙,這會使得發收信雙方所擁有的鑰匙數量呈幾何級數增加,
      密鑰管理成爲用戶的負擔。對稱加密算法在分佈式網絡系統上使用較爲困難,主要是由於密鑰管理困難,使用成本較高。
  3. 經常使用的對稱加密算法。
    DES(Data Encryption Standard):數據加密標準,速度較快,適用於加密大量數據的場合。
    3DES(Triple DES):是基於DES,對一塊數據用三個不一樣的密鑰進行三次加密,強度更高。
    AES(Advanced Encryption Standard):高級加密標準,是下一代的加密算法標準,速度快,安全級別最高
  4. 對稱密碼經常使用的數學運算
    • 移位和循環移位
        移位就是將一段數碼按照規定的位數總體性地左移或右移。循環右移就是當右移時,把數碼的最後的位移到數碼的最前頭,循環左移正相反。例如,對十進制數碼12345678循環右移1位(十進制位)的結果爲81234567,而循環左移1位的結果則爲23456781。
    • 置換
        就是將數碼中的某一位的值根據置換表的規定,用另外一位代替。它不像移位操做那樣整齊有序,看上去雜亂無章。這正是加密所需,被常常應用。
    • 擴展
        就是將一段數碼擴展成比原來位數更長的數碼。擴展方法有多種,例如,能夠用置換的方法,以擴展置換表來規定擴展後的數碼每一位的替代值。
    • 壓縮
        就是將一段數碼壓縮成比原來位數更短的數碼。壓縮方法有多種,例如,也能夠用置換的方法,以表來規定壓縮後的數碼每一位的替代值。
    • 異或
        這是一種二進制布爾代數運算。異或的數學符號爲⊕ ,它的運算法則以下:
      1⊕ 1 = 0
      0⊕ 0 = 0
      1⊕ 0 = 1
      0⊕ 1 = 1
        也能夠簡單地理解爲,參與異或運算的兩數位如相等,則結果爲0,不等則爲1。
    • 迭代
        迭代就是屢次重複相同的運算,這在密碼算法中常用,以使得造成的密文更加難以破解。
  5. 分組加密
    參考 分組加密的四種模式
    ECB模式 -- 電子密碼本模式
    CBC模式 -- 密碼分組連接模式
    CFB模式 -- 密文反饋模式
    OFB模式 -- 輸出反饋模式
    CTR模式 -- 計數器模式
  6. 經常使用的填充方式
    在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(加解密算法,加解密模式,填充模式)
  2. 初始化
    Cipher.init(加解密模式 -- Cypher.ENCRIPT/DECRYPT,密鑰)
  3. 完成加解密
    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
    p = 61, q = 53
  2. 計算 p 和 q 的乘積 n
    n = 61*53 = 3233
  3. 計算 n 的歐拉函數 φ(n)
    φ(n) = (p-1)(q-1) = 60 * 52 = 3120
  4. 隨機選擇一個整數 e , 條件是 1 < e < φ(n) , 且 e 與 φ(n) 互質
    e = 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計時攻擊

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密鑰。

附錄

1. DES 詳細加密過程

1. **對輸入的密鑰進行變換**。
    用戶的64bit密鑰,其中第8, 16, 24, 32, 40, 48, 56, 64位是校驗位, 使得每一個密鑰都有奇數個1。因此密鑰事實上是56位。對這56位密鑰進行以下表的換位。

57, 49, 41, 33, 25, 17,   9,  1, 58, 50, 42, 34, 26, 18, 10,  2, 59, 51, 43, 35, 27, 19, 11,  3, 60, 52, 44, 36, 
63, 55, 47, 39, 31, 23, 15,  7, 62, 54, 46, 38, 30, 22, 14,  6, 61, 53, 45, 37, 29, 21, 13,  5, 28, 20, 12,   4,

表的意思是第57位移到第1位,第49位移到第2位,...... 以此類推。變換後獲得56bit數據,將它分紅兩部分,C[0][28], D[0][28]。

2. **計算16個子密鑰**,計算方法C[i][28] D[i][28]爲對前一個C[i-1][28], D[i-1][28]作循環左移操做。16次的左移位數以下表:

 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16   (第i次)
 1,  1,  2,  2,  2,  2,  2,  2,  1,  2,  2,  2,  2,  2,  2,  1    (左移位數)

3. **串聯**計算出來的C[i][28] D[i][28] 獲得56位,而後對它進行以下變換獲得48位子密鑰K[i][48]

14, 17, 11, 24,  1,  5,  3, 28, 15,  6, 21, 10, 23, 19, 12,  4, 26,  8, 16,  7, 27, 20, 13,  2,
41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32,

表的意思是第14位移到第1位,第17位移到第2位,以此類推。在此過程當中,發現第9,18,22,25, 35,38,43,54位丟棄。

4. 對64bit的明文輸入進行換位變換。換位表以下:

58, 50, 12, 34, 26, 18, 10,  2, 60, 52, 44, 36, 28, 20, 12,  4,
62, 54, 46, 38, 30, 22, 14,  6, 64, 56, 48, 40, 32, 24, 16,  8,
57, 49, 41, 33, 25, 17,   9,  1, 59, 51, 43, 35, 27, 19, 11,  3,
61, 53, 45, 37, 29, 21, 13,  5, 63, 55, 47, 39, 31, 23, 15,  7

表的意思就是第一次變換時,第58位移到第1位,第50位移到第2位,...... 依此類推。獲得64位數據,將這數據先後分紅兩塊L[0][32], R[0][32]。

5. 加密過程,對R[i][32]進行擴展變換成48位數,方法以下, 記爲E(R[i][32])

32,  1,  2,  3,  4,  5,   
 4,  5,  6,  7,  8,  9,
 8,  9, 10, 11, 12, 13, 
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25, 
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32,  1,

6. 將E(R[i][32])與K[i][48]做異或運算,獲得48位數,將48位數順序分紅8份,6位一份,B[8][6]。

7. 使用S[i]替換B[i][6]。過程以下: 取出B[i][6]的第1位和第6位連成一個2位數m, m就是S[i]中對應的行數(0-3),取出B[i][6]的第2到第5位連成一個4位數n(0-15),n就是S[i]中對應的列數,用S[i][m][n]代替B[i][6]。S是4行16列的對應表,裏面是4位的數,一共有8個S,定義以下:

S[1]:
   14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7, 
  0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8, 
  4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0, 
  15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13,
S[2]:
    15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10, 
    3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5, 
    0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15, 
    13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9,
S[3]:
    10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8, 
    13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1, 
    13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7, 
    1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12,
S[4]:
    7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15, 
  13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9, 
  10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4, 
  3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14, 
S[5]: 
  2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9, 
  14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6, 
  4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14, 
  11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3, 
S[6]: 
  12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11, 
  10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8, 
  9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6, 
  4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13, 
S[7]: 
  4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1, 
  13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6, 
  1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2, 
  6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12, 
S[8]: 
  13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7, 
  1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2, 
  7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8, 
  2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11,

8. 將從B[i][6]通過S獲得的8個4位數連起來獲得32位數。對這個數進行以下變換:

   16,7,20,21,29,12,28,17, 1,15,23,26, 5,18,31,10, 
  2,8,24,14,32,27, 3, 9,19,13,30, 6,22,11, 4,25,

  獲得的結果與L[i][32]做異或運算,把結果賦給R[i][32]。

9. 把R[i-1][32]的值賦給L[i],從5開始循環。直到K[16][48]結束。

10. 將最後的L,R合併成64位,而後進行以下轉化獲得最後的結果。這是對第4步的一個逆變化。
 40, 8, 48, 16, 56, 24, 64, 32, 
 39, 7, 47, 15, 55, 23, 63, 31, 
 38, 6, 46, 14, 54, 22, 62, 30, 
 37, 5, 45, 13, 53, 21, 61, 29, 
 36, 4, 44, 12, 52, 20, 60, 28,
 35, 3, 43, 11, 51, 19, 59, 27, 
 34, 2, 42, 10, 50, 18, 58, 26, 
 33, 1, 41,   9, 49, 17, 57, 25複製代碼

2. https 的加密算法

因爲以前看過 https 是 由 secure socket layer 實現的。 也是經過 公鑰私鑰 保證其安全性,因此在學習這篇文章的時候,就想 https 是由哪一種 加密算法 作爲其 底層實現的呢。 所以,就有了下面這部分。

關於 https 與 http 的區別 請看個人這篇博客,再也不贅述。網絡基礎知識

原理:

  • 瀏覽器把自身支持的一系列Cipher Suite(密鑰算法套件,後文簡稱Cipher)[C1,C2,C3, …]發給服務器;
  • 服務器接收到瀏覽器的全部Cipher後,與本身支持的套件做對比,若是找到雙方都支持的Cipher,則告知瀏覽器;
  • 瀏覽器與服務器使用匹配的Cipher進行後續通訊。若是服務器沒有找到匹配的算法,瀏覽器(以 Chrome 56爲例)將給出錯誤信息:

下面講一下如何分析。

  1. 準備: 經過能夠抓取網絡包的工具,這裏經過 Wireshark 分析。關於wireshark 的介紹請點擊這裏.查看瀏覽器發送給服務器的 Ciper服務器的 Ciper
  2. 流程:
    • 瀏覽器首先發起握手協議, 一個'Client Hello'消息,以下圖,按照Protocol協議順序排序,而後,找到Client Hello,選中,依次查找 'Secure Sockets Layer' -> TLSv1.2 Record Layer -> Handshake protocal ->Ciper Suites.
    • 能夠看到, Cipher有不少。總共16,第一個是Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)。
    • 若是按照順序繼續尋找第一個 Info 爲'Sever Hello' 的報文,能夠找到相應的Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b) 。
      .
  3. Cipher介紹:
    • 密鑰交換算法,用於決定客戶端與服務器之間在握手的過程當中如何認證,用到的算法包括RSA,Diffie-Hellman,ECDH,PSK等
    • 加密算法,用於加密消息流,該名稱後一般會帶有兩個數字,分別表示密鑰的長度和初始向量的長度,好比DES 56/56, RC2 56/128, RC4 128/128, AES 128/128, AES 256/256
    • 報文認證信息碼(MAC)算法,用於建立報文摘要,確保消息的完整性(沒有被篡改),算法包括MD5,SHA等。
    • PRF(僞隨機數函數),用於生成「master secret」。
    • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b):
      • 基於TLS協議
      • 使用 ECDHE,ECDSA做爲密鑰交換算法
      • 加密算法 AES(密鑰與初始向量的長度爲128)
      • MAC 算法 SHA
  4. 總結:
    Client端密鑰算法套件[C1,C2,C3],Server端密鑰算法套件[C4,C2,C1,C3],
    則,IIS(Internet Infomation Services),C2將被優先返回

3. wireshark 的使用問題

問題:第一次使用 wireshark 的時候,不顯示接口。緣由是。。。
剛開始使用 在windows 上須要 winpacp 而且開啓 npf 服務。
注: 若是 沒有安裝 winpacp ,想直接 經過 net start npf 開啓服務,將會提示。 發生系統錯誤2

  1. winpacp 安裝 。。。
    這裏是下載網站
    直接安裝便可。
  2. 開啓 npf 服務
    打開 cmd ,輸入 net start npf ,提示:服務已經啓動。
  3. 進入界面,選擇相應的網卡。

    這裏,能夠經過 網絡鏈接 看出來。

    因此,個人是無線網絡鏈接。
  4. 最終界面

    WireShark 主要分爲這幾個界面
  5. Display Filter(顯示過濾器), 用於過濾
  6. Packet List Pane(封包列表), 顯示捕獲到的封包, 有源地址和目標地址,端口號。 顏色不一樣,表明
  7. Packet Details Pane(封包詳細信息), 顯示封包中的字段
  8. Dissector Pane(16進制數據)
  9. Miscellanous(地址欄,雜項)

結語

都看到這裏了,點個關注,點波再走,QAQ。
你的小手輕點,是我最大的動力哦。

一隻想當程序員的1米88處女座大可愛如此說

參考

  1. DES 加密算法解析
  2. 分組加密的四種模式
  3. 阮一峯--RSA算法原理
  4. java中RSA加解密的實現
  5. 關於RSA算法密鑰長度/密文長度/明文長度
  6. https背後的加密算法
相關文章
相關標籤/搜索