AES 加密模式完全指南

深入比較 ECB、CBC、CFB、OFB、CTR、GCM 六種 AES 加密模式的原理、安全性與選用建議


目錄


為什麼需要「加密模式」?

AES 的本質限制

AES(Advanced Encryption Standard) 本質上是一個 區塊密碼(Block Cipher)——它一次只能處理「一塊固定大小(128 bits = 16 bytes)」的資料。

明文(16 bytes)   ──→   AES + 金鑰   ──→   密文(16 bytes)

但現實中,要加密的資料幾乎不可能剛好是 16 bytes:

  • 一段 30 bytes 的訊息 → 怎麼加密?
  • 一個 1GB 的檔案 → 切成多少塊?怎麼串起來?
  • 一段持續傳輸的資料流 → 沒辦法等湊滿一塊才加密?

加密模式(Mode of Operation) 就是解答以上問題的「使用方法」——告訴你「如何把多個區塊串起來加密」、「不滿一塊怎麼處理」、「怎麼避免規律被看出來」。

加密模式要解決的四大問題

問題 說明
如何處理多塊? 一塊一塊獨立加密?還是要互相關聯?
如何處理不滿一塊? 補齊(Padding)?還是改成串流方式?
如何避免規律洩漏? 同樣的明文不能加密成同樣的密文
如何兼顧效能與安全? 能不能平行加密?能不能隨機存取?

不同的加密模式就是對這四個問題給出不同的答案,因此各有優缺點與適用情境。


核心名詞先理解

理解這些名詞後,後面六種模式就會清晰許多。

區塊密碼 vs 串流密碼

類型 處理單位 代表演算法
區塊密碼(Block Cipher) 固定長度的「一塊」(AES:128 bits) AES、DES、3DES
串流密碼(Stream Cipher) 一個 bit 或一個 byte ChaCha20、RC4

重點:CFB、OFB、CTR、GCM 模式其實都是「把 AES 變成串流密碼」的不同做法——它們先用 AES 產生一段「金鑰流(Keystream)」,再把金鑰流跟明文 XOR 得到密文。這是理解後面四種模式的關鍵。

AES 的金鑰長度與規格

AES 有三種變體,差別在「金鑰長度」與「加密輪數」:

變體 金鑰長度 加密輪數 區塊大小 安全強度
AES-128 16 bytes (128 bits) 10 輪 16 bytes 標準
AES-192 24 bytes (192 bits) 12 輪 16 bytes 較強
AES-256 32 bytes (256 bits) 14 輪 16 bytes 最強(業界主流)

幾個容易混淆的重點

  1. 區塊大小與金鑰長度是兩回事:不論哪個變體,區塊永遠是 16 bytes——所以 IV、Padding、Counter 的設計都跟金鑰長度無關
  2. 「AES-256」指的是金鑰 256 bits,不是區塊 256 bits
  3. IV/Nonce 長度由模式決定,不由金鑰長度決定:
    • CBC、CFB、OFB 的 IV:16 bytes(=區塊長度)
    • CTR 的 Nonce:通常 12 bytes(後 4 bytes 留給 Counter)
    • GCM 的 Nonce:規範推薦 12 bytes(96 bits)

實務上絕大多數新系統選 AES-256:除了安全餘裕較大,也能在量子電腦的 Grover 攻擊下(將安全性砍半)仍維持 128 bits 等效強度。

IV(Initialization Vector,初始向量)

  • 一段「隨機」的資料(通常與區塊同長度,AES 是 128 bits = 16 bytes)
  • 與金鑰一起餵給加密的第一個區塊
  • 目的:讓「同一把金鑰、同一份明文」每次加密都產生「不同的密文」
  • 不需要保密,但對某些模式(特別是 CBC)必須不可預測
  • 通常會把 IV 連同密文一起傳送給接收方

Nonce(Number used Once,一次性數值)

  • 概念類似 IV,但更強調「只能用一次
  • 不一定要隨機,但絕對不能在同一把金鑰下重複
  • CTR 與 GCM 模式使用的是 Nonce 而非 IV

Padding(填充)

  • AES 一塊是 16 bytes。若明文不是 16 的倍數,最後一塊必須補齊
  • 常見演算法 PKCS#7:補上的每個 byte 值,等於需要補的 byte 數量
    • 例:30 bytes 的明文 → 第二塊只有 14 bytes → 補上 2 個 byte,每個值都是 0x02
    • 例:剛好 32 bytes → 仍會多補一整塊 16 bytes,每個值都是 0x10(避免歧義)
  • 只有區塊式模式(ECB、CBC)需要,串流式模式(CFB、OFB、CTR、GCM)不需要

XOR(互斥或)

加密模式的核心運算,具備一個關鍵性質:

A XOR B XOR B = A

因此「明文 XOR 金鑰流 = 密文」、「密文 XOR 金鑰流 = 明文」——加密與解密是同一個運算,只要雙方知道相同的金鑰流。

金鑰流(Keystream)

由 AES 加密器產生的一段「偽隨機資料」,用來與明文 XOR。串流式模式(CFB、OFB、CTR、GCM)的核心就在於「如何產生金鑰流」。

平行化(Parallelization)

能否同時用多個 CPU 核心加密不同區塊。會大幅影響大檔案的加解密效能。

認證加密(AEAD, Authenticated Encryption with Associated Data)

