真正安全的加密唯有一次性密碼本,但沒人想處理與訊息等長的巨大金鑰。對稱式金鑰密碼學(symmetric key cryptography)改用數學構造打造安全的密碼,金鑰長度遠短於訊息、且不隨加密量增長,因此更容易散布。加解密使用同一把金鑰。
若演算法本身沒有明顯弱點,安全性的關鍵就落在金鑰長度上。金鑰太短,攻擊者就能以暴力搜尋(brute-force)試出正確金鑰。
對稱式密碼主要分為兩類:區塊加密法(block cipher)與串流加密法(stream cipher)。在協定中選錯密碼類型,可能嚴重衝擊網路通訊的安全。
區塊加密法(Block Ciphers)#
許多知名的對稱式演算法(如 AES 與 DES)每次套用時,都對固定位元數(稱為一個區塊 block)進行加解密。若訊息長於一個區塊,就必須切分成多個區塊,逐一套用演算法,且每次都使用同一把金鑰。

圖表 7-3:區塊加密法的加密
DES#
DES 大概是現代應用中仍在使用、最古老的區塊加密法,由 IBM 開發(原名 Lucifer),1979 年發布為 FIPS 標準。它以 Feistel 網路實作加密流程:對輸入反覆套用某函式若干回合(round),每回合使用由原始金鑰經金鑰排程演算法(key-scheduling)推導出的子金鑰。
DES 使用 64 位元區塊與 64 位元金鑰,但其中 8 位元用於錯誤檢查,有效金鑰僅 56 位元,對現代應用太短。1998 年電子前哨基金會(EFF)的 DES 破解機在約 56 小時內就找出未知金鑰;當年那套客製硬體約需 25 萬美元,如今雲端破解工具能在一天內以低廉許多的成本破解。
Triple DES#
密碼學家沒有完全拋棄 DES,而是把演算法套用三次。Triple DES(TDES/3DES)使用三把獨立的 DES 金鑰,有效金鑰長度為 168 位元(但可證明實際安全性低於此數字)。流程為:以第一把金鑰加密明文 → 以第二把金鑰解密 → 以第三把金鑰再加密,得到最終密文;解密則反向操作。

圖表 7-4:Triple DES 的加密流程
AES#
AES 是更現代的演算法,基於 Rijndael,固定使用 128 位元區塊,可選 128、192、256 位元三種金鑰長度(分別稱 AES128、AES192、AES256)。它不用 Feistel 網路,而採用替換-置換網路(substitution-permutation network),由兩個主要元件串接構成一回合:
- 替換盒(S-Box):類似簡單替換密碼的映射表,接受輸入、查表、產生輸出。
- 置換盒(P-Box):負責位元位置的重排。
S-Box 使用龐大而獨特的查表,這為特定演算法提供了很大的「指紋」,可在應用程式執行檔中被找到——正是第 6 章逆向工程辨識未知密碼演算法時利用的特徵。
常見區塊加密法對照表
| 密碼名稱 | 區塊大小(位元) | 金鑰大小(位元) | 問世年份 |
|---|---|---|---|
| Data Encryption Standard (DES) | 64 | 56 | 1979 |
| Blowfish | 64 | 32–448 | 1993 |
| Triple DES (TDES/3DES) | 64 | 56, 112, 168 | 1998 |
| Serpent | 128 | 128, 192, 256 | 1998 |
| Twofish | 128 | 128, 192, 256 | 1998 |
| Camellia | 128 | 128, 192, 256 | 2000 |
| Advanced Encryption Standard (AES) | 128 | 128, 192, 256 | 2001 |
區塊與金鑰大小可協助你根據金鑰的指定方式、或加密資料如何切分成區塊,來判斷協定使用的是哪種密碼。
區塊加密模式(Block Cipher Modes)#
區塊加密法單獨使用有一些弱點,因此實務上常搭配另一種稱為操作模式(mode of operation)的演算法。模式提供額外的安全性質(例如讓輸出更不可預測),有時甚至改變密碼的運作方式(例如把區塊加密法轉成串流加密法)。
電子密碼本(ECB)#
電子密碼本(Electronic Code Book, ECB)是區塊加密法最簡單也是預設的模式:把明文切成固定大小區塊,各自獨立套用加密演算法。以 AES 為例,每個區塊為 16 位元組。
因為每個明文區塊獨立加密,相同的明文區塊永遠加密成相同的密文區塊。ECB 無法隱藏明文中的大尺度結構(經典例子是加密點陣圖後仍能看出原圖輪廓)。此外,攻擊者可在解密前重排密文區塊,藉此破壞或操縱解密後的資料。

圖表 7-5:以 ECB 加密點陣圖影像
密碼區塊鏈(CBC)#
密碼區塊鏈(Cipher Block Chaining, CBC)比 ECB 複雜並避開其陷阱:加密某個明文區塊前,先把它與前一個密文區塊做 XOR,再套用加密演算法。如此一來,輸出密文同時取決於當前明文與先前的密文區塊。

