真正安全的加密唯有一次性密碼本,但沒人想處理與訊息等長的巨大金鑰。對稱式金鑰密碼學(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)64561979
Blowfish6432–4481993
Triple DES (TDES/3DES)6456, 112, 1681998
Serpent128128, 192, 2561998
Twofish128128, 192, 2561998
Camellia128128, 192, 2562000
Advanced Encryption Standard (AES)128128, 192, 2562001

區塊與金鑰大小可協助你根據金鑰的指定方式、或加密資料如何切分成區塊,來判斷協定使用的是哪種密碼。

區塊加密模式(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 BookECB區塊
Cipher Block ChainingCBC區塊
Output FeedbackOFB串流
Cipher FeedbackCFB串流
CounterCTR串流
Galois Counter ModeGCM串流(含資料完整性)

其中 GCM 這類特殊模式能同時提供資料完整性與機密性。

區塊加密填充(Block Cipher Padding)#

區塊加密法只對固定大小的區塊運作。若要加密的資料不足一個區塊(例如區塊 16 位元組但只有 1 位元組資料),就需要填充機制(padding scheme)來處理區塊剩餘的空間。

最單純的作法是用固定已知值(例如重複的 0 位元組)填充,但解密時無從分辨填充位元組與有意義的資料。PKCS#7 填充方案解決了這個問題:所有填充位元組都設為「填充位元組數量」這個值。例如有 3 個位元組的填充,每個位元組都設為值 3。

圖表 7-7:PKCS#7 填充的範例

若最後一個區塊剛好是正確長度、不需填充,仍必須額外傳送一個「只含填充」的整塊,以通知解密端最後這一塊應被丟棄,否則解密端會把合法資料誤判為填充。

PKCS#7 解密時如何驗證填充

解密後的區塊可輕易驗證填充位元組數量:

  1. 讀取區塊最後一個位元組,得知預期的填充位元組數(例如值為 3)。
  2. 讀取其餘預期的填充位元組,驗證每個位元組是否也都等於該值。
  3. 若填充不正確——所有預期填充位元組並非同值,或填充值超出範圍(必須大於 0 且小於等於區塊大小)——就發生錯誤,可能導致解密流程失敗。

失敗的方式本身就是一項安全考量,這正是下一節填充預言攻擊的切入點。

填充預言攻擊(Padding Oracle Attack)#

CBC 模式PKCS#7 填充結合時,會出現一個嚴重的安全漏洞:填充預言攻擊(padding oracle attack)。它讓攻擊者即使不知道金鑰,也能解密資料,某些情況下甚至能加密自己的資料(如工作階段權杖 session token)。

「預言(oracle)」一詞源自攻擊者能向服務提出一個問題並得到真/假的答案——具體來說,是「我送出的加密區塊其填充是否有效?」。只要服務會透露 CBC 加密區塊解密成功與否,攻擊者就能反推出該區塊底層的未加密值。

以下這段程式碼從網路讀取 IV 與加密資料,用內部應用金鑰做 DES CBC 解密,並依成敗回傳 ERRORSUCCESS——這正好把「解密成敗」這個資訊洩露給攻擊者:

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 解密

推導最低位元組:

  1. 攻擊者送出各種 IV 最後位元組值,逐一嘗試。只要解密後的值不等於 0x01(或碰巧構成其他有效填充),解密就回傳錯誤;一旦填充有效便回傳成功。
  2. 假設送出 IV 最後位元組為 0x2A 時回傳成功,代表「解密位元組 XOR 0x2A = 0x01」。
  3. 於是攻擊者算出解密值:0x2A XOR 0x01 = 0x2B
  4. 再把此值與原始 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 或 641989
RC4最高 20481993
Counter mode (CTR)取決於區塊加密法N/A
Output Feedback mode (OFB)取決於區塊加密法N/A
Cipher Feedback mode (CFB)取決於區塊加密法N/A