AES(Advanced Encryption Standard,進階加密標準)是全宇宙使用最廣的密碼。

在 AES 被採用之前,標準密碼是 DES 與其升級版 Triple DES(3DES)

  • DES 的安全性只有荒謬的 56 位元。
  • 3DES 雖然提供更高的安全等級(112 位元),卻沒有效率——金鑰得長達 168 位元才能得到 112 位元安全性,而且在軟體中很慢(DES 是為了在積體電路上快,而非在主流 CPU 上快而設計的)。

AES 兩個問題都解決了。

NIST 於 2000 年將 AES 標準化以取代 DES,此後它成為全世界事實上的加密標準。今日多數商用加密產品都支援 AES,NSA 也核准它用於保護最高機密資訊。

(有些國家確實偏好使用自己的密碼,多半是因為他們不想用美國標準——但 AES 其實比較像比利時的東西,而非美國的。)

延伸:AES 競賽與 Rijndael 這個名字

AES 在成為 AES 競賽 15 個候選者之一時,原本叫做 Rijndael——這是它兩位發明者 Rijmen 與 Daemen 名字的合併,唸起來像 “rain-dull”。

AES 競賽是 NIST 於 1997 至 2000 年間舉辦的流程,目的依 1997 年《聯邦公報》的競賽公告所述,是要規範出「一個非機密的、公開揭露的加密演算法,能夠妥善保護敏感的政府資訊直到下個世紀」。

這場競賽有點像密碼學家版的「達人秀」:任何人都能參加,方式是提交一個密碼,或是破解其他參賽者的密碼。

AES 內部結構#

AES 以 128、192 或 256 位元的秘密金鑰處理 128 位元的區塊。128 位元金鑰最常見,因為它讓加密略快一些,而且對多數應用而言,128 位元與 256 位元安全性之間的差別沒有意義。

內部狀態#

有些密碼處理單一位元或 64 位元字,AES 則操作位元組。它把 16 位元組的明文視為一個二維位元組陣列 s = s₀, s₁, ..., s₁₅,排成 4×4:

s0   s4   s8   s12
s1   s5   s9   s13
s2   s6   s10  s14
s3   s7   s11  s15

(用字母 s 是因為這個陣列稱為內部狀態,internal state,或簡稱 state。)AES 變換這個陣列的位元組、欄與列,產生最終值也就是密文。

圖 4-3:AES 的內部狀態:16 位元組的 4 × 4 陣列

輪數#

AES 使用 SPN 結構,輪數依金鑰長度而定:

金鑰長度輪數
128 位元10
192 位元12
256 位元14

圖 4-4:AES 的內部運作

一輪的四個構件#

除了最後一輪之外,每一輪都是 SubBytes、ShiftRows、MixColumns、AddRoundKey 的序列:

  • AddRoundKey:把一把輪金鑰 XOR 進內部狀態。

  • SubBytes:依 S-box 把每個位元組 s₀, s₁, ..., s₁₅ 替換成另一個位元組。此處的 S-box 是一張 256 個元素的查找表。

  • ShiftRows:把第 i 列位移 i 個位置(i 從 0 到 3):

    s0   s4   s8   s12          s0   s4   s8   s12
    s1   s5   s9   s13    →     s5   s9   s13  s1
    s2   s6   s10  s14          s10  s14  s2   s6
    s3   s7   s11  s15          s15  s3   s7   s11

圖 4-5:ShiftRows 在內部狀態的每一列中旋轉位元組

  • MixColumns:對狀態的四個欄各套用相同的線性變換。

記得 SPN 中 S 代表替換、P 代表置換。這裡的替換層是 SubBytes置換層是 ShiftRows 與 MixColumns 的組合

金鑰排程:KeyExpansion#

KeyExpansion 是 AES 的金鑰排程演算法。它從 16 位元組的金鑰產生 11 把各 16 位元組的輪金鑰(K₀, K₁, ..., K₁₀),使用與 SubBytes 相同的 S-box 以及一系列 XOR。

這種「從任一輪金鑰就能取得主金鑰」的能力,通常被視為對旁道攻擊的防禦不夠完善——因為攻擊者可能輕易還原出某一把輪金鑰。

每個操作為何不可或缺#

