Luke a Pro

Luke Sun

Developer & Marketer

🇺🇦
EN||

AES 是如何運作的(不靠數學也能懂)

| , 6 minutes reading.

1. 為什麼要關心這個問題?

每當你:

  • 訪問 HTTPS 網站
  • 使用 WhatsApp 或 Signal 發訊息
  • 將檔案存到加密硬碟
  • 用 SSH 連接伺服器

你都在使用 AES。

但大多數開發者對 AES 的了解停留在「呼叫函式庫」的層面。這篇文章的目標是讓你理解 AES 內部在做什麼——不需要數學證明,只需要直觀理解。

理解 AES 的運作方式能幫助你:

  • 選擇正確的工作模式(ECB vs CBC vs GCM)
  • 理解為什麼某些配置是危險的
  • 在除錯時知道問題可能出在哪裡

2. 定義

AES(Advanced Encryption Standard) 是一種對稱分組加密演算法,在 2001 年被 NIST 選為取代 DES 的新標準。

技術規格:

  • 區塊大小: 固定 128 位元(16 位元組)
  • 金鑰長度: 128、192 或 256 位元
  • 輪數: 10、12 或 14 輪(取決於金鑰長度)
  • 結構: SPN(替換-置換網路)

AES 的原名是 Rijndael(發音接近「Rain-doll」),由比利時密碼學家 Vincent Rijmen 和 Joan Daemen 設計。

3. SPN vs Feistel:結構的差異

Feistel(DES 使用)

每輪只處理一半資料:
L' = R
R' = L ⊕ F(R, K)

優點:加密解密共用相同電路
缺點:擴散較慢,需要更多輪數

SPN(AES 使用)

每輪處理全部資料:
State' = MixColumns(ShiftRows(SubBytes(State))) ⊕ RoundKey

優點:擴散快,更少輪數達到相同安全性
缺點:加密和解密需要不同的操作(逆操作)

4. AES 的狀態矩陣

AES 把 16 位元組的輸入組織成一個 4×4 的位元組矩陣:

輸入:00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

狀態矩陣:
┌────┬────┬────┬────┐
│ 00 │ 04 │ 08 │ 0C │
├────┼────┼────┼────┤
│ 01 │ 05 │ 09 │ 0D │
├────┼────┼────┼────┤
│ 02 │ 06 │ 0A │ 0E │
├────┼────┼────┼────┤
│ 03 │ 07 │ 0B │ 0F │
└────┴────┴────┴────┘

注意:是按列填充,不是按行!

所有的加密操作都在這個矩陣上進行。

5. AES 輪函式的四個步驟

每一輪(除了最後一輪)都執行這四個操作:

步驟 1:SubBytes(位元組替換)

每個位元組通過一個叫做 S-Box 的查找表進行替換。

┌─────────────────────────────────────────────┐
│ 輸入位元組 → 查表 → 輸出位元組              │
│                                             │
│ 例如:0x53 → S-Box[0x53] → 0xED             │
└─────────────────────────────────────────────┘

為什麼需要這步?

這是 AES 中唯一的非線性操作。沒有它,AES 就只是一堆線性操作(XOR、移位、乘法),可以用線性代數直接求解。

S-Box 的設計基於有限域的乘法逆元,具有良好的密碼學特性:

  • 沒有不動點(沒有 x 使得 S(x) = x)
  • 沒有反不動點(沒有 x 使得 S(x) = x ⊕ 0xFF)
  • 高度非線性

步驟 2:ShiftRows(行移位)

矩陣的每一行向左循環移位不同的位置:

第 0 行:不移位
第 1 行:左移 1 位
第 2 行:左移 2 位
第 3 行:左移 3 位

之前:                    之後:
┌────┬────┬────┬────┐    ┌────┬────┬────┬────┐
│ 00 │ 04 │ 08 │ 0C │    │ 00 │ 04 │ 08 │ 0C │
├────┼────┼────┼────┤    ├────┼────┼────┼────┤
│ 01 │ 05 │ 09 │ 0D │ →  │ 05 │ 09 │ 0D │ 01 │
├────┼────┼────┼────┤    ├────┼────┼────┼────┤
│ 02 │ 06 │ 0A │ 0E │    │ 0A │ 0E │ 02 │ 06 │
├────┼────┼────┼────┤    ├────┼────┼────┼────┤
│ 03 │ 07 │ 0B │ 0F │    │ 0F │ 03 │ 07 │ 0B │
└────┴────┴────┴────┘    └────┴────┴────┴────┘

為什麼需要這步?

確保每一列的位元組在下一輪會被分散到不同的列。這提供了擴散——一個輸入位元的變化會影響整個輸出。

步驟 3:MixColumns(列混合)

每一列被視為一個多項式,與一個固定的多項式相乘(在 GF(2⁸) 有限域中):

┌────┐     ┌────────────────┐     ┌────┐
│ a₀ │     │ 02 03 01 01 │     │ b₀ │
│ a₁ │  ×  │ 01 02 03 01 │  =  │ b₁ │
│ a₂ │     │ 01 01 02 03 │     │ b₂ │
│ a₃ │     │ 03 01 01 02 │     │ b₃ │
└────┘     └────────────────┘     └────┘

為什麼需要這步?

這是擴散的另一個來源。它確保一列中的每個位元組都會影響該列的所有位元組。結合 ShiftRows,幾輪之後輸入的每個位元都會影響輸出的每個位元。

步驟 4:AddRoundKey(輪金鑰加)

狀態矩陣與該輪的子金鑰進行 XOR:

State' = State ⊕ RoundKey

為什麼需要這步?

這是金鑰材料被引入的地方。沒有這步,加密就與金鑰無關——任何人都可以「解密」。

6. 完整的 AES 流程

