一文搞懂對稱加密:加密演算法、工作模式、填充方式、程式碼實現

碼農StayUp發表於2021-11-16
微信搜尋:碼農StayUp
主頁地址:https://gozhuyinglong.github.io
原始碼分享:https://github.com/gozhuyinglong/blog-demos

上篇介紹了《單向雜湊加密》,它是一種訊息摘要演算法。該演算法在資訊保安領域,有很多重要的應用場景,比如:使用者密碼保護、數字簽名、檔案完整性校驗、雲盤妙傳等。

單向雜湊加密只能夠對訊息進行加密(嚴格來說是計算訊息的摘要),想要實現對密文解密,需要使用其它加密方式了。今天介紹一個在資訊保安領域中,比較重要的加密方式——對稱加密。

下面是本篇講述內容:
目錄

加密、解密和金鑰

加密(Encrypt)是從明文生成密文的步驟,解密(Decrypt)是從密文還原成明文的步驟,而這兩個步驟都需要用到金鑰(Key)。這和我們現實中,用鑰匙上鎖和開鎖是一樣的。

加密、解密和金鑰

什麼是對稱加密

對稱加密(Symmetric Cryptography)是密碼學中的一類加密演算法,這類演算法在加密和解密時,使用相同的金鑰。

對稱加密又稱為共享金鑰加密,其最大的缺點是,對稱加密的安全性依賴於金鑰,一旦洩露,就意味著任何人都能解密訊息。

對稱加密的優點是加密速度快,所以在很多場合被使用。

常見演算法

本節介紹對稱加密的一些常見演算法,包括DES、3DES和AES。

DES演算法

DES(Data Encryption Standard,中文:資料加密標準),是一種對稱加密演算法。該演算法在1976年被美國聯邦政府的國家標準局確定為聯邦資料處理標準(FIPS),並於1977年被髮布,隨後在國際上廣泛流傳開來。然而,隨著計算機的進步,DES 已經能夠被暴力破解,所以該演算法已經不安全了。

DES是一種分組密碼(Block Cipher,或者叫塊加密),即將明文按64位元進行分組加密,每組生成64位位元的密文。它的金鑰長度為56位元(從規格上來說,金鑰長度是64位元,但由於每隔7位元會設定一個用於錯誤檢查的位元,因此實際長度為56位元)。

DES演算法

3DES演算法

三重資料加密演算法(Triple Data Encryption Algorithm,縮寫為TDEA),簡稱3DES(Triple-DES),是DES的增強版,相當於對每組資料應用了三次DES演算法。

由於DES演算法的金鑰長度過短,容易被暴力破解,為了解決這一問題,設計出了該演算法。它使用簡單的方法,通過增加DES金鑰長度的方式來避免類似攻擊,而不是一種全新的密碼演算法。

該演算法在每次應用DES時,使用不同的金鑰,所以有三把獨立金鑰。這三把金鑰組成一起,是一個長度為168(56 + 56 + 56)位元的金鑰,所以3DES演算法的金鑰總長度為168位元。

3DES的加密過程,並不是進行三次DES加密(加密→加密→加密),而是以金鑰1、金鑰2、金鑰3的順序,進行加密解密加密的過程。

3DES加密

3DES的解密過程和加密正好相反,是以金鑰3、金鑰2、金鑰1的順序,進行解密加密解密的操作。

3DES解密

AES演算法

AES(Advanced Encryption Standard),即高階加密標準,是取代DES演算法的一種新的對稱加密演算法。AES演算法是從全世界的企業和密碼學家,提交的對稱密碼演算法中競選出來的,最終 Rijndael 加密演算法勝出,所以AES又稱為 Rijndael 加密演算法。

AES也是一種分組密碼,它的分組長度為128位元,金鑰長度可以為128位元、192位元或256位元。

AES演算法

分組密碼的模式

上面介紹的DES、3DES和AES都屬於分組密碼,它們只能加密固定長度的明文。如果需要加密更長的明文,就需要對分組密碼進行迭代,而分組密碼的迭代方法稱為分組密碼的模式(Model)。簡而一句話:分組密碼的模式,就是分組密碼的迭代方式。

