跳轉到

4.3 串流加密與 ChaCha20#

概念說明#

串流加密(stream cipher)延續 3.1 節一次性密碼本的想法:C = M ⊕ keystream,但把「真隨機、等長、只用一次」的金鑰換成由短金鑰 + nonce 經偽隨機產生器擴張出的金鑰流。安全性因此從「資訊理論」降級為「計算安全」:只要金鑰流與隨機不可區分,攻擊者就無法區分密文與隨機。

歷史教訓已內建在定義裡:維吉尼亞密碼(2.3 節)敗於金鑰流循環,RC4 敗於金鑰流統計偏誤(前幾位元組偏向 0,被 WEP 與 TLS 舊版利用)。現代串流加密的設計目標就是:金鑰流週期天文數字、無統計偏誤、nonce 重用防護明確。

ChaCha20:現代串流加密標準#

由 Bernstein 設計(2008),Google 於 2014 年起用於 TLS,RFC 8439 標準化,是 TLS 1.3 內建的兩個加密套件之一(另一為 AES-GCM)。核心是 ARX 運算(Add-Rotate-XOR):只用加法、循環位移、XOR——在軟體上是常數時間的,天然免疫於 AES 常見的快取側頻道,且在無 AES 硬體加速的裝置(嵌入式、部分手機)上更快。

結構與步驟#

  1. 建立 4×4 的 32 位元字初始狀態:4 個常數字("expa nd 3 2-by te k")、8 個金鑰字(256 位元)、1 個計數器、3 個 nonce 字(96 位元)。
  2. 對狀態做 20 輪(10 次「雙輪」):每雙輪含 4 次欄輪 + 4 次對角輪,每次 quarter_round(a,b,c,d):
  3. a += b; d ^= a; d <<<= 16
  4. c += d; b ^= c; b <<<= 12
  5. a += b; d ^= a; d <<<= 8
  6. c += d; b ^= c; b <<<= 7
  7. 把 20 輪後的狀態與初始狀態相加(字級),序列化為 64 位元組金鑰流區塊。
  8. 與明文 XOR 得密文;計數器遞增產生下一區塊。認證由配套的 Poly1305 MAC 提供(組成 ChaCha20-Poly1305 AEAD)。

Python 實作#

# (a) 教學版:完整可跑的 ChaCha20 區塊函數(RFC 8439)
def rotl(v, n): return ((v << n) | (v >> (32 - n))) & 0xffffffff

def quarter_round(s, a, b, c, d):
    s[a] = (s[a] + s[b]) & 0xffffffff; s[d] = rotl(s[d] ^ s[a], 16)
    s[c] = (s[c] + s[d]) & 0xffffffff; s[b] = rotl(s[b] ^ s[c], 12)
    s[a] = (s[a] + s[b]) & 0xffffffff; s[d] = rotl(s[d] ^ s[a], 8)
    s[c] = (s[c] + s[d]) & 0xffffffff; s[b] = rotl(s[b] ^ s[c], 7)

def chacha20_block(key32: bytes, counter: int, nonce12: bytes) -> bytes:
    c = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574]
    k = list(int.from_bytes(key32[i*4:i*4+4], "little") for i in range(8))
    n = list(int.from_bytes(nonce12[i*4:i*4+4], "little") for i in range(3))
    state = c + k + [counter] + n
    w = state[:]
    for _ in range(10):
        quarter_round(w, 0, 4,  8, 12); quarter_round(w, 1, 5,  9, 13)
        quarter_round(w, 2, 6, 10, 14); quarter_round(w, 3, 7, 11, 15)
        quarter_round(w, 0, 5, 10, 15); quarter_round(w, 1, 6, 11, 12)
        quarter_round(w, 2, 7,  8, 13); quarter_round(w, 3, 4,  9, 14)
    out = [(x + y) & 0xffffffff for x, y in zip(w, state)]
    return b"".join(x.to_bytes(4, "little") for x in out)

# (b) 實務:用 PyCryptodome 的 ChaCha20-Poly1305 AEAD
from Crypto.Cipher import ChaCha20_Poly1305
import os

key = os.urandom(32); nonce = os.urandom(12)
c = ChaCha20_Poly1305.new(key=key, nonce=nonce)
ct, tag = c.encrypt_and_digest(b"stream cipher with integrity")
d = ChaCha20_Poly1305.new(key=key, nonce=nonce)
print(d.decrypt_and_verify(ct, tag))

安全性分析#

  • 金鑰流性質:ChaCha20 經 20 輪 ARX 後被分析為遠超過 256 位元安全度的偽隨機函數;公開最佳攻擊僅達 7 輪左右。
  • nonce 紀律:96 位元 nonce 在同一金鑰下必須唯一。隨機產生時 2^32 個訊息內碰撞機率可忽略,但高流量系統建議用計數器式 nonce(RFC 8439 §4)。
  • 與 AES 的選擇:有 AES-NI 硬體 → AES-GCM 通常更快;無硬體或需抗快取側頻道 → ChaCha20-Poly1305。兩者皆為 AEAD、皆受 TLS 1.3 支援,是現代對稱加密的「雙支柱」。
  • 切勿自製串流加密:把雜湊函數、RC4 變體拿來湊金鑰流是常見漏洞來源;請只用標準 AEAD。

練習#

  1. 用教學版 chacha20_block 生成第一個區塊,驗證與 RFC 8439 §2.3.2 的測試向量一致。
  2. 寫程式示範 nonce 重用的災難:用同一把金鑰與 nonce 加密兩段訊息,將兩段密文 XOR,觀察金鑰流消去後可讀的資訊。
  3. 比較:你的機器上 AES-GCM 與 ChaCha20-Poly1305 各加密 10 MB 的耗時(用 time.perf_counter),並解釋差異來源(是否有 AES 硬體加速)。