TLS 1.3 的 ClientHello 得在還不知道 origin 支援什麼之前,先押一個 key agreement 演算法——Cloudflare 過去全站統一押 X25519:事後量出來,這個猜測對大約三成的連線不是最佳選擇;真正觸發 HelloRetryRequest、要多付一次來回的連線比例則是 52%。
握手之前,先把 origin 支援什麼量出來
TLS 1.3 把 key agreement 搬到最前面:client 開連線送出的第一個封包(ClientHello)裡就得附上一個 keyshare,這時候它連 origin 支援哪個演算法都還不知道——這是協定本身的規定,Cloudflare 沒有置喙空間。舊版 TLS 允許雙方先商量再決定要用哪個演算法,TLS 1.3 為了把握手壓到一個 round trip,把這段商量拿掉,改成 client 主動下注:直接挑一個 key agreement group、算出對應的 keyshare、跟 ClientHello 一起送出去。運氣好,origin 剛好支援這個猜測,握手一次就完成;運氣不好,就是 HelloRetryRequest 那一整輪額外的來回。多年來,Cloudflare 面對這個問題的答案很單純:不管對面是誰,一律押 X25519。
賭錯要多付一次來回
猜對了,握手一個 round trip 就結束。猜錯了,origin 會退回一個 HelloRetryRequest,告訴 client 它想要哪個演算法,client 只能照做、重送一次 ClientHello——這多出來的一整輪來回,就是猜錯要付的代價。固定押 X25519 的年代,真正被打回 HelloRetryRequest、需要多送一次 ClientHello 的連線比例長期停在五成上下,大約 52%,等於每兩條新連線裡就有一條要多繞一圈。Automatic Key Exchange 上線之後,這個比例降到 3.7%,p90 的握手延遲也跟著省下超過 150 毫秒——對一個一次性的、使用者感受得到的握手來說,這不是小數目。
按播放看猜對/猜錯兩條路徑怎麼分岔 · 2 條路徑
上排:origin 剛好支援 Cloudflare 猜的演算法,一次 round trip 打完握手
猜對一次握手就通;猜錯要多吃一次 HelloRetryRequest。HRR 從 52% 降到 3.7%,p90 省下 150 毫秒以上。
上面兩條路徑走的是同一個協定,差別只在 client 第一次猜中還是猜錯。猜中的那條在圖裡先停,猜錯的那條要繞回 HelloRetryRequest 那個記號再走一次——多出來的那一段距離,不是視覺誇張,是協定規定的額外一輪。押錯從來不是罕見狀況,而是「固定猜一個值」必然要付的稅:origin 支援哪個 key agreement,取決於它自己的 TLS stack 版本、OpenSSL 或其他 TLS 函式庫的組態,Cloudflare 完全看不到,只能賭。
X25519 會被選成早期的安全預設值不難理解——它是 TLS 1.3 裡支援度最廣的橢圓曲線 Diffie-Hellman 選項,超過 95% 的 origin 都支援它。問題是「超過 95%」不等於「全部」:Cloudflare 事後量測,這個固定猜測對大約三成的連線不是最佳選擇——不是連不上,是那個 origin 其實更想用別的演算法,只是 X25519 這個猜測還能將就用。Cloudflare 服務的 origin 涵蓋整條長尾,總有伺服器版本比較舊、或者對某些演算法支援不完整,固定押同一個值,代表這條長尾每次都要繞一圈。另一個做法理論上也行得通:乾脆同時在 ClientHello 裡塞多個 keyshare,讓 origin 自己選。但 keyshare 每多一個,ClientHello 就多一截,來回的頻寬成本跟著漲——尤其現在後量子的 keyshare 動輒破千 bytes,塞好幾個下去,第一個封包很快就爆掉。與其在每一條連線裡都背著好幾份猜測,不如把猜測搬到連線之外、一次量好,之後每條連線都只帶一份正確答案。
把賭注換成量測
Automatic Key Exchange 把這個賭注換成量測。針對每一個支援 TLS 1.3 的 origin,Cloudflare 在正式流量之外跑幾次輕量握手,每次只提供一種 key agreement 演算法——依序測 X25519、P-256、P-384、P-521、X25519MLKEM768,看哪些真的能打通。這段掃描完全走在 production 路徑之外:任何一條真實連線依賴新演算法之前,origin 跟中間的網路設備都已經先被驗證過一輪,不會讓第一個踩到問題的使用者變成白老鼠。這種「先在背景量、量完才套用到正式流量」的設計,代價是要多維護一整條掃描 pipeline——對超過一百萬個網域、每個測五種演算法,光是排程跟儲存結果就是額外的系統負擔。但換來的是正式流量永遠不會撞上第一次的未知:任何一次新設定生效之前,都已經先在低風險的探測連線上驗證過一遍。
一個網域底下常常掛著好幾個子網域,各自可能指到能力不同的 origin——api、cdn、靜態資源可能各自跑在不同的伺服器上,支援的演算法不一定一樣。Cloudflare 對每個子網域分開評估,再按它的實際流量量體加權:熱門的 API 端點跟一個幾乎沒人打的測試子網域,不會被平等對待,整體偏好反映的是真正的流量分布,冷清端點跟主力端點不會被算成一樣重要。量完支援清單之後,規則很硬:先選 post-quantum 混合演算法 X25519MLKEM768;origin 不支援,才退回到它能接受、速度最快的傳統演算法——X25519、P-256、P-384 或 P-521 之一。優先順序寫死在邏輯裡,不是「哪個比較快就選哪個」的單純比較。
掃描也可能什麼都測不出來——如果一個 origin 完全不支援 TLS 1.3,或五種 key agreement 都碰壁,這套機制自然沒有東西可以覆寫,Cloudflare 仍然照原本的邏輯處理,不會憑空生出一個新偏好。這也是為什麼「掃描」跟「決定偏好」要分成兩個獨立步驟:先誠實地把支援清單量出來,能不能決定出偏好、決定出哪一個,是下一層的邏輯,兩件事不會互相污染,掃描結果本身也不代表任何承諾。
| 演算法 | keyshare 大小 | 屬性 | 目前偏好比例 |
|---|---|---|---|
| X25519 | 32 bytes | 傳統橢圓曲線 | 64% |
| X25519MLKEM768 | 1,216 bytes | post-quantum 混合 | 33% |
| 其他傳統曲線(P-256 / P-384 / P-521) | 未揭露 | 傳統橢圓曲線 | 3% |
X25519MLKEM768 這個名字本身就是答案:它把傳統的 X25519 橢圓曲線運算,跟後量子的 ML-KEM-768 格基演算法綁在一起算一次 key agreement,兩邊都要成立,結果才算數。就算格基演算法未來被破解,還有 X25519 撐著;就算量子電腦真的出現,還有 ML-KEM-768 擋著——這也是為什麼優先順序把它放在最前面,即使它的 keyshare 比純傳統演算法重上好幾十倍。
這四個步驟——掃描、加權、排序、每天重掃——原文各自有一句話對應,滑鼠移到底下的詞上可以直接看英文出處:
決定偏好的四步,附原文出處
流程是背景掃描原文:「For each TLS 1.3 capable origin, we run a series of a few lightweight TLS handshakes, each offering exactly one key agreement group.」→ 按各子網域流量加權原文:「We evaluate each subdomain independently and weight the results by its actual traffic volume.」→ 套用嚴格優先順序原文:「post-quantum hybrids (X25519MLKEM768) first, falling back to the fastest classical algorithm accepted by the origin.」選出偏好,再靠每日重掃原文:「We rescan every origin daily.」確保設定跟得上 origin 的變化。
上線前先切一小片流量
決定出偏好以後,Cloudflare 不會直接對整個 origin 全量切換。新設定先套用到這個 origin 一小部分流量,系統同時監控失敗率與 HelloRetryRequest 率;一旦重試率超過這個 origin 自己的基線,系統就把設定改回去——機制跟 Automatic SSL/TLS 在加密模式升級出問題時的復原一樣,都是「先小範圍試、壞了就退」而不是「賭一次全上」。整個流程也不是量一次就定案:Cloudflare 每天對每個 origin 重新掃描一次。origin 背後的伺服器版本會升級、CDN 供應商會換、負載平衡器的 TLS 設定會調整,今天量到的偏好不保證明天還成立——每天重掃就是為了讓這份偏好清單跟著 origin 的實際狀態走,避免它變成一份過期的快照。
按按鈕模擬一次 canary 監控與復原 · 1 個按鈕
監控的兩個指標各自對應不同的失敗模式:失敗率抓的是握手直接連不上,HelloRetryRequest 率抓的是猜錯但還能挽回的情況。兩個都在一小部分流量上先看過,才會決定要不要把新偏好推到這個 origin 的全部流量——跟拿掉一個 feature flag 之前先看 canary 的錯誤率,是同一套工程直覺。
多出來的位元組去哪了
選擇本身不是沒有代價。X25519MLKEM768 的 keyshare 有 1,216 bytes,同樣角色的 X25519 只要 32 bytes——多出來的份量差了將近四十倍,會把整個 ClientHello 推過一個網路封包的邊界,握手一開始就得跨多個 TCP 封包送出去,而不是像過去那樣一個封包搞定。TCP 本身不保證「一個封包裝得下就一定用一個封包送」;按常見的路徑 MTU 推算,大多數路徑落在一千多 bytes,一個明顯超過這個量級的 ClientHello,很容易就得切成兩個以上的 TCP segment。跨 segment 本身在協定層面不是問題——TCP 本來就會處理分段與重組,麻煩出在中間攔截、檢查、甚至主動介入 TLS 交握的網路設備,不見得每一種都把「跨多個封包的 ClientHello」這種情境考慮進去。
多數網路設備處理跨封包的 TLS 交握沒問題,但不是全部:在先前一次掃描裡,約 0.34% 的已掃描 origin,在收到 post-quantum keyshare 時直接握手失敗。常見原因是舊款 middlebox、防火牆或 server buffer 丟掉了跨多個封包的 payload,或沒能正確重組被切成多段的 ClientHello——這些設備當初設計的時候,大概沒想過一個 TLS 握手會需要不只一個封包。
這個「跨封包」在 TCP 層級的實際樣子是:origin 端的 TCP stack 收到的不是一個完整封包,而是兩個以上的 TCP segment,得先把它們接回同一個 byte stream,才看得到完整的 ClientHello。多數作業系統的 TCP 實作處理這件事沒有問題——重組本來就是 TCP 協定的基本責任,client 端也一樣要處理。麻煩出在路徑中間:如果有一台設備會攔截、檢查甚至改寫 TLS 交握內容,例如做深度封包檢測的防火牆、某些負載平衡器,它得自己維護一份 TCP 重組邏輯才看得懂完整的 ClientHello;這份邏輯如果沒跟上「握手可能跨多個封包」這種情境,就可能直接把後面的封包丟掉,或者把它誤判成異常流量而擋下來。按機制推論,這類問題多半出在會解析、甚至主動介入 TLS 交握內容的設備上,純粹轉送封包的路由器通常不用面對這一層——不過原文的歸因本來就只到 middlebox、防火牆、server buffer 這個層級,用的是「often」而非絕對化的說法。
拖曳圓點比較兩種 keyshare 大小 · 32 bytes 與 1,216 bytes
0.34% 聽起來很小,但握手直接失敗跟多一次 round trip 是完全不同等級的問題——後者只是慢一點,前者是連不上。這也是為什麼掃描要走在正式流量之外:先確認 origin 跟中間的網路設備接得住比較大的 keyshare,再決定要不要把它設成偏好,而不是讓第一個踩到問題的真實使用者連線失敗。反過來說,這也提醒維運 origin 的工程師一件事:如果你的 origin 前面有年紀比較大的防火牆或負載平衡器,值得自己檢查一下它能不能正確重組跨封包的 ClientHello,不要等到被 Cloudflare 的掃描篩出來才發現。
具體要看什麼:先確認 origin 前面那層設備——防火牆、負載平衡器、反向代理——用的是哪個 TLS 函式庫、版本多舊;如果是自己維護 TLS 終止層,檢查它有沒有對 ClientHello 大小設過寫死的上限,或者對「TCP 三方交握完成後、還沒看到完整 TLS record 之前」這種中間狀態的緩衝區大小做過假設。這類假設在 keyshare 只有 32 bytes 的年代不會出事,遇到 1,216 bytes 的 X25519MLKEM768 才會現形。
這裡有個對稱的地方值得說:Cloudflare 不是先把 X25519MLKEM768 設成新的萬用預設值再處理例外,而是把「origin 撐不撐得住」這件事量出來之後,才決定要不要換。0.34% 這個數字本身,就是「先量再決定」比「先猜再處理例外」更划算的證據——如果一開始就無差別全部改用比較大的 keyshare,這 0.34% 會變成正式流量裡的握手失敗,不會停留在掃描階段就被篩掉的已知風險。
數字說話,也有量不到的地方
這套機制現在已經跑到相當規模。掃描 pipeline 累計為超過一百萬個網域指定過 key exchange 偏好,每天還有大約 9,000 個網域的偏好被改成非 X25519 的演算法——這個數字某種程度上就是「每天重掃」的具體體現,代表持續有 origin 的能力在變。在最初那批被納入的掃描群組裡,64% 的網域仍然留在傳統 X25519,對它們來說連線行為什麼都沒變;33% 的偏好變成 X25519MLKEM768;剩下 3% 選了 origin 更偏好的其他傳統曲線,像是 P-256 或 P-384。另外,在目前持續掃描的群組裡,post-quantum TLS 1.3 連線有 99.2% 現在一次 round trip 就能完成,不用再吃 HelloRetryRequest——跟固定猜測的年代比起來,這是完全不同量級的成功率。
origin 端支援 post-quantum key exchange 演算法的比例,從 2023 年的 0.5% 漲到現在的 12.8%——三年之內從幾乎沒有支援,變成超過一成的 origin 願意跟你談 X25519MLKEM768。累計效果也反映在整體流量上:在目前持續掃描的群組裡,post-quantum origin 流量從每天大約 250 億條連線漲到 450 億條,接近翻倍。這兩個數字都在成長:origin 端的支援度在漲,實際走 post-quantum key exchange 的連線量也在漲。
這個每天約 9,000 個網域的調整量,比一次性的「全網切換」更能反映系統實際在做的事——它不是一次大爆發的遷移,而是每天都有一批 origin 的能力被重新確認,偏好清單持續跟著現實調整,不靠一次性的公告或手動盤點。對維運 origin 的工程師來說,這組數字也有具體的檢查清單:你的伺服器有沒有被掃到、目前偏好是什麼;如果偏好是傳統曲線,要分清楚是你不支援 X25519MLKEM768,還是你的 middlebox 在收到比較大的 keyshare 時出了問題——後者可以自己驗證:用支援 X25519MLKEM768 的 client 起一條連線,看握手會不會卡在跨封包的那一步。
這一整套機制解決的是一個特定的威脅:現在就有人在錄下加密流量,賭自己將來能解開它——這個手法有個名字,叫 harvest-now, decrypt-later。Cloudflare 把讓網路變成量子安全的目標訂在 2029 年,部分業界專家估計 classical 加密演算法可能在那一年被攻破,這一天被稱為 Q-Day。但 key agreement 的後量子化只擋得住這一種威脅:它防止今天錄下的流量將來被解密,卻不解決有人拿到量子電腦之後偽造憑證、冒充你的 origin 的問題——那是另一種攻擊,key agreement 管不到。要擋住偽造憑證,需要 post-quantum authentication;今年稍早,Authenticated Origin Pulls 跟 Custom Origin Trust Store 開始支援 ML-DSA 憑證,而原文把「把 Automatic SSL/TLS 的掃描延伸到偵測 origin 的 post-quantum authentication 支援」列為計畫中的下一步——換句話說,這條線的自動化還停在計畫階段,不像 key agreement 已經是每天在跑的掃描。Automatic Key Exchange 測的對象也就是「支援 TLS 1.3 的 origin」——協定版本以外的猜測問題,不在這套機制的處理範圍內。
這條界線來自 TLS 握手本身分成兩件不同的事:key agreement 談的是「這條連線的對稱金鑰要怎麼算出來」,authentication 談的是「憑證上簽名的這個人,是不是真的擁有私鑰、真的是它自稱的那個 origin」。量子電腦如果真的出現,它能做的事分別對應這兩層:對 key agreement,它能事後解開今天錄下的加密流量;對 authentication,它能偽造一份看起來合法的憑證簽章,讓 client 誤信一個假冒的 origin。Automatic Key Exchange 處理的只是第一層——把今天的流量用後量子演算法保護起來,讓錄下來的封包在量子電腦出現之後依然是密文。第二層要擋,得先讓憑證本身也用後量子簽章演算法簽,這正是 ML-DSA 要做的事,跟 key agreement 是完全獨立的兩條技術路線,只是剛好都掛在同一次 TLS 握手裡。
What this enables:把「先猜再握手」換成「先量再決定」——原本統一押 X25519 時,這個猜測對大約三成連線不是最佳選擇,真正逼出 HelloRetryRequest、要多付一次來回的連線比例則長期停在五成上下;上線後這個比例降到個位數、p90 省下 150 毫秒以上,而且這個決定逐 origin、逐子網域都不一樣。原本連線層級的猜測,現在變成每天重新量一次的既有資料。只是憑證偽造那一半的量子威脅,key agreement 管不到,還得靠另一條線的 post-quantum authentication 才擋得住,而那條線目前走到 ML-DSA 憑證支援這一步,把掃描延伸過去偵測 origin 的支援情況,在原文裡還是計畫中的下一步。