不只保證「機密性」(外人看不懂),還保證「完整性 + 真實性」(密文沒被竄改、確實來自持有金鑰的人)。GCM 就是 AEAD 的代表。


六種模式實務規格速覽

在深入每個模式的細節前,先用一張表掌握「每個模式需要準備什麼、有什麼關鍵差異」。後面各章會展開細節。

輸入規格與關鍵差異對照

模式 Key IV / Nonce Padding 額外輸出 一句話差異
ECB 32 bytes ✅ PKCS#7 唯一不需 IV;KDF 也救不了模式洩漏
CBC 32 bytes IV 16 bytes(隨機 + 不可預測 ✅ PKCS#7 經典模式;KDF 與 IV 教學的代表
CFB 32 bytes IV 16 bytes(不可預測 規格同 CBC,差別只在跳過 padding
OFB 32 bytes IV 16 bytes(唯一即可 IV 重用比 CBC 更嚴重——金鑰流全洩
CTR 32 bytes Nonce 12 bytes + Counter 4 bytes 平行 + 隨機存取;單金鑰上限 ~64 GB
GCM 32 bytes Nonce 12 bytes (96 bits) Tag 16 bytes + AAD AEAD:機密 + 完整性 + 真實性

表格基於 AES-256。若改用 AES-128 / AES-192,只需把 Key 換成 16 / 24 bytes,其他規格(IV、Nonce、Counter、Tag、Padding)都不變。

從表格能看出的三個規律

  1. Key 長度只跟 AES 變體有關(AES-256 永遠是 32 bytes),與模式無關
  2. Padding 只有 ECB 與 CBC 需要——其他四種都是「串流式運作」,不需要對齊區塊
  3. IV/Nonce 的安全要求逐漸放寬,但唯一性要求逐漸加強
    • CBC:必須隨機 + 不可預測(防選擇明文攻擊)
    • CFB:必須不可預測
    • OFB / CTR / GCM:只要唯一即可(不必隨機),但重用就災難

從密碼推導金鑰的策略對照

如果輸入是「使用者密碼字串」而非隨機金鑰,所有模式都需要 KDF。下表整理常見做法(只有 KDF 的選擇通用,IV/Nonce 都應該獨立隨機產生):

情境 KDF 推薦 IV / Nonce 產生方式
使用者密碼加密檔案 Argon2id(記憶體硬,抗 GPU/ASIC) 每次加密用 RNG 隨機產生
後端 API 加密 token 不需要 KDF(直接用 32-byte 隨機金鑰) 每次加密用 RNG 隨機產生
老系統相容 PBKDF2-SHA256,iterations ≥ 600,000 每次加密用 RNG 隨機產生

核心原則:金鑰可以從密碼推導,IV / Nonce 永遠不能從密碼推導——否則同密碼會產生同 IV,等於沒用 IV。


ECB - 電子密碼本模式

ECB(Electronic Codebook) 是最簡單也最危險的模式。

運作原理

把明文切成 16 bytes 一塊,每塊「獨立」用同一把金鑰加密。

明文塊1            明文塊2            明文塊3
   │                  │                  │
   ▼                  ▼                  ▼
 [AES]              [AES]              [AES]
 金鑰 K            金鑰 K             金鑰 K
   │                  │                  │
   ▼                  ▼                  ▼
密文塊1            密文塊2             密文塊3

致命缺陷:模式洩漏

相同的明文塊永遠產生相同的密文塊。這意味著資料中的規律(重複的字串、相同顏色的圖片區塊)會直接保留在密文中。

最著名的範例是「Tux 企鵝圖」——將企鵝圖片用 ECB 加密後,雖然顏色被打亂,企鵝的輪廓仍清晰可見。

特性整覽

項目 評估
平行加密 ✅ 可以
平行解密 ✅ 可以
需要 IV ❌ 不需要
需要 Padding ✅ 需要
安全性 🚨 絕對不要使用

實務範例:金鑰產生

ECB 是六種模式中唯一不需要 IV 的,所以實務上只需要產生金鑰:

輸入 長度 說明
金鑰(Key) 32 bytes AES-256 金鑰
IV 不需要
passphrase = "..."
key = Argon2id(passphrase, salt, ...)   ← 仍應使用 KDF + 隨機 Salt

⚠️ 再次強調:即使金鑰產生方式完全正確,ECB 的模式洩漏問題依然存在。Tux 企鵝圖用 KDF 產生的金鑰加密後,企鵝輪廓還是會出現。ECB 的問題是模式本身,不是金鑰

適用情境

幾乎沒有。唯一可考慮的是「明文長度恰好等於一塊、且每次加密的明文都不同」的特殊情境(例如加密單一隨機金鑰),但通常還是建議改用其他模式。


CBC - 密碼分組鏈接模式

CBC(Cipher Block Chaining) 是最經典的模式,曾長年是業界主流。

運作原理

每塊明文先與「前一塊密文」XOR,再加密。第一塊與 IV XOR。

   IV              密文塊1            密文塊2
    │                 │                  │
明文塊1 ─⊕     明文塊2 ─⊕      明文塊3 ─⊕
    │                 │                  │
    ▼                 ▼                  ▼
  [AES]             [AES]              [AES]
    │                 │                  │
    ▼                 ▼                  ▼
 密文塊1            密文塊2             密文塊3

解密流程

密文塊1 → [AES⁻¹] → ⊕ IV         → 明文塊1
密文塊2 → [AES⁻¹] → ⊕ 密文塊1    → 明文塊2
密文塊3 → [AES⁻¹] → ⊕ 密文塊2    → 明文塊3

特性整覽

項目 評估
平行加密 ❌ 不行(必須等前一塊加密完)
平行解密 ✅ 可以
需要 IV ✅ 必須隨機且不可預測
需要 Padding ✅ 需要
主要風險 Padding Oracle Attack、IV 重用、IV 可預測

主要弱點

  1. 加密無法平行:對大檔案效能不佳
  2. Padding Oracle Attack:若伺服器在 padding 錯誤時回傳特殊錯誤訊息,攻擊者可在不知金鑰的情況下還原明文
  3. 位元翻轉攻擊:攻擊者翻轉密文中的某 bit,會讓對應明文塊毀損,但下一塊明文中對應的 bit 也會被翻轉——若沒有完整性驗證,可被惡意利用

實務範例:金鑰與 IV 的產生

實作 AES-256-CBC 加密時,必須先準備好兩個輸入:

輸入 長度 說明
金鑰(Key) 32 bytes 對應 AES-256 的金鑰長度
初始向量(IV) 16 bytes 對應 AES 的區塊長度

問題來了:開發者手上通常只有「密碼字串(passphrase)」,要怎麼把它變成「剛好 32 bytes 的 Key」與「剛好 16 bytes 的 IV」?

常見(但不夠安全)的做法

許多教學文、舊系統、甚至某些開源專案會這樣寫:

passphrase = "my-secret-phrase-2026"

key = SHA-256(passphrase)   →  32 bytes  ← 剛好對應 AES-256 金鑰長度
iv  = MD5(passphrase)       →  16 bytes  ← 剛好對應 AES 區塊長度

為什麼是 SHA-256 + MD5?——純粹是「長度剛好對得上」:

Hash 演算法 輸出長度 對應用途
SHA-256 32 bytes AES-256 Key
SHA-1 20 bytes (長度不對)
MD5 16 bytes AES IV / AES-128 Key

這個慣例輸出長度確實對得上,但安全上有三個嚴重問題

為什麼這種做法不安全?

問題 1:沒有 Salt → 同密碼產生同金鑰

使用者 A 用密碼 "password123"  →  key 永遠是 ef92b778...
使用者 B 用密碼 "password123"  →  key 永遠是 ef92b778...(一模一樣)

攻擊者只要預先把常見密碼的 SHA-256 算好(Rainbow Table),看到金鑰就能反查密碼。

問題 2:沒有 Stretching → 暴力破解便宜

SHA-256 一秒可在 GPU 上算數十億次。8 位數的英數密碼空間 ≈ 2.18 × 10¹⁴,現代 GPU 數小時內就能跑完全部組合。

問題 3:IV 從密碼推導 → IV 等於失效

IV 的核心目的是「讓相同密碼/明文每次加密都產生不同密文」。如果 IV 也從密碼推導,同密碼永遠對到同 IV——這個保護就完全消失了。

「我愛你」+ 密碼 "abc"  →  永遠加密成相同密文 ABC123...

攻擊者只要看到密文一致,就知道明文也一致——回到了 ECB 模式的問題。

正確做法:使用 KDF + 隨機 IV

KDF(Key Derivation Function,金鑰衍生函數) 就是為了解決上述問題而設計:

passphrase = "my-secret-phrase-2026"
salt       = 隨機 16 bytes(每次加密重新產生,與密文一起儲存)
iv         = 隨機 16 bytes(每次加密重新產生,與密文一起儲存)

key = PBKDF2(passphrase, salt, iterations=600_000, length=32)
   或 scrypt(passphrase, salt, N=2^17, r=8, p=1, length=32)
   或 Argon2id(passphrase, salt, t=3, m=64MB, p=4, length=32)

KDF 解決了什麼

問題 KDF 的對策
Rainbow Table Salt 讓相同密碼產生不同金鑰
暴力破解便宜 Stretching(高 iteration)讓每次嘗試都很慢
GPU/ASIC 加速 記憶體硬(scrypt/Argon2)讓平行化變昂貴
IV 失效 IV 獨立隨機產生,與密碼脫鉤

三種情境的選擇

情境 推薦做法 理由
使用者密碼加密檔案 Argon2id 記憶體硬,最能抗 GPU/ASIC
後端 API token 加密 直接用隨機 32-byte 金鑰(存環境變數) 沒有「使用者輸入密碼」的問題,KDF 多餘
老系統相容 PBKDF2-SHA256,iterations ≥ 600,000 OWASP 2023 標準,相容性最佳

完整加密流程(AES-256-CBC + KDF)

1. 接收使用者密碼 passphrase
2. 產生隨機 salt (16 bytes)
3. 產生隨機 iv   (16 bytes)
4. key = Argon2id(passphrase, salt, ...)
5. 用 PKCS#7 對明文做 padding
6. ciphertext = AES-256-CBC(key, iv, padded_plaintext)
7. 輸出:salt ‖ iv ‖ ciphertext   ← salt 與 iv 都是公開儲存

解密時,從輸出中取出 salt 與 iv,重新跑 KDF 算出 key,再用 AES-256-CBC 解密即可。

更安全的補強:CBC 沒有完整性保護,建議再用 HMAC-SHA256 對 salt ‖ iv ‖ ciphertext 計算 MAC(Encrypt-then-MAC),把 MAC 一起儲存。解密前先驗證 MAC,避免位元翻轉攻擊與 Padding Oracle Attack。

現代地位

TLS 1.3 已完全淘汰 CBC 模式。新系統不應再使用 CBC,請改用 GCM。


CFB - 密文回饋模式

CFB(Cipher Feedback) 把 AES 變成串流密碼,逐塊用「前一塊密文」產生金鑰流。

運作原理

   IV               密文塊1            密文塊2
    │                  │                  │
    ▼                  ▼                  ▼
  [AES]              [AES]              [AES]
    │                  │                  │
明文塊1 ─⊕     明文塊2 ─⊕      明文塊3 ─⊕
    │                  │                  │
    ▼                  ▼                  ▼
 密文塊1             密文塊2             密文塊3

注意:AES 只用「加密」運算,連解密時也是用 AES 加密器(而非解密器)。

特性整覽

項目 評估
平行加密 ❌ 不行
平行解密 ✅ 可以
需要 IV ✅ 需要
需要 Padding ❌ 不需要(串流式)
錯誤傳播 一塊密文錯誤 → 影響本塊與下一塊

實務範例:金鑰與 IV 的產生

CFB 的金鑰與 IV 規格與 CBC 完全相同,差別只在不需要 padding:

輸入 長度 說明
金鑰(Key) 32 bytes AES-256 金鑰長度
初始向量(IV) 16 bytes AES 區塊長度

與 CBC 的對照

面向 CBC CFB
Key 長度 32 bytes 32 bytes
IV 長度 16 bytes 16 bytes
IV 必須不可預測 ✅(同樣容易被選擇明文攻擊)
需要 Padding ✅ PKCS#7 ❌ 串流式
KDF 與 Salt 需求
完整性保護 ❌ 需自行加 HMAC ❌ 需自行加 HMAC

結論:金鑰產生方式請參考 CBC 實務範例,唯一差別是「跳過 padding 步驟」。CFB 的 IV 同樣必須由密碼學等級 RNG 產生,且不能從 passphrase 推導

適用情境

歷史上用於即時通訊、需要逐 byte 處理且不能等湊滿一塊的場景。現代已少見,多被 CTR/GCM 取代。


OFB - 輸出回饋模式

OFB(Output Feedback)——你原本問題裡寫的「OCF」應該是這個。它與 CFB 類似,但回饋的不是「密文」,而是「AES 加密器的輸出本身」。

運作原理

 IV ──→ [AES] ──→ O₁ ──→ [AES] ──→ O₂ ──→ [AES] ──→ O₃
                   │                │                │
明文塊1 ──────────⊕   明文塊2 ────⊕   明文塊3 ────⊕
                   │                │                │
                   ▼                ▼                ▼
                密文塊1           密文塊2           密文塊3

與 CFB 的關鍵差異

OFB 的金鑰流(O₁、O₂、O₃...)完全與明文無關,只取決於 IV 與金鑰。這帶來兩個重要特性:

  1. 可預先計算金鑰流:在拿到明文前就能準備好所有金鑰流,加密只剩 XOR 運算
  2. 無錯誤擴散:密文中某個 bit 損毀,只會影響解密後對應位置的 bit,不影響其他區塊

特性整覽

項目 評估
平行加密 ❌ 不行(金鑰流是鏈式產生)
平行解密 ❌ 不行
需要 IV ✅ 需要(且不可重複)
需要 Padding ❌ 不需要
錯誤擴散

實務範例:金鑰、IV 與重用的災難

OFB 的金鑰與 IV 規格與 CBC、CFB 相同,但 IV 重用的後果嚴重得多

輸入 長度 說明
金鑰(Key) 32 bytes AES-256 金鑰
IV 16 bytes 必須唯一,但不必不可預測

IV 重用為什麼在 OFB 是災難?

OFB 的金鑰流(Keystream)只取決於 Key 和 IV,與明文完全無關。如果同一把 Key 配同一個 IV 加密兩段不同明文 P1、P2:

密文1 = P1 XOR Keystream
密文2 = P2 XOR Keystream

攻擊者把兩段密文 XOR:

密文1 XOR 密文2 = P1 XOR P2

對英文這種有規律的明文,P1 XOR P2 幾乎等於明文洩漏。

與 CBC 的關鍵對比

模式 IV 重用後果 嚴重程度
CBC 僅相同的前綴明文塊會洩漏,後續塊靠鏈式串接還能保密 ⚠️ 部分洩漏
OFB 整段金鑰流洩漏,密文完全失守 🚨 全面洩漏
CTR/GCM 同 OFB(金鑰流洩漏 + GMAC 認證金鑰可能可被推導) 🚨 全面洩漏

安全實作要點

key = Argon2id(passphrase, salt, ...)        ← 從密碼推導時用 KDF
iv  = SecureRandom.nextBytes(16)             ← 每次加密重新產生

關鍵:IV 必須由密碼學等級 RNG 產生,且永不重複。OFB 不像 CBC 那樣要求 IV「不可預測」(因為攻擊者無法選擇明文影響金鑰流),但「唯一」的要求比 CBC 更嚴格。

適用情境

需要「無錯誤擴散」的場景,例如雜訊較多的通訊頻道(衛星、無線電)。但現代環境多用 CTR 取代(CTR 同樣無錯誤擴散,且支援平行)。


CTR - 計數器模式

CTR(Counter) 徹底擺脫鏈式依賴,是現代主流之一。

運作原理

不依賴前一塊,而是用「Nonce + 遞增計數器」當作 AES 的輸入產生金鑰流。

[Nonce ‖ Counter=0]   [Nonce ‖ Counter=1]   [Nonce ‖ Counter=2]
        │                     │                     │
        ▼                     ▼                     ▼
      [AES]                 [AES]                 [AES]
        │                     │                     │
明文塊1 ⊕            明文塊2 ⊕            明文塊3 ⊕
        │                     │                     │
        ▼                     ▼                     ▼
     密文塊1                密文塊2                密文塊3

表示串接:通常 Nonce 佔前 96 bits,Counter 佔後 32 bits,組成 128 bits 的 AES 輸入。

革命性優點

  1. 加解密皆可平行:每塊獨立計算,多核 CPU 可滿載
  2. 隨機存取:要解密第 1000 塊?直接用 Counter=999 算出金鑰流即可,不必從頭跑——這是「磁碟加密」的關鍵
  3. 無需 Padding:串流式,明文長度可任意
  4. 可預先計算:金鑰流產生與明文無關

Nonce 重用是死刑

CTR 模式最大的安全陷阱是 Nonce 重用。若同一把金鑰下用了同一個 Nonce 加密兩段不同明文 P1、P2:

密文1 = P1 XOR 金鑰流
密文2 = P2 XOR 金鑰流

攻擊者把兩段密文 XOR 起來:

密文1 XOR 密文2 = P1 XOR P2

對英文等有規律的明文,P1 XOR P2 幾乎等於明文洩漏。WEP(早期 Wi-Fi 加密)就是因為 Nonce 太短(24 bits)導致重用而被破。

特性整覽

項目 評估
平行加密 ✅ 可以
平行解密 ✅ 可以
隨機存取 ✅ 可以
需要 Nonce 絕對不可重複
需要 Padding ❌ 不需要
完整性保證 ❌ 無(位元翻轉攻擊可成立)

實務範例:金鑰、Nonce 與 Counter 的組成

CTR 把 IV 拆成兩段——「Nonce」+「Counter」——組合成 128 bits 的 AES 輸入:

輸入 長度 說明
金鑰(Key) 32 bytes AES-256 金鑰
Nonce 12 bytes (96 bits) 每次加密唯一
Counter 4 bytes (32 bits) 從 0 開始遞增,每塊 +1

兩者串接後共 128 bits = 16 bytes,剛好是 AES 的區塊大小:

┌────────────────────────┬─────────────────────┐
│   Nonce (12 bytes)     │  Counter (4 bytes)  │
│   每次加密唯一           │  從 0 遞增到 N       │
└────────────────────────┴─────────────────────┘
              16 bytes = AES 區塊大小

Nonce 的兩種產生策略

策略 做法 優點 缺點
隨機 Nonce SecureRandom.nextBytes(12) 無狀態、多端可平行 2³² 訊息後碰撞機率不可忽略
計數器 Nonce 跨訊息持久遞增(如:DB 序號) 永不碰撞 需要狀態管理(重啟、多 server 困難)

情境選擇

場景 推薦策略
單機應用 計數器(簡單可靠)
無狀態 API、多 server 隨機 Nonce(避免分散式狀態同步)
高頻加密(>2³² 訊息/金鑰) 改用 AES-GCM-SIV(容忍偶然 Nonce 重複)

單次加密的容量上限

Counter 4 bytes (32 bits)  →  最多 2³² 個區塊
2³² × 16 bytes              →  ≈ 64 GB

單次加密超過 64 GB 必須換 Nonce 或拆段——否則 Counter 會 wrap-around 回到 0,金鑰流重複出現,等同 Nonce 重用攻擊。

完整加密流程

1. 接收使用者密碼 passphrase(如有)
2. 產生隨機 salt   (16 bytes)            ← KDF 用
3. 產生隨機 nonce  (12 bytes)            ← 每次加密唯一
4. key = Argon2id(passphrase, salt, ...)
5. ciphertext = AES-256-CTR(key, nonce, counter=0, plaintext)
6. 輸出:salt ‖ nonce ‖ ciphertext

⚠️ 不要單獨使用 CTR:CTR 沒有完整性保護。若必須用 CTR,請額外用 HMAC-SHA256 對 salt ‖ nonce ‖ ciphertext 計算 MAC(Encrypt-then-MAC)。或乾脆改用 GCM——它本質就是「CTR + 內建認證」。

為什麼還不算終極答案?

CTR 只保證「機密性」,不保證「完整性」。攻擊者可以翻轉密文中任意 bit,解密後的明文就被改了,接收方無法察覺。要解決這個問題,就需要下一個模式:GCM。


GCM - 認證加密模式

GCM(Galois/Counter Mode) 是 CTR 的延伸:CTR 模式 + GMAC 認證標籤,提供業界目前最完整的對稱加密方案。

運作原理

GCM 的加密由兩部分組成:

┌─────────────────────────────────────────────┐
│ 1. CTR 加密:產生密文                         │
│    Nonce + Counter → AES → ⊕ 明文 → 密文     │
└─────────────────────────────────────────────┘
                    +
┌─────────────────────────────────────────────┐
│ 2. GMAC 認證:產生 Authentication Tag         │
│    對「密文 + AAD」進行有限域 GF(2¹²⁸) 運算     │
│    產生 128-bit 標籤 T                       │
└─────────────────────────────────────────────┘

接收方收到密文後,必須先驗證 Tag 是否正確;若驗證失敗,整段密文直接拒絕,不嘗試解密。

AAD(Additional Authenticated Data,附加認證資料)

GCM 允許將「不需要加密、但需要驗證」的資料納入認證範圍。例如:

  • HTTP 標頭:要明文傳輸給代理伺服器讀取,但不能被竄改
  • 封包路由資訊
  • 使用者 ID、時間戳記

AAD 不會被加密,但任何修改會導致 Tag 驗證失敗。

GCM 的三大保證

保證 說明
機密性(Confidentiality) 沒有金鑰看不懂密文(來自 CTR 部分)
完整性(Integrity) 密文若被竄改,Tag 驗證會失敗
真實性(Authenticity) 沒有金鑰無法產生有效的 Tag,所以收到的訊息確實來自持有金鑰的人

這三項合起來稱為 AEAD(Authenticated Encryption with Associated Data)

特性整覽

項目 評估
平行加密 ✅ 可以
平行解密 ✅ 可以
隨機存取 ✅ 可以
需要 Nonce 絕對不可重複
需要 Padding ❌ 不需要
完整性保證 ✅ 內建 Authentication Tag
AAD 支援 ✅ 支援
主要風險 Nonce 重用、Tag 截斷

實務範例:完整 AEAD 加密流程

GCM 的輸入與輸出比其他模式多一些,這也是它能提供 AEAD 三重保證的代價。

加密所需輸入

輸入 長度 必要性 說明
金鑰(Key) 32 bytes ✅ 必要 AES-256 金鑰
Nonce 12 bytes (96 bits) ✅ 必要 每次加密唯一,絕不可重複
明文(Plaintext) 任意長度 ✅ 必要 串流式,無 padding
AAD 任意長度 ⭕ 選擇性 不加密但要驗證的資料

加密產生的輸出

輸出 長度 說明
密文(Ciphertext) 與明文相同 串流加密、無 padding
Tag 16 bytes (128 bits) 認證標籤,解密時必須驗證

為什麼 Nonce 是 96 bits?

GCM 規範推薦 96 bits 的 Nonce 長度,因為它能直接組成 128 bits 的內部 Counter Block:

┌───────────────────────────┬────────────────────┐
│    Nonce (96 bits)        │  Counter (32 bits) │
│    每次加密唯一             │  從 1 開始遞增       │
└───────────────────────────┴────────────────────┘
                128 bits = AES 區塊大小

其他長度的 Nonce 雖然支援,但會多一次 GHASH 運算,效能較差且容易踩到實作 bug。新系統一律使用 96 bits Nonce

完整加密流程

1. 接收使用者密碼 passphrase(如有)
2. 產生隨機 salt   (16 bytes)            ← KDF 用
3. 產生隨機 nonce  (12 bytes)            ← 每次加密唯一
4. key = Argon2id(passphrase, salt, ...) ← 從密碼推導金鑰
5. (ciphertext, tag) = AES-256-GCM(key, nonce, plaintext, aad)
6. 輸出:salt ‖ nonce ‖ ciphertext ‖ tag
   ↑ 全部公開儲存/傳輸,只有 key 是秘密

解密驗證流程

1. 從輸入解出:salt, nonce, ciphertext, tag
2. key = Argon2id(passphrase, salt, ...)
3. plaintext = AES-256-GCM-decrypt(key, nonce, ciphertext, aad, tag)
   ↑ 若 tag 驗證失敗 → 直接拒絕,不回傳任何明文!

⚠️ 鐵律:Tag 驗證失敗時,絕對不能回傳「部分解密的明文」或具體錯誤原因。任何資訊洩漏都可能被攻擊者用於 oracle 攻擊。多數密碼學函式庫的 GCM API 設計成「驗證失敗就拋例外、不會回傳明文」,這是刻意的——不要繞過。

Nonce 重用:比 CTR 更嚴重

GCM 的 Nonce 規範與 CTR 類似,但重用後果更嚴重

模式 Nonce 重用後果
CTR 金鑰流洩漏(密文機密性失守)
GCM 金鑰流洩漏 + GMAC 認證金鑰 H 可被推導 → 攻擊者可偽造任意訊息的 Tag

也就是說,CTR 重用 Nonce 後攻擊者「能讀」,GCM 重用 Nonce 後攻擊者**「能讀 + 能偽造」**。這也是 2016 年 Nonce-Disrespecting Adversaries 論文揭露的災難——當時許多 IPSec 實作都有 Nonce 重用漏洞。

Nonce 策略

場景 策略
單機、單金鑰 計數器(從 1 開始遞增,每次加密 +1)
多 server、共享金鑰 隨機 Nonce(≤ 2³² 訊息/金鑰)或為每台 server 預留 Nonce 區段
高頻加密(>2³² 訊息/金鑰) 強烈建議改用 AES-GCM-SIV(容忍 Nonce 重複)

AAD 實務範例

情境:加密 HTTP Cookie,Cookie 名稱要明文(讓 proxy 識別),但要驗證未被竄改:

key       = (從金鑰庫取出)
nonce     = SecureRandom.nextBytes(12)
plaintext = "user_id=12345; role=admin"
aad       = "cookie-name: session_token; expires: 2026-04-30"

(ciphertext, tag) = AES-256-GCM(key, nonce, plaintext, aad)

最終 Cookie = base64(nonce ‖ ciphertext ‖ tag)
(cookie-name 與 expires 留在 HTTP header 明文)

如果攻擊者修改 cookie-nameexpiresTag 驗證會失敗——即使這些欄位是明文也受到保護。這就是 AAD 的核心價值:明文資料也可以納入完整性保護。

反例:別這樣用 GCM

❌ 用密碼直接推導 Nonce:nonce = SHA256(passphrase)[:12]
   → 同密碼永遠對到同 Nonce → 重用 → 災難

❌ 截斷 Tag 到 64 bits 「省空間」
   → 偽造成功率從 2⁻¹²⁸ 暴增到 2⁻⁶⁴ → 在某些場景可被暴力破解

❌ Tag 驗證失敗時回傳「部分明文」或具體錯誤碼
   → 構成 oracle,可能洩漏明文或金鑰

❌ 同一把 Key 加密超過 2³² 個訊息
   → 隨機 Nonce 碰撞機率超過 2⁻³²,可能踩到 Nonce 重用

現代地位

  • TLS 1.3:只支援 GCM 與 ChaCha20-Poly1305
  • HTTPS:絕大多數現代連線使用 AES-GCM
  • API 通訊:JWE、各大雲端 SDK 預設使用
  • 磁碟加密:BitLocker、LUKS 部分模式採用

全面比較表

功能與效能比較

模式 平行加密 平行解密 隨機存取 需 Padding 串流式 完整性
ECB
CBC
CFB
OFB
CTR
GCM

安全性與現代地位

模式 抗模式洩漏 主要弱點 現代推薦度
ECB 模式洩漏,根本不安全 🚨 禁用
CBC Padding Oracle、無完整性 ⚠️ 老系統相容
CFB 無完整性、無平行加密 ⚠️ 不推薦新用
OFB 完全無平行、無完整性 ⚠️ 不推薦新用
CTR 無完整性、Nonce 重用 ✅ 配合 MAC 使用
GCM Nonce 重用 業界標準

IV / Nonce 的需求差異

模式 名稱 隨機性要求 重用後果
ECB 不需要
CBC IV 必須隨機且不可預測 部分明文洩漏
CFB IV 必須不可預測 部分明文洩漏
OFB IV 必須唯一 完全洩漏金鑰流
CTR Nonce 必須唯一(不必隨機) 完全洩漏金鑰流
GCM Nonce 必須唯一 災難級:金鑰流洩漏 + 認證金鑰可被推導

GCM 的 Nonce 重用比 CTR 還嚴重:除了金鑰流洩漏外,攻擊者還能推導出 GMAC 的認證金鑰,進而偽造任意訊息的 Tag。


常見攻擊與安全陷阱

1. 模式洩漏攻擊(針對 ECB)

原理:相同明文塊 → 相同密文塊。直接觀察密文中的重複模式即可推測明文結構。

經典案例:Tux 企鵝圖、Adobe 2013 年外洩的 1.5 億筆密碼資料庫(用 ECB 加密相同密碼產生相同密文,可直接統計常用密碼)。

2. Padding Oracle Attack(針對 CBC)

原理:若伺服器在密文 padding 錯誤時回傳特殊錯誤訊息(HTTP 500、特定錯誤碼、回應時間差異等),攻擊者可在每塊密文上逐 byte 試錯,無需金鑰即可還原明文。

著名案例:2010 年 ASP.NET 框架的 Padding Oracle 漏洞(CVE-2010-3332),可解密整段加密的 ViewState 與 Cookie。

防禦

  • 改用 GCM 等具備認證的模式
  • 若必須用 CBC,務必先驗證 MAC 再嘗試 padding 解析(Encrypt-then-MAC)
  • 任何解密失敗都回傳「相同錯誤訊息 + 相同回應時間」

3. 位元翻轉攻擊(針對 CBC、CTR 等無認證模式)

原理:攻擊者翻轉密文中某個 bit,解密後對應的明文 bit 也會被翻轉。若應用層信任解密後的內容,可能造成嚴重後果。

範例:Cookie 中的 role=user 被翻轉為 role=admin

防禦:使用 GCM,或對密文額外計算 HMAC 驗證。

4. IV / Nonce 重用攻擊

模式 重用後果
CBC 第一塊明文若相同則密文相同(部分洩漏)
CTR 兩段密文 XOR 後等於兩段明文 XOR(嚴重洩漏)
GCM 同 CTR + 認證金鑰可被推導(災難)

防禦

  • IV 由密碼學等級的 RNG 產生
  • Nonce 採「計數器」或「隨機數」策略,並嚴格保證唯一性
  • 若 Nonce 空間較小(如 GCM 的 96 bits),切勿超過約 2³² 次加密就更換金鑰

5. 弱亂數產生器

許多 IV/Nonce 漏洞並非模式本身的問題,而是用了 java.util.RandomMath.random() 這類非密碼學等級的 RNG。

防禦:使用 SecureRandom/dev/urandomcrypto.randomBytes() 等密碼學安全的 RNG。


實戰選用指南

決策樹

要加密資料?
├── 新專案、可自由選擇?
│   └── ✅ 使用 AES-GCM(首選)
│       或 ChaCha20-Poly1305(無 AES 硬體加速時更快)
├── 與既有系統相容?
│   ├── 對方只支援 CBC?
│   │   └── ⚠️ CBC + HMAC(Encrypt-then-MAC),絕不單用 CBC
│   └── 對方只支援 CTR?
│       └── ⚠️ CTR + HMAC,絕不單用 CTR
├── 磁碟加密 / 隨機存取?
│   └── XTS-AES(CTR 衍生,專為磁碟設計)
└── 想用 ECB / OFB / CFB?
    └── 🚨 重新評估,幾乎都應改用 GCM

場景對照表

場景 建議模式 理由
HTTPS / TLS AES-GCM TLS 1.3 標準,硬體加速廣泛
API 訊息加密 AES-GCM AEAD 同時保證機密與完整
JWT / JWE AES-GCM 規範主要支援
資料庫欄位加密 AES-GCM 短資料、需驗證未被竄改
大檔案加密 AES-GCM 或 AES-GCM-SIV 平行高效、有完整性
磁碟全盤加密 XTS-AES 隨機存取、無需 IV 管理
IoT 裝置(弱 CPU) ChaCha20-Poly1305 純軟體實作快於 AES
政府 / 合規環境 AES-GCM 或 CBC + HMAC FIPS 140-2 認證

反例:絕對不要做的事

❌ AES-ECB          → 模式洩漏
❌ AES-CBC(單用)  → Padding Oracle、無完整性
❌ AES-CTR(單用)  → 無完整性,可被位元翻轉
❌ 重複的 IV/Nonce  → 各種洩漏
❌ 用 Random 產生 IV → 可預測
❌ 自己實作 GCM 認證 → 旁路時序攻擊難避免

常見問題

Q1:為什麼 CBC 已經不推薦,但很多舊系統還在用?

CBC 在 2000 年代是業界標準,許多協定(TLS 1.2、舊版 SSH、舊 IPSec)廣泛採用。它的問題是「無完整性保證 + Padding Oracle 風險」。如果系統正確實作 Encrypt-then-MAC(先加密、再對密文計算 HMAC),CBC 仍可安全使用。但對新專案來說,GCM 在效能、安全與 API 簡潔度上都更優,沒有理由再選 CBC。

Q2:為什麼 GCM 這麼好還有 ChaCha20-Poly1305?

GCM 的高效能依賴 CPU 的 AES-NI 指令集(Intel/AMD 從 2010 年起、ARM v8 起內建)。在沒有硬體加速的環境(老舊 IoT、部分嵌入式裝置),純軟體實作的 AES 比 ChaCha20 慢數倍。Google 為此推動 ChaCha20-Poly1305,TLS 1.3 將兩者並列為標準算法。

Q3:IV 可以公開嗎?要不要保密?

IV 不需要保密,通常會直接附在密文前面或一起傳輸。重要的是「不可重複」,CBC 還要求「不可預測」。把 IV 視為「公開但唯一」的元資料即可。

Q4:GCM 的 Nonce 為什麼是 96 bits 而不是 128 bits?

96 bits 是 GCM 規範的「推薦長度」,因為這個長度可以最有效率地產生 GMAC 的內部狀態(直接用 Nonce ‖ 32-bit counter 組成 128 bits)。其他長度雖然支援,但會多一次 GHASH 運算,效能較差。

Q5:CTR 的 Counter 會不會用完?

128 bits 的區塊空間中,若用 96 bits Nonce + 32 bits Counter,單次加密最多能處理 2³² × 16 bytes = 64 GB單一檔案超過 64 GB 就需要更換 Nonce 或拆段。對於需要加密 TB 級資料的系統,要使用 AES-GCM-SIV 或設計 Nonce 輪替機制。

Q6:Padding 為什麼一定要補一整塊?

考量歧義:若明文剛好 16 bytes 就不補,解密方無法判斷「最後一塊有沒有 padding」。PKCS#7 規定「永遠要補」,這樣解密方看最後一個 byte 就能知道要去掉幾個 byte。例:明文剛好 16 bytes → 多補一整塊全是 0x10

Q7:ECB 真的完全沒用嗎?

理論上,當「明文長度恰好等於一塊(16 bytes)、且每次明文都不同、且不會被攻擊者選擇明文」時,ECB 是安全的。實務上這種情境極少且邊界容易踩錯,建議直接禁用。


總結

核心要點

  1. AES 是區塊密碼,加密模式決定如何串接多塊
  2. ECB 永遠不要用——模式洩漏是致命缺陷
  3. CBC、CFB、OFB 是上一個世代的選擇——無內建完整性保護
  4. CTR 是現代基礎——平行高效但需配合認證
  5. GCM 是現代答案——AEAD 同時保證機密、完整、真實性
  6. Nonce/IV 唯一性是所有模式的命脈

一句話速記

模式 一句話形容
ECB 「最簡單但最危險」
CBC 「鏈式串接,曾經主流,現已落伍」
CFB 「CBC 的串流版,少見」
OFB 「不依賴密文的串流,無錯誤擴散」
CTR 「計數器模式,可平行可隨機存取,但無完整性」
GCM 「CTR + 認證 = 現代業界標準」

選用速查

新專案     → GCM
老系統     → CBC + HMAC
磁碟加密   → XTS-AES
弱 CPU     → ChaCha20-Poly1305

延伸閱讀


建立日期:2026-04-30 最後更新:2026-04-30(補充 AES 金鑰長度規格、六種模式的實務範例、模式速覽對照表)

🔗相關文章