少了這些操作,AES 會完全不安全。每個操作都以特定方式貢獻了 AES 的安全性:

  • 沒有 KeyExpansion:所有輪都會使用同一把金鑰 K,AES 就會受滑動攻擊。
  • 沒有 AddRoundKey:加密就不依賴金鑰,任何人都能在沒有金鑰的情況下解密任何密文。
  • 沒有 SubBytes:SubBytes 帶來非線性運算,也就是密碼學強度。少了它,AES 只會是一個大型線性方程組,用高中代數就能解開。
  • 沒有 ShiftRows:某一欄的變化永遠不會影響其他欄,意味著你可以為每一欄建立四張 2^32 個元素的碼本來破解 AES。(記得在安全的區塊密碼中,翻轉輸入的一個位元應該影響所有輸出位元。)
  • 沒有 MixColumns:某個位元組的變化不會影響狀態中的其他位元組。選擇明文攻擊者只要存下 16 張各 256 位元組的查找表(存放每個位元組每個可能值的加密結果),就能解密任何密文。

AES 的最後一輪不含 MixColumns。省略它是為了免去無用的計算:因為 MixColumns 是線性的(也就是可預測的),你可以用一種不依賴位元值也不依賴金鑰的方式組合位元,來抵銷它在最末輪的效果。

但 SubBytes 不同——在 AddRoundKey 之前的狀態值未知時,它無法被反轉。

解密#

要解密一段密文,AES 對每個操作取其反函式逐一回捲:

  • SubBytes 的反查找表還原 SubBytes 變換。
  • ShiftRows 往反方向位移。
  • 套用 MixColumns 的反矩陣(即編碼其操作之矩陣的反矩陣)。
  • AddRoundKey 的 XOR 維持不變,因為 XOR 的反運算還是 XOR。

動手試試#

用 Python 的 cryptography 函式庫試試 AES 的加解密:

#!/usr/bin/env python

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from binascii import hexlify as hexa
from os import urandom

# pick a random 16-byte key using Python's crypto PRNG
k = urandom(16)
print "k = %s" % hexa(k)
# create an instance of AES-128 to encrypt a single block
cipher = Cipher(algorithms.AES(k), modes.ECB(), backend = default_backend())
aes_encrypt = cipher.encryptor()

# set plaintext block p to the all-zero string
p = '\x00'*16
# encrypt plaintext p to ciphertext c
c = aes_encrypt.update(p) + aes_encrypt.finalize()
print "enc(%s) = %s" % (hexa(p), hexa(c))
# decrypt ciphertext c to plaintext p
aes_decrypt = cipher.decryptor()
p = aes_decrypt.update(c) + aes_decrypt.finalize()
print "dec(%s) = %s" % (hexa(c), hexa(p))

執行後會得到類似這樣的輸出(因為每次執行金鑰都隨機,你的結果會不同):

$ ./aes_block.py
k = 2c6202f9a582668aa96d511862d8a279
enc(00000000000000000000000000000000) = 12b620bb5eddcde9a07523e59292a6d7
dec(12b620bb5eddcde9a07523e59292a6d7) = 00000000000000000000000000000000

AES 安全嗎#

AES 已經安全到一個區塊密碼所能達到的程度,而且它永遠不會被破解。

根本原因在於:所有輸出位元都以某種複雜的、偽隨機的方式依賴於所有輸入位元。

為了達成這點,AES 的設計者為每個元件都有特定理由地審慎挑選——MixColumns 是為了它最大化的擴散性質,SubBytes 是為了它最佳的非線性——並且證明了這樣的組合能保護 AES 免於整類整類的密碼分析攻擊。

但沒有證明#

沒有證明說 AES 對所有可能的攻擊免疫。

一來我們不知道所有可能的攻擊有哪些,二來我們也不總是知道如何證明一個密碼對某個給定攻擊是安全的。

真正對 AES 安全性建立信心的唯一方式,就是群眾外包攻擊:讓許多有能力的人嘗試破解 AES,並且(但願如此)失敗。

經過超過 15 年、數百篇研究論文,AES 的理論安全性只被搔到一點皮毛。

2011 年密碼分析者找到一個方法,執行約 2^126 次(而非 2^128 次)運算就能還原 AES-128 金鑰,加速了四倍。

但這個「攻擊」需要瘋狂數量的明文—密文對——約 2^88 位元之多。換句話說,這是個不錯的發現,但不是你需要擔心的東西。

結論:實作與部署密碼學時你該在意的事有一百萬件,但 AES 的安全性不在其中

區塊密碼最大的威脅不在其核心演算法,而在其運作模式。當模式選錯了、或正確的模式被誤用,即使強如 AES 也救不了你。