分組密碼以及分組密碼的模式

摘要

爲了保證信息的安全性,在傳輸過程當中不被攻擊(攻擊分爲主動攻擊和被動攻擊),一般都會採用把消息加密的方式,使用的過程當中,咱們會常用DES,3DES以及AES這些對稱加密的加密算法,而且咱們也常常聽到分組密碼和流密碼這些名詞。而且也見到什麼ECB,CBC這些東西,本文以DES這種對稱加密算法的分組密碼作一個淺顯的介紹。java

密碼算法分爲分組密碼和流密碼兩種。算法

分組密碼:是每次只能處理特定長度的一塊數據的一類密碼算法,這裏的「一塊」就稱爲分組。此外一個分組的比特數就稱爲分組長度安全

流密碼:流密碼是對數據流進行連續處理的一類密碼算法,流密碼中通常以1比特,8比特或者32比特等爲單位進行解密和加密。bash

     分組密碼處理完一個分組就結束了,所以不須要經過內部狀態來記錄加密的進度;相對的流密碼是對一串數據流進行連續處理,所以須要保持內部狀態。網絡

是否是看完以上感受還比較暈,再看一個更進一步的定義,分組加密(Block cipher),又稱爲分塊加密或者塊密碼,它將明文分紅多個等長的模塊,使用肯定的算法和對稱祕鑰對每一個分組進行加解密。app

分組密碼算法

接下來以美國政府覈定的標準加密算法DES爲例來講明什麼分組加密算法。ide

DES的基本機構是有Hors Feistel設計的,所以也叫Feistel網絡,這個結構不只僅用在了DES上,其餘不少的密碼算法也有應用。函數

在Feistel網絡中,加密的各個步驟稱做輪,整個加密過程就是進行若干次輪的循環。下圖展示的是Fesitel網絡一輪的計算流程。加密

一輪明文輸入會被分爲左右兩部分進行處理,左右兩部分分別是32個bytespa

子祕鑰指的是本輪加密所使用的祕鑰。在Fesistel網絡中,每一輪都須要使用一個不一樣的子祕鑰。因爲子祕鑰只在一輪中使用,因此只是一個局部的祕鑰,所以才叫子祕鑰

輪函數的做用是根據右側和子祕鑰生成左側進行加密的比特序列,是整個加密的核心。講輪函數的輸出與左側進行XOR(異或)運算,運算後的結果就是加密後的左側。也就說用XOR與左側的輸入進行合併,而右側則會直接成爲輸出的右側。

其步驟以下:

(1)將輸入的數據等分爲左右兩部分

(2)將輸入的右側直接輸入到右側

(3)講輸入的右側發送給輪函數

(4)將輪函數根據右側數據和子祕鑰,計算出一串隨機比特序列

(5)將上一步獲得的比特序列與左側數據進行XOR運算,並將結果做爲加密後的左側。

上述過程當中能夠看出來,右側的數據根本就沒有加密,因此須要用不一樣的子祕鑰對這一輪重複處理若干次,而且每次都進行左右對調。

而解密的過程就是一個相反的過程。

以上的一個單位(64byte)就叫作一個分組,而對這組數據進行加密的過程就是分組加密。

分組模式

那什麼是模式呢?

從上能夠看出分組密碼算法只能加密固定長度的分組,可是須要加密的明文長度一般會超過度組密碼的分組長度,這時候就須要對分組密碼算法進行迭代,才能把全部的明文進行加密,而迭代的方法就稱爲分組密碼的模式。

分組密碼有不少模式,比較常見的有下面五種

  1. ECB模式:Electronic CodeBook mode(電子密碼本模式)
  2. CBC模式:Cipher Block Chainiing mode(密碼分組連接模式)
  3. CFB模式:Cipher FeedBack mode(密文反饋模式)
  4. OFB模式:Output FeedBack mode(輸出反饋模式)
  5. CTR模式:CounTeR mode (計數器模式)

ECB模式(電子密碼本模式)

ECB模式比較簡單,這種模式就是把一段明文,分割成一個分組長度的整數倍(例如DES的一個分組長度是64byte,上文中有提到),若是不是分組長度的整數倍,則會用一些特殊的字符進行填充,而後分別按照上述加密算法,對每個分組進行加密,最後對全部分組加密出來的數據進行合併,成爲密文。下圖就是對明文爲(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)採用ECB模式加密的示意圖