分組密碼有很多種模式,這裡主要介紹以下幾種:ECB、CBC、CFB、OFB、CTR。

明文分組與密文分組

在下面對模式的介紹時,會用到兩個術語,這裡先介紹一下:

在分組密碼中,我們稱每組的明文為明文分組,每組生成的密文稱為密文分組

若將所有的明文分組合並起來就是完整的明文(先忽略填充),將所以的密文分組合並起來就是完整的密文。

ECB模式

ECB(Electronic CodeBook)模式,即電子密碼本模式。該模式是將明文分組,加密後直接成為密文分組,分組之間沒有關係。

ECB模式

ECB模式是所有模式中最簡單的一種,該模式的明文分組與密文分組是一一對應的關係,若明文分組相同,其密文分組也一定相同。因此,ECB模式也是最不安全的模式。

CBC模式

CBC(Cipher Block Chaining)模式,即密碼分組連結模式。該模式首先將明文分組與前一個密文分組進行XOR運算,然後再進行加密。只有第一個明文分組特殊,需要提前為其生成一個與分組長度相同的位元序列,進行XOR運算,這個位元序列稱為初始化向量(Initialization Vector),簡稱IV

CBC模式

CFB模式

CFB(Cipher FeedBack)模式,即密文反饋模式。該模式首先將前一個密文分組進行加密,再與當前明文分組進行XOR運算,來生成密文分組。同樣CFB模式也需要一個IV。

CFB模式

OFB模式

OFB(Output FeedBack)模式,即輸出反饋模式。該模式會產生一個金鑰流,即將密碼演算法的前一個輸出值,做為當前密碼演算法的輸入值。該輸入值再與明文分組進行XOR執行,計算得出密文分組。該模式需要一個IV,進行加密後做為第一個分組的輸入。

OFB模式

CTR模式

CTR(CounTeR)模式,即計數器模式。該模式也會產生一個金鑰流,它通過遞增一個計數器來產生連續的金鑰流。對該計數器進行加密,再與明文分組進行XOR運算,計算得出密文分組。

CTR模式

分組密碼的填充

在分組密碼中,當資料長度不符合分組長度時,需要按一定的方式,將尾部明文分組進行填充,這種將尾部分組資料填滿的方法稱為填充(Padding)。

No Padding

即不填充,要求明文的長度,必須是加密演算法分組長度的整數倍。

... | DD DD DD DD DD DD DD DD | DD DD DD DD DD DD DD DD |

ANSI X9.23

在填充位元組序列中,最後一個位元組填充為需要填充的位元組長度,其餘位元組填充0

... | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 04 |

ISO 10126

在填充位元組序列中,最後一個位元組填充為需要填充的位元組長度,其餘位元組填充隨機數

... | DD DD DD DD DD DD DD DD | DD DD DD DD 81 A6 23 04 |

PKCS#5和PKCS#7

在填充位元組序列中,每個位元組填充為需要填充的位元組長度

... | DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |

ISO/IEC 7816-4

在填充位元組序列中,第一個位元組填充固定值80,其餘位元組填充0。若只需填充一個位元組,則直接填充80

... | DD DD DD DD DD DD DD DD | DD DD DD DD 80 00 00 00 |

... | DD DD DD DD DD DD DD DD | DD DD DD DD DD DD DD 80 |

Zero Padding

在填充位元組序列中,每個位元組填充為0

... | DD DD DD DD DD DD DD DD | DD DD DD DD 00 00 00 00 |

Java程式碼實現

Java在底層已經封裝好了對稱加密的實現, 我們只需要使用即可。現在介紹幾個重要的類:

SecureRandom類

SecureRandom類是一個強安全的隨機數生成器(Random Number Generator,簡稱:RNG),加密相關的推薦使用此隨機數生成器。

我們可以通過構造方法生成一個例項,或者向構造方法傳遞一個種子來建立例項。

