目錄
- 為什麼需要「加密模式」?
- 核心名詞先理解
- 六種模式實務規格速覽
- ECB - 電子密碼本模式
- CBC - 密碼分組鏈接模式
- CFB - 密文回饋模式
- OFB - 輸出回饋模式
- CTR - 計數器模式
- GCM - 認證加密模式
- 全面比較表
- 常見攻擊與安全陷阱
- 實戰選用指南
- 常見問題
- 總結
為什麼需要「加密模式」?
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 | 最強(業界主流) |
幾個容易混淆的重點:
- 區塊大小與金鑰長度是兩回事:不論哪個變體,區塊永遠是 16 bytes——所以 IV、Padding、Counter 的設計都跟金鑰長度無關
- 「AES-256」指的是金鑰 256 bits,不是區塊 256 bits
- 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(避免歧義)
- 例:30 bytes 的明文 → 第二塊只有 14 bytes → 補上 2 個 byte,每個值都是
- 只有區塊式模式(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)都不變。
從表格能看出的三個規律
- Key 長度只跟 AES 變體有關(AES-256 永遠是 32 bytes),與模式無關
- Padding 只有 ECB 與 CBC 需要——其他四種都是「串流式運作」,不需要對齊區塊
- 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 可預測 |
主要弱點
- 加密無法平行:對大檔案效能不佳
- Padding Oracle Attack:若伺服器在 padding 錯誤時回傳特殊錯誤訊息,攻擊者可在不知金鑰的情況下還原明文
- 位元翻轉攻擊:攻擊者翻轉密文中的某 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 與金鑰。這帶來兩個重要特性:
- 可預先計算金鑰流:在拿到明文前就能準備好所有金鑰流,加密只剩 XOR 運算
- 無錯誤擴散:密文中某個 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 輸入。
革命性優點
- 加解密皆可平行:每塊獨立計算,多核 CPU 可滿載
- 隨機存取:要解密第 1000 塊?直接用 Counter=999 算出金鑰流即可,不必從頭跑——這是「磁碟加密」的關鍵
- 無需 Padding:串流式,明文長度可任意
- 可預先計算:金鑰流產生與明文無關
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-name 或 expires,Tag 驗證會失敗——即使這些欄位是明文也受到保護。這就是 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.Random、Math.random() 這類非密碼學等級的 RNG。
防禦:使用 SecureRandom、/dev/urandom、crypto.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 是安全的。實務上這種情境極少且邊界容易踩錯,建議直接禁用。
總結
核心要點
- AES 是區塊密碼,加密模式決定如何串接多塊
- ECB 永遠不要用——模式洩漏是致命缺陷
- CBC、CFB、OFB 是上一個世代的選擇——無內建完整性保護
- CTR 是現代基礎——平行高效但需配合認證
- GCM 是現代答案——AEAD 同時保證機密、完整、真實性
- 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 金鑰長度規格、六種模式的實務範例、模式速覽對照表)