在前一章我們了解到,區塊加密演算法(如 AES、DES)本質上是一個接收固定輸入長度資料的「加密引擎」(例如 AES 只能處理 16 位元組 / 128 位元的區塊)。
然而,我們在真實世界中要加密的資料(如圖片、文章、PDF 檔)長度往往遠大於 16 位元組。如何將任意長度的資料切塊、組合並傳入區塊加密引擎,就是操作模式(Mode of Operation)要解決的核心課題。
今天我們就從最經典、最基礎的兩大模式——ECB 與 CBC,以及處理長度不足的 Padding(填充機制) 開始介紹。
ECB 是最直覺、最簡單的操作模式。你可以把它想像成一本「密碼對照本」:資料切成一塊塊,每塊獨立加密,最後直接拼起來。
C_i = E_K(P_i)
P_i = D_K(C_i)
明文 P1 ---> [ AES 加密 (Key) ] ---> 密文 C1
明文 P2 ---> [ AES 加密 (Key) ] ---> 密文 C2
明文 P3 ---> [ AES 加密 (Key) ] ---> 密文 C3
操作模式在加密過程中完全不對資料進行任何串接加工,單純將加密後的 16 位元組密文塊 C_i 直接串接輸出。
因為每個區塊是完全獨立運算的,只要輸入相同的明文區塊,就會產出完全相同的密文區塊。
這會導致密文保留了明文的「統計結構與規律」。最著名的例子就是將企鵝點陣圖以 ECB 模式加密後,密文依然能清晰看出企鵝的輪廓(著名的 ECB Penguin 漏洞)。因此,在現代密碼學實務中,絕對禁止使用 ECB 模式處理一般資料。
為了徹底解決 ECB 的圖案規律問題,CBC 模式引進了「鏈接(Chaining)」機制:將前一個區塊的密文,拿來與當前區塊的明文進行 XOR 運算後再送入加密引擎。
因為第 1 個明文區塊 P_1 前面沒有「前一個區塊的密文」,所以必須由外部傳入一組隨機生成的位元組陣列,稱為 初始向量(IV)。
C_1 = E_K(P_1 ⊕ IV)
C_i = E_K(P_i ⊕ C_{i-1})
P_1 = D_K(C_1) ⊕ IV
P_i = D_K(C_i) ⊕ C_{i-1}
[IV] ---------+
v
明文 P1 ---> (XOR) ---> [ AES 加密 (Key) ] ---> 密文 C1
|
+-----------------------------------+
v
明文 P2 ---> (XOR) ---> [ AES 加密 (Key) ] ---> 密文 C2
區塊加密演算法(如 AES)要求每一個輸入區塊的長度必須完全符合規格(AES 為 16 Bytes)。然而,現實中的資料長度幾乎不可能剛好是 16 的倍數。當最後一個區塊的長度不足時,就必須透過 Padding 來補齊。
最常見的 Padding 方式是 PKCS#7。其規則為:缺了 N 個位元組,就填入 N 個數值為 N 的 Byte。
[Data...] + 0x03 0x03 0x03
[Data...] + 0x01
0x10):[Data (16 bytes)] + [0x10 0x10 ... 0x10 (16 bytes)]
0x01 究竟是 Padding 還是原本的明文資料」。| 比較項目 | PKCS#5 Padding | PKCS#7 Padding |
|---|---|---|
| 規範來源 | RFC 2898 (PKCS#5 v2.1) | RFC 2315 / RFC 5652 (PKCS#7) |
| 適用區塊大小 | 固定 8 位元組 (64 bits) | 1 至 255 位元組 |
| 常見搭配演算法 | DES、3DES | AES (16 位元組) 及其他區塊演算法 |
| 填充邏輯 | 缺 N 個位元組,就補 N 個數值為 N 的 Byte | 缺N 個位元組,就補 N 個數值為 N 的 Byte |
在 Java JCA 中,我們傳給 Cipher.getInstance() 的字串,格式由 「演算法/操作模式/填充機制」 三者組合而成:
// 經典對稱加密寫法 (CBC 模式 + PKCS#7 填充)
// (註:Java 歷史因素,PKCS5Padding 在 AES 16-byte 下運作等同於 PKCS#7)
Cipher cbcCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
PKCS5Padding。PKCS5Padding 時,Java 底層會自動將其視為針對 16 位元組區塊的 PKCS#7 Padding 運作。