SecureRandom random = new SecureRandom();

KeyGenerator類

KeyGenerator類是對稱密碼的金鑰生成器,需要指定加密演算法,來生成相應的金鑰。

Java中支援的演算法:

  • AES (128)
  • DES (56)
  • DESede (168)
  • HmacSHA1
  • HmacSHA256

下面是一些標準演算法的介紹:

標準演算法

生成金鑰程式碼如下:

/**
 * 通過密碼和演算法獲取 Key 物件
 *
 * @param key       金鑰
 * @param algorithm 演算法,例如:AES (128)、DES (56)、DESede (168)、HmacSHA1、HmacSHA256
 * @return 金鑰 Key
 * @throws Exception
 */
private static Key getKey(byte[] key, String algorithm) throws Exception {
    // 通過演算法獲取 KeyGenerator 物件
    KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
    // 使用金鑰做為隨機數,初始化 KeyGenerator 物件
    keyGenerator.init(new SecureRandom(key));
    // 生成 Key
    return keyGenerator.generateKey();
}

Cipher類

Cipher類提供了加密和解密的功能。該類需要指定一個轉換(Transformation)來建立一個例項,轉換的命名方式:演算法名稱/工作模式/填充方式

下面是Java支援的轉換:

  • AES/CBC/NoPadding (128)
  • AES/CBC/PKCS5Padding (128)
  • AES/ECB/NoPadding (128)
  • AES/ECB/PKCS5Padding (128)
  • DES/CBC/NoPadding (56)
  • DES/CBC/PKCS5Padding (56)
  • DES/ECB/NoPadding (56)
  • DES/ECB/PKCS5Padding (56)
  • DESede/CBC/NoPadding (168)
  • DESede/CBC/PKCS5Padding (168)
  • DESede/ECB/NoPadding (168)
  • DESede/ECB/PKCS5Padding (168)
  • RSA/ECB/PKCS1Padding (1024, 2048)
  • RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
  • RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)

下面是一些標準的模式:

標準模式

下面是一些標準的填充:

標準填充

加密程式碼如下:

private static final String DES_ALGORITHM = "DES";
private static final String DES_TRANSFORMATION = "DES/ECB/PKCS5Padding";

/**
 * DES 加密
 *
 * @param data 原始資料
 * @param key  金鑰
 * @return 密文
 */
private static byte[] encryptDES(byte[] data, byte[] key) throws Exception {
    // 獲取 DES Key
    Key secretKey = getKey(key, DES_ALGORITHM);

    // 通過標準轉換獲取 Cipher 物件, 由該物件完成實際的加密操作
    Cipher cipher = Cipher.getInstance(DES_TRANSFORMATION);
    // 通過加密模式、金鑰,初始化 Cipher 物件
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    // 生成密文
    return cipher.doFinal(data);
}

解密程式碼如下:

private static final String DES_ALGORITHM = "DES";
private static final String DES_TRANSFORMATION = "DES/ECB/PKCS5Padding";

/**
 * DES 解密
 *
 * @param data 密文
 * @param key  金鑰
 * @return 原始資料
 */
private static byte[] decryptDES(byte[] data, byte[] key) throws Exception {
    // 獲取 DES Key
    Key secretKey = getKey(key, DES_ALGORITHM);
    // 通過標準轉換獲取 Cipher 物件, 由該物件完成實際的加密操作
    Cipher cipher = Cipher.getInstance(DES_TRANSFORMATION);
    // 通過解密模式、金鑰,初始化 Cipher 物件
    cipher.init(Cipher.DECRYPT_MODE, secretKey);
    // 生成原始資料
    return cipher.doFinal(data);
}

完整程式碼

完整程式碼請訪問我的Github,若對你有幫助,歡迎給個⭐,感謝~~???

https://github.com/gozhuyinglong/blog-demos/blob/main/java-source-analysis/src/main/java/io/github/gozhuyinglong/utils/SymmetricKeyUtil.java

推薦閱讀

掃碼關注,不迷路

相關文章