如下使用代碼來演示一下使用AES加密算法,使用ECB模式之後,對字符串加密後而且用16進制顯示出來的結果

public class AESTest {
    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
        byte[] data = str.getBytes();
        String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
        String KEY_ALGORITHM = "AES";
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
        keyGenerator.init(128);
        SecretKey secretKey = keyGenerator.generateKey();
        byte[] encoded = secretKey.getEncoded();
        Key key = new SecretKeySpec(encoded,KEY_ALGORITHM);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE,key);
        byte[] bytes = cipher.doFinal(data);
        System.out.println("加密後"+parseByte2HexStr(bytes));
    }

    /**將二進制轉換成16進制
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }
}

輸出結果爲:

C57EC72731C0983EF3FFCB88D8EAB922C57EC72731C0983EF3FFCB88D8EAB922C57EC72731C0983EF3FFCB88D8EAB922C57EC72731C0983EF3FFCB88D8EAB922F628B5120016D660138EBA0EA993A5E7

這樣看還不太明顯,若是進行換行顯示的話能夠看到以下規律

C57EC72731C0983EF3FFCB88D8EAB922
C57EC72731C0983EF3FFCB88D8EAB922
C57EC72731C0983EF3FFCB88D8EAB922
C57EC72731C0983EF3FFCB88D8EAB922
F628B5120016D660138EBA0EA993A5E7

其中 C57EC72731C0983EF3FFCB88D8EAB922 這個重複顯示,這個就是ECB模式加密的特色,這樣只須要觀察一下密文的組合,就能根據一些線索來破譯密碼。

因此ECB模式是存在必定風險的,所以此種加密模式並不推薦採用。

CBC模式(密碼分組連接模式)

CBC模式的全稱是Cipher Block Chaining模式,是由於密文分組像鏈條同樣互相鏈接在一塊兒。

在CBC模式中,首先將明文分組與前一個密文分組進行XOR運算,而後再進行加密

從上圖能夠看出,在和ECB模式相比較,在加密以前CBC模式先和上一組加密後的密文進行了一次XOR,而後才進行加密,因爲第一次沒有前一個分組,因此準備了一個長度爲一個分組長度的比特序列用來代替「前一個分組」,這個比特序列叫作初始化向量(initialization vector),CBC模式能夠克服ECB模式因爲字符相同,解密出來的密文分組相同的缺點。

若是把ECB中章節中程序的模式更改成CBC,則加密結果爲:

F354627AE9BA742ECB61C2D9114A3CA2938DFA3A0E7AC12D913E9A48E4AE8CBF9BC93BE23122A583DA00EE6440DD30447920421E410E40211216D126C2641FA21F70E1039D7ADC440928E3CF94E1F982

不會出現重複。

其中初始化向量是必須的,若是僅僅把CIPHER_ALGORITHM 改成AES/CBC/PKCS5Padding,再次運行程序,解密時將會報錯:

加密後:FDA2BF43B9A8A4C8E0C4D2315B2B18C497B999D931BD8EE61AD60E70ABAEEE3DF9CF92C1672AE26B45A83DF7B05BF437830CAC49FDC267A53D928E72188EB2F7D26B06A2DE2673D97E5922A80A12D343
Exception in thread "main" java.security.InvalidKeyException: Parameters missing
	at com.sun.crypto.provider.CipherCore.init(CipherCore.java:388)
	at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:186)
	at javax.crypto.Cipher.implInit(Cipher.java:787)
	at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
	at javax.crypto.Cipher.init(Cipher.java:1213)
	at javax.crypto.Cipher.init(Cipher.java:1153)
	at com.wtf.crypto.AESTest.main(AESTest.java:30)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

 

須要對上述的程序進行修正,初始化一個向量,修改後的程序爲:

public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
    String str = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    byte[] data = str.getBytes();

    String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    String KEY_ALGORITHM = "AES";
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
    keyGenerator.init(128);
    SecretKey secretKey = keyGenerator.generateKey();
    byte[] encoded = secretKey.getEncoded();
    Key key = new SecretKeySpec(encoded,KEY_ALGORITHM);
    Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE,key,new IvParameterSpec(new byte[16])); //設置初始向量
    byte[] bytes = cipher.doFinal(data);
    System.out.println("加密後:"+parseByte2HexStr(bytes));
    Cipher decode = Cipher.getInstance(CIPHER_ALGORITHM);
    decode.init(Cipher.DECRYPT_MODE,key,new IvParameterSpec(new byte[16])); //設置初始向量
    byte[] bytes1 = decode.doFinal(bytes);
    System.out.println("解密後:"+new String(bytes1));

}

 

這樣加解密之後會產生以下輸出:

加密後:976CF9D4DDE0B0544C6008CFE9CB57EFA888E0B8AC2187F1039D837CEAD88BC42A50E033B70CC2FA18BDC507F71C1CD4E0D7AA23F1A9281EB208C815FC3785E4397C00809E9C474F06131A65345B3854
解密後:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

 

CFB模式(祕鑰反饋模式)

在CBC模式以及ECB模式中,都是要把明文進行加密,從上圖能夠看出,在CFB模式中,並無通過對明文進行加密,而是對上一個密文分組直接進行了XOR,初始化向量和CBC模式的初始化向量含義相同。在CFB模式中,密碼算法的輸出至關於一次性密碼本中的隨機比特序列。由密碼算法所生成的比特序列成爲祕鑰流,明文數據能夠被逐個比特加密,所以能夠講CFB模式看作是一種使用分組密碼來實現流密碼的方式。

 

OFB(輸出反饋模式)

OFB模式和CFB模式的區別僅僅在於密碼算法的輸入。

CFB模式中,密碼算法的輸入是前一個密文分組,也就是將密文分組反饋到密碼的算法中,所以叫作「密文反饋模式」。因此對於OFB模式,密碼算法的輸入則是密碼算法的前一個輸出,也就是將輸出反饋給密碼算法,故叫作「輸出反饋模式」。

 

因爲CFB模式中是對密文分組進行反饋的,所以必須從第一個明文分組開始按順序進行加密,也就是說沒法跳過明文分組1而先對明文分組2進行加密。

而在OFB模式中,XOR所須要的比特序列能夠事先經過密碼算法生成,和明文的分組沒什麼關係。只要提早準備好所須要的祕鑰流,則在實際從明文生成密文的過程當中,就徹底不在須要動祕鑰算法了,只須要把準備好的祕鑰流和明文分組XOR便可。

CTR模式(計數器模式)

 

CTR模式是經過將逐次累加的計數器進行累加加密後生成祕鑰流的流密碼

CTR模式中,每一個分組對應一個逐次累加的計數器,並經過對計數器進行加密來生成祕鑰流。也就是說,最終的密文分組是經過將計數器加密獲得的比特序列,而後與明文XOR而獲得的。

各模式比較

分組密碼模式對比

模式 名稱 優勢 缺點 備註
ECB 電子密碼本模式
  • 簡單、快速
  • 加解密時支持並行計算
  • 明文中的重複排列會反饋到密文中
  • 能夠經過刪除、替換密文分組進行攻擊
  • 不能抵禦重放攻擊
不推薦使用
CBC 密碼分組連接模式
  • 明文中重複排列不會反饋到密文
  • 解密支持並行計算
  • 可以解密任意密文分組
  • 對包含某些錯誤比特的密文進行解密時,第一個分組的所有比特及後一個分組比特會出錯
  • 加密不支持並行計算
推薦使用
CFB 祕鑰反饋模式
  • 不須要進行填充
  • 解密支持並行計算
  • 可以解密任意密文分組
  • 加密不支持並行計算
  • 對包含某些錯誤比特的密文進行解密時,第一個分組的所有比特以及後一個分組比特會出錯
  • 不能抵禦重放攻擊
如今已經再也不使用,推薦使用CTR模式作替代
OFB 輸出反饋模式
  • 不須要填充
  • 能夠實現準備好祕鑰流直接和明文進行XOR加密
  • 加解密使用相同結構
  • 一個分組的錯誤不影響到另一個分組的加解密
  • 不支持並行計算
  • 主動攻擊者反轉密文分組中的某些比特時,明文分鐘中相應的比特也會被反轉
推薦用CTR模式替代
CTR 計數器模式
  • 不須要填充
  • 可事先爲加解密作準備
  • 加解密時使用相同的結構
  • 錯誤分組不影響到其餘分組
  • 支持加解密並行計算    
  • 主動攻擊者反轉密文分組中的某些比特時,明文分鐘中相應的比特也會被反轉
推薦使用
相關文章
相關標籤/搜索