圖表 7-6:CBC 操作模式
由於第一個明文區塊沒有前一個密文區塊可供 XOR,需搭配一個手動選擇或隨機產生的區塊,稱為初始化向量(initialization vector, IV)。
若 IV 為隨機產生,就必須與加密資料一起傳送,否則接收方無法解出第一個區塊。使用固定 IV 在同一金鑰用於所有通訊時會有問題:同一則訊息每次都會加密成相同密文。
CBC 常見操作模式一覽
| 模式名稱 | 縮寫 | 模式類型 |
|---|---|---|
| Electronic Code Book | ECB | 區塊 |
| Cipher Block Chaining | CBC | 區塊 |
| Output Feedback | OFB | 串流 |
| Cipher Feedback | CFB | 串流 |
| Counter | CTR | 串流 |
| Galois Counter Mode | GCM | 串流(含資料完整性) |
其中 GCM 這類特殊模式能同時提供資料完整性與機密性。
區塊加密填充(Block Cipher Padding)#
區塊加密法只對固定大小的區塊運作。若要加密的資料不足一個區塊(例如區塊 16 位元組但只有 1 位元組資料),就需要填充機制(padding scheme)來處理區塊剩餘的空間。
最單純的作法是用固定已知值(例如重複的 0 位元組)填充,但解密時無從分辨填充位元組與有意義的資料。PKCS#7 填充方案解決了這個問題:所有填充位元組都設為「填充位元組數量」這個值。例如有 3 個位元組的填充,每個位元組都設為值 3。

圖表 7-7:PKCS#7 填充的範例
若最後一個區塊剛好是正確長度、不需填充,仍必須額外傳送一個「只含填充」的整塊,以通知解密端最後這一塊應被丟棄,否則解密端會把合法資料誤判為填充。
PKCS#7 解密時如何驗證填充
解密後的區塊可輕易驗證填充位元組數量:
- 讀取區塊最後一個位元組,得知預期的填充位元組數(例如值為 3)。
- 讀取其餘預期的填充位元組,驗證每個位元組是否也都等於該值。
- 若填充不正確——所有預期填充位元組並非同值,或填充值超出範圍(必須大於 0 且小於等於區塊大小)——就發生錯誤,可能導致解密流程失敗。
失敗的方式本身就是一項安全考量,這正是下一節填充預言攻擊的切入點。
填充預言攻擊(Padding Oracle Attack)#
當 CBC 模式與 PKCS#7 填充結合時,會出現一個嚴重的安全漏洞:填充預言攻擊(padding oracle attack)。它讓攻擊者即使不知道金鑰,也能解密資料,某些情況下甚至能加密自己的資料(如工作階段權杖 session token)。
「預言(oracle)」一詞源自攻擊者能向服務提出一個問題並得到真/假的答案——具體來說,是「我送出的加密區塊其填充是否有效?」。只要服務會透露 CBC 加密區塊解密成功與否,攻擊者就能反推出該區塊底層的未加密值。
以下這段程式碼從網路讀取 IV 與加密資料,用內部應用金鑰做 DES CBC 解密,並依成敗回傳 ERROR 或 SUCCESS——這正好把「解密成敗」這個資訊洩露給攻擊者:
def decrypt_session_token(byte key[])
{
byte iv[] = read_bytes(8);
byte token[] = read_to_end();
bool error = des_cbc_decrypt(key, iv, token);
if(error) {
write_string("ERROR");
} else {
write_string("SUCCESS");
}
}這種模式在 Web 應用框架中很常見:用戶端實質上無狀態,每次請求都必須送出權杖以驗證身分。只要程式使用 PKCS#7 填充,並在填充不符時回傳錯誤,攻擊者就能據此執行填充預言攻擊,解出自己送給脆弱服務的那塊資料。
填充預言攻擊逐步拆解
回到 CBC 如何解密單一區塊。假設明文是字串 Hello,後面接 3 個位元組的 PKCS#7 填充。攻擊者透過查詢 Web 服務,能直接控制密文與 IV;由於解密最後一步每個明文位元組都與對應 IV 位元組 XOR,攻擊者改動 IV 位元組就能直接控制輸出的明文。

圖表 7-8:帶 IV 的 CBC 解密
推導最低位元組:
- 攻擊者送出各種 IV 最後位元組值,逐一嘗試。只要解密後的值不等於
0x01(或碰巧構成其他有效填充),解密就回傳錯誤;一旦填充有效便回傳成功。 - 假設送出 IV 最後位元組為
0x2A時回傳成功,代表「解密位元組 XOR0x2A=0x01」。 - 於是攻擊者算出解密值:
0x2A XOR 0x01 = 0x2B。 - 再把此值與原始 IV 位元組(
0x28)XOR:0x2B XOR 0x28 = 0x03,正是預期的原始填充值。
往前推導其餘位元組:
- 下一步用 IV 讓明文最低兩個位元組都變成
0x02。因為已知最低位元組的值,可用適當的 IV 值把它設成0x02。 - 接著對倒數第二個位元組做暴力搜尋,直到解密成功(代表該位元組解密後等於
0x02)。 - 如此反覆,直到算出所有位元組——攻擊者即可解出任意區塊。
串流加密法(Stream Ciphers)#
不同於處理整塊訊息的區塊加密法,串流加密法(stream cipher)在個別位元的層級運作。最常見的作法是從初始金鑰產生一段偽亂數位元流,稱為金鑰流(key stream),再以算術運算(通常是 XOR)套用到訊息上產生密文。

圖表 7-9:串流加密法的運算
只要算術運算可逆,解密就只需產生與加密時相同的金鑰流,對密文執行反向運算即可(XOR 的反向運算仍是 XOR)。金鑰流可用完全客製的演算法產生(如 RC4),也可用區塊加密法搭配適當的操作模式產生。
常見串流加密法對照表
| 密碼名稱 | 金鑰大小(位元) | 問世年份 |
|---|---|---|
| A5/1 與 A5/2(用於 GSM 語音加密) | 54 或 64 | 1989 |
| RC4 | 最高 2048 | 1993 |
| Counter mode (CTR) | 取決於區塊加密法 | N/A |
| Output Feedback mode (OFB) | 取決於區塊加密法 | N/A |
| Cipher Feedback mode (CFB) | 取決於區塊加密法 | N/A |