逐位元組解析 X25519MLKEM768:封包層上的後量子金鑰交換

後量子遷移常被以簡報的方式討論—法規、期限、「密碼敏捷性」。但在一條 TLS 1.3 連線上,實際的改變其實具體而微小,小到可以用手讀完:一個代碼點,加上約 1.2 KB 的金鑰材料。以下就是它真正的樣貌。

命名群組

TLS 1.3 客戶端會在 supported_groups 擴充中宣告它支援的金鑰交換群組,並將實際的公鑰放在 key_share 中。混合後量子群組為 X25519MLKEM768,IANA 代碼點 0x11EC。「混合」意指它同時攜帶兩種金鑰交換:傳統的 X25519 與後量子的 ML-KEM-768(標準化為 FIPS 203 的金鑰封裝機制)。連線祕密由兩者共同推導,因此只要其中任一原語仍然安全,連線就維持安全—讓您獲得後量子保護,而無須將一切賭在一個年輕的演算法上。此群組定義於 IETF 草案 draft-ietf-tls-ecdhe-mlkem

代碼點陷阱

0x11EC 換算為十進位是 4588。這個十六進位/十進位的混淆經常讓人踩雷:有人讀到「4588」,卻在設定檔或 C 字面值中寫成 0x4588,於是指向了一個完全不同、且未被指派的群組—然後納悶為什麼交握悄悄退回了傳統密碼。0x4588 的十進位是 17800,毫無意義。每次處理這件事都會重複的教訓是:別相信標籤,要讀位元組。

key_share 裡有什麼

傳統上,X25519 的 key_share 項目為 32 位元組。改用 X25519MLKEM768 後,客戶端的項目躍升至 1216 位元組:一個 ML-KEM-768 封裝金鑰串接 X25519 公鑰—順序為 ML-KEM 在前。

client key_share (X25519MLKEM768):
  [ ML-KEM-768 encapsulation key : 1184 bytes ]
  [ X25519 public key            :   32 bytes ]
    total                        : 1216 bytes

伺服器則以 1120 位元組回應—一個 ML-KEM-768 密文(1088 位元組)後接其 X25519 公鑰(32 位元組)。

這個大小的躍升並非只是外觀問題。原本能輕鬆塞進單一封包的 ClientHello,現在會超出常見的邊界—你會看到它橫跨多個 TLS 記錄與 TCP 區段。任何要解析或改寫交握的元件,都必須先重組資料流,才能讀到 key_share。這正是許多中間設備在 PQC 上悄悄失效的地方。

親自驗證

你不必聽我說:

openssl s_client -connect cloudflare.com:443 \
    -groups X25519MLKEM768 -trace

讀取 trace 中的 key_share 擴充:你會看到 0x11EC 群組 ID 與約 1.2 KB 的資料。若伺服器在其 ServerHello 中以相同群組回應,你就完成了端到端的後量子金鑰交換協商。Cloudflare、Google 與 AWS 都已支援。

當兩端意見不一致時,才是有趣之處

遷移的難處不在於全新的交握—而在於你的客戶端與伺服器不會同步移動。現代客戶端提供 X25519MLKEM768;老舊的來源端只懂 X25519。或者反過來。一般情況下,這意味著在兩端都升級之前都沒有 PQC—而在真實的設備、嵌入式裝置與第三方軟體環境中,這一天永遠不會到來。

一個位於路徑上的元件可以化解這個分歧:對能說 X25519MLKEM768 的一端呈現該群組,對不能的一端使用傳統密碼,並在兩套金鑰排程之間轉譯—在封包層升級連線,而兩端皆無須變更。這就是 TLS Lane 背後的構想,也是為什麼我從位元組層級、而非政策層級思考 PQC。這項遷移是你今天就能實際著手的事,一次一條連線。

若你想知道自己的基礎設施此刻正在協商什麼,那正是我的起點

← 所有文章