明文(16 位元組)


AddRoundKey(初始輪金鑰)


┌─────────────────────────┐
│ 重複 N-1 輪:           │
│   SubBytes              │
│   ShiftRows             │
│   MixColumns            │
│   AddRoundKey           │
└─────────────────────────┘


┌─────────────────────────┐
│ 最後一輪(無 MixColumns)│
│   SubBytes              │
│   ShiftRows             │
│   AddRoundKey           │
└─────────────────────────┘


密文(16 位元組)

N = 10(AES-128)、12(AES-192)、14(AES-256)

為什麼最後一輪沒有 MixColumns?這是為了讓加密和解密更對稱——解密時第一輪也沒有 MixColumns。

7. 金鑰擴展

AES 需要為每一輪產生一個子金鑰。這通過金鑰擴展演算法完成:

原始金鑰(128/192/256 位元)


┌─────────────────────────────────────────┐
│ 金鑰擴展演算法:                        │
│   - 使用 S-Box                          │
│   - 使用輪常數(Rcon)                  │
│   - 每輪金鑰依賴前一輪金鑰              │
└─────────────────────────────────────────┘


11/13/15 個輪金鑰(每個 128 位元)

金鑰擴展確保:

  • 原始金鑰的任何位元變化都會影響多個輪金鑰
  • 無法從一個輪金鑰推導出其他輪金鑰(不知道原始金鑰的情況下)

8. 為什麼是 128 位元區塊?

安全考量

64 位元區塊(DES):2³² 個區塊後發生碰撞(約 32GB)
128 位元區塊(AES):2⁶⁴ 個區塊後發生碰撞(約 256 EB)

128 位元區塊讓你可以安全處理海量資料而不必擔心生日攻擊。

效能考量

現代 CPU 的暫存器:64 位元或更大
128 位元 = 2 × 64 位元操作
256 位元區塊會更慢且收益遞減

128 位元是安全性和效能的甜蜜點。

9. AES 的安全性

目前狀態

AES-128:安全
AES-192:安全
AES-256:安全

最佳已知攻擊:
- AES-128 的複雜度從 2¹²⁸ 降到約 2¹²⁶·¹
- 這在實務上仍然不可行
- 沒有實際的破解方法

相關金鑰攻擊

如果攻擊者能用多個相關金鑰加密:
AES-256 可能比 AES-128 更脆弱

但在實際應用中:
- 金鑰應該是隨機的
- 不存在「相關金鑰」
- AES-256 仍然安全

側通道攻擊

AES 本身是安全的,但實作可能洩漏資訊:
- 時序攻擊:不同操作用時不同
- 快取攻擊:S-Box 查找的快取行為
- 電力分析:消耗的電力與資料相關

防禦:
- 使用硬體加速(AES-NI)
- 常數時間實作
- 不要自己實作 AES

10. 程式碼範例:AES 的基本使用

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import os

def aes_encrypt_block(plaintext: bytes, key: bytes) -> bytes:
    """
    使用 AES-ECB 加密單個區塊(僅用於理解,不要在生產中使用 ECB!)
    """
    if len(plaintext) != 16:
        raise ValueError("AES 區塊必須是 16 位元組")
    if len(key) not in (16, 24, 32):
        raise ValueError("AES 金鑰必須是 16、24 或 32 位元組")

    cipher = Cipher(algorithms.AES(key), modes.ECB())
    encryptor = cipher.encryptor()
    return encryptor.update(plaintext) + encryptor.finalize()

def aes_decrypt_block(ciphertext: bytes, key: bytes) -> bytes:
    """
    使用 AES-ECB 解密單個區塊
    """
    cipher = Cipher(algorithms.AES(key), modes.ECB())
    decryptor = cipher.decryptor()
    return decryptor.update(ciphertext) + decryptor.finalize()

# 示範
if __name__ == "__main__":
    # 產生隨機金鑰
    key = os.urandom(32)  # AES-256

    # 明文必須是 16 位元組
    plaintext = b"Hello, AES-256!!"

    # 加密和解密
    ciphertext = aes_encrypt_block(plaintext, key)
    decrypted = aes_decrypt_block(ciphertext, key)

    print(f"明文:   {plaintext}")
    print(f"密文:   {ciphertext.hex()}")
    print(f"解密:   {decrypted}")

11. 常見誤區

誤區現實
「AES-256 比 AES-128 安全兩倍」AES-256 有 2^256 種金鑰,是 AES-128 的 2^128 倍,但兩者在實務上都是不可破解的
「AES 加密是安全的」AES 區塊加密是安全的,但工作模式的選擇同樣重要(ECB 是不安全的)
「更長的金鑰一定更好」對於量子電腦,AES-256 確實更好。但對於經典電腦,AES-128 已經足夠
「AES 解密比加密慢」使用硬體加速時,兩者速度相同

12. 本章小結

三點要記住:

  1. AES 使用 SPN 結構。 每輪的四個步驟(SubBytes、ShiftRows、MixColumns、AddRoundKey)各有其目的:非線性、擴散、更多擴散、引入金鑰。

  2. 128 位元區塊解決了 DES 的生日攻擊問題。 你可以用同一個金鑰安全加密 EB 級的資料,而不用擔心碰撞。

  3. AES 本身是安全的,但使用方式很重要。 選擇正確的工作模式(GCM、CBC+HMAC)比選擇 AES-128 還是 AES-256 更重要。

13. 下一步

我們理解了 AES 對單個區塊的加密。但現實中的資料很少剛好是 16 位元組。我們如何加密任意長度的資料?

在下一篇文章中,我們將探討:AES 的工作模式——為什麼 ECB 是災難,CBC 需要注意什麼,以及為什麼 GCM 成為了現代預設選擇。