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 硬體加速的裝置(嵌入式、部分手機)上更快。
結構與步驟#
- 建立 4×4 的 32 位元字初始狀態:4 個常數字(
"expa nd 3 2-by te k")、8 個金鑰字(256 位元)、1 個計數器、3 個 nonce 字(96 位元)。 - 對狀態做 20 輪(10 次「雙輪」):每雙輪含 4 次欄輪 + 4 次對角輪,每次
quarter_round(a,b,c,d): a += b; d ^= a; d <<<= 16c += d; b ^= c; b <<<= 12a += b; d ^= a; d <<<= 8c += d; b ^= c; b <<<= 7- 把 20 輪後的狀態與初始狀態相加(字級),序列化為 64 位元組金鑰流區塊。
- 與明文 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。
練習#
- 用教學版
chacha20_block生成第一個區塊,驗證與 RFC 8439 §2.3.2 的測試向量一致。 - 寫程式示範 nonce 重用的災難:用同一把金鑰與 nonce 加密兩段訊息,將兩段密文 XOR,觀察金鑰流消去後可讀的資訊。
- 比較:你的機器上 AES-GCM 與 ChaCha20-Poly1305 各加密 10 MB 的耗時(用
time.perf_counter),並解釋差異來源(是否有 AES 硬體加速)。