Go 部落格

crypto/tls 中自動密碼組合排序

Filippo Valsorda
2021 年 9 月 15 日

Go 標準函式庫提供 crypto/tls,這是一個強健的傳輸層安全性 (TLS) 實作,這是網際網路上最重要的安全協定,也是 HTTPS 的基本元件。在 Go 1.17 中,我們透過自動化密碼組合的優先順序來,讓它的設定更簡單、更安全、更有效率。

密碼組合如何運作

密碼組合可以追溯到 TLS 的前身安全套接字層 (SSL),其中 將其稱為「密碼種類」。它們是被視為 TLS_RSA_WITH_AES_256_CBC_SHATLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 這類嚇人的識別碼,它們說明了在 TLS 連線中用於交換金鑰、驗證憑證和加密記錄的演算法。

密碼組合會在 TLS 握手過程中協商:用戶端在第一則訊息(用戶端 Hello)中傳送它支援的密碼組合清單,而伺服器會從清單中挑選一個,並告知用戶端它選擇的項目。用戶端會傳送以它自己的偏好順序所列出的支援密碼組合清單,而伺服器可以自由從中選擇。最常見的情況是,伺服器會依據其設定,從用戶端偏好順序或伺服器偏好順序中選擇雙方都支援的第一個密碼組合。

密碼組只是眾多協調參數中的一種,支援的曲線/群組和簽章演算法是另外透過專屬延伸協調,但密碼組最為複雜和著名,也是唯一經過多年訓練讓開發人員和管理員能對它發表意見。

在 TLS 1.0 到 1.2 中,所有這些參數都會在複雜的相互依賴情況下交互作用:例如,支援的憑證取決於支援的簽章演算法、支援的曲線和支援的密碼組。在 TLS 1.3 中,這一切大為簡化:密碼組只會指定對稱式加密演算法,而支援的曲線/群組會控制金鑰交換,而且支援的簽章演算法則適用於憑證。

對開發人員放棄的複雜選擇

大部分 HTTPS 和 TLS 伺服器都會委派密碼組和偏好順序的選擇給伺服器操作員或應用程式開發人員。這是一個複雜的選擇,需要具備最新的專業知識才能執行,原因如下。

有些舊的密碼組有非安全的組成元件,有些需要極為小心和複雜的執行才能安全,有些只有在用戶端套用特定緩解措施或擁有特定硬體時才會安全。除了個別組成元件的安全性之外,不同的密碼組還會為整個連線提供極為不同的安全性,例如,沒有 ECDHE 或 DHE 的密碼組不會提供轉發式保密,也就是連線無法使用憑證金鑰進行追溯或被動解密的特性。最後,支援的密碼組選擇會影響相容性和效能,而且如果沒有對生態系統有最新的了解就進行變更,可能會導致與舊版用戶端的連線中斷、增加伺服器消耗的資源,或耗盡行動用戶端的電池電量。

此選擇非常深奧而且棘手,因此有專屬的工具能協助操作員,例如出色的 Mozilla SSL 組態產生器

為何會變成這樣,我們又是如何走到這一步的?

要開始時,用來破解的東西,個別的密碼學組件經常斷掉。在 2011 年,當 BEAST 攻擊破壞 CBC 加密套件的方式,只有客戶端能夠減輕攻擊,伺服器則轉為偏好不受影響的 RC4。在 2013 年,當清楚 RC4 已被破解時,伺服器便回到 CBC。當 Lucky Thirteen 明確表示,由於 CBC 加密套件反向 MAC 然後加密的設計,實作極端困難……嗯,桌上沒有任何東西,所以實作必須 小心翼翼地跨越難關才能實作 CBC,並且 多年來不斷失敗。可設定的加密套件和 密碼學的靈活應變用來提供某種保證,當某個組件故障時,它可以被隨時替換。

現代密碼學有顯著的不同。通訊協定仍然可能會時不時中斷,但是幾乎不曾是特定的抽象組件故障造成。從 2008 年的TLS 1.2 開始推出的 AEAD 為基礎的加密套件,沒有一個被破解。現在的密碼學靈活應變是一種負擔:它引進了複雜性,可能會導致弱點或降級,並且只為了效能和相容性理由而需要。

補丁的作法也截然不同。今天我們了解到,立即套用公開弱點的軟體補丁,是安全軟體部署的基石,但十年前這並不是標準作法。更改設定被視為對應有弱點的加密套件,一個更快速的選項,所以管理員透過設定,對它們完全負責。現在我們有相反的問題:有些伺服器完全修補且更新,但仍表現得怪異、次最佳或不安全,因為它們的設定好幾年沒有變動。

最後,我們了解到,伺服器傾向比客戶端更新得慢,因此,不那麼可靠來判斷最佳的加密套件選項。然而,由伺服器決定加密套件的最終選擇,所以預設值變成讓伺服器讓步於客戶端的喜好順序,而不是有強烈的堅持。這仍然部份為真:瀏覽器設法讓自動更新發生,並且比一般伺服器更為最新。一方面,許多傳統裝置現在已經沒有支援,並停滯在舊的 TLS 客戶端設定,這通常讓最新的伺服器具備比某些客戶端更好的選擇性。

無論我們如何走到這一步,讓應用程式開發人員和伺服器操作員成為密鑰組選擇上的專家,並隨時關注最新發展以使組態保持最新,這是加密工程的失敗。如果他們正在部署我們的安全程式碼修補程式,這就足夠了。

Mozilla SSL 組態產生器十分出色,但它不應存在。

這有變得更好嗎?

對於過去幾年事物的發展趨勢,有好的消息,也有壞的消息。壞消息是訂購變得更加微妙,因為有一些密鑰組具有等效的安全屬性。這樣一個組內最佳的選擇取決於可用的硬體,而且難以在組態檔中表達。在其他系統中,原本作為密鑰組的一個簡單清單,現在取決於更複雜的語法或其他標誌,例如SSL_OP_PRIORITIZE_CHACHA

好消息是,TLS 1.3 大幅簡化了密鑰組,而且從 TLS 1.0–1.2 使用不重疊的組。所有 TLS 1.3 密鑰組都是安全的,因此應用程式開發人員和伺服器操作員完全不必擔心它們。確實,某些 TLS 函式庫(例如 BoringSSL 和 Go 的crypto/tls)根本不允許設定它們。

Go 的 crypto/tls 和密鑰組

Go 確實允許在 TLS 1.0–1.2 中設定密鑰組。應用程式一直都能夠透過Config.CipherSuites設定已啟用的密鑰組和偏好順序。伺服器預設優先考慮客戶端的偏好順序,除非已設定Config.PreferServerCipherSuites

當我們在 Go 1.12 中實作 TLS 1.3 時,我們並未將 TLS 1.3 密鑰組設定為可設定,因為它們是與 TLS 1.0–1.2 密鑰組不重疊的組,最重要的是它們都是安全的,因此無需委派選擇權給應用程式。Config.PreferServerCipherSuites 仍控制使用哪一方的偏好順序,而本機端偏好取決於現有的硬體。

在 Go 1.14 中,我們揭露受支援的密鑰組,但我們明確選擇依據中立順序傳回(按其 ID 排序),因此我們最終不會受到用靜態排序順序表述優先順序邏輯的約束。

在 Go 1.16 中,我們開始積極針對伺服器偏好使用 ChaCha20Poly1305 加密套組而非 AES-GCM,我們會在偵測到客戶端或伺服器缺乏 AES-GCM 硬體支援之後開始執行。這是因為 AES-GCM 很難在沒有專用硬體支援(例如 AES-NI 和 CLMUL 指令集)的情況下有效率且安全的執行。

最近發布的 Go 1.17 針對所有 Go 使用者處理加密套組偏好序。雖然 Config.CipherSuites 仍會控制哪些 TLS 1.0-1.2 加密套組是啟用的,但不會用於排序,而且 Config.PreferServerCipherSuites 目前已被忽略。取而代之的是,crypto/tls 做出所有排序的決定,這些決定是根據可用的加密套組、本機硬體和推論的遠端硬體功能所做出的。

目前的 TLS 1.0-1.2 排序邏輯遵循以下規則:

  1. ECDHE 優先於靜態 RSA 金鑰交換。

    加密套組中最重要的屬性是啟動前進式保密性。我們沒有執行「傳統」有限域 Diffie-Hellman,因為它複雜、較慢、較弱,而且在 TLS 1.0-1.2 中有 微妙的缺陷,這表示橢圓曲線 Diffie-Hellman 金鑰交換優先於傳統靜態 RSA 金鑰交換。(後者僅使用憑證公開金鑰加密連線的秘密,如果憑證在未來遭到入侵,這會使其能夠進行解密。)

  2. 針對加密,AEAD 模式優先於 CBC。

    即使我們為 Lucky13 執行部分反制措施(我在 2015 年對 Go 標準函式庫做出的第一個貢獻!),但 CBC 套組仍然 難以正確執行,因此所有其他更重要的因素與其相當,我們會選擇 AES-GCM 和 ChaCha20Poly1305。

  3. 僅在其他資源都不可用時才使用 3DES、CBC-SHA256 和 RC4,其優先序依序同前述。

    3DES 有 64 位元區塊,這會讓它在足夠的流量下對 生日攻擊有根本上的弱點。3DES 在 InsecureCipherSuites 中,但它會在預設值中啟用以確保相容性。(控制優先順序的額外好處是我們可以負擔在預設值中保留較不安全的加密套組,無須擔心應用程式或客戶端會在不得已的情況下選擇這些套組。這樣做是安全的,因為沒有任何降級攻擊會仰賴使用較弱的加密套組來攻擊支援較佳替代方案的對象。)

    CBC 加密套件容易遭受 Lucky13 類型的側通道攻擊,我們僅部份採用上述針對 SHA-1雜湊(而非 SHA-256)討論的複雜對策。CBC-SHA1 套件具有相容性價值,證明額外的複雜性是合理的,而 CBC-SHA256 套件則不然,因此預設會停用它們。

    RC4 具有實際可利用的偏差,可能導致在沒有側通道的情況下恢復明文。情況不會比這更糟了,所以預設會停用 RC4。

  4. 在加密時,除非雙方都有硬體支援,否則 ChaCha20Poly1305 優於 AES-GCM。

    正如我們在上面討論的,在沒有硬體支援的情況下,AES-GCM 難以有效率且安全的實作。如果我們偵測到沒有本機硬體支援,或(在伺服器上)偵測到用戶端沒有優先採用 AES-GCM,我們就會選擇 ChaCha20Poly1305。

  5. 在加密時,AES-128 優於 AES-256。

    AES-256 的金鑰比 AES-128 大,這通常是好的,但它也執行更多輪的核心加密函數,因此速度較慢。(AES-256 中的額外輪與金鑰大小變更無關;它們試圖提供更廣泛的邊際,以防備密碼分析。)較大的金鑰僅在多用戶和後量子設定中才有用,這與 TLS 無關,因為 TLS 會產生足夠隨機的 IV,並且不支援後量子金鑰交換。由於較大的金鑰沒有任何好處,因此為了速度,我們偏好 AES-128。

TLS 1.3 的排序邏輯只需要最後兩條規則,因為 TLS 1.3 刪除了前三條規則防範的有問題演算法。

常見問題集

如果加密套件最後證明被破解了怎麼辦?就像任何其他漏洞一樣,它會在所有受支持的 Go 版本的安全性更新中修復。所有應用程式都需要準備好套用安全性修補程式來安全操作。回顧過去,被破解的加密套件越來越少見。

為什麼保留啟用 TLS 1.0–1.2 加密套件的可設定性?在選擇要啟用哪些加密套件時,在基準安全性與舊版相容性之間存在著有意義的權衡,這是一個我們無法自行做出的選擇,除非我們切掉生態系統中無法接受的部分,或降低現代使用者的安全性保證。

為什麼不讓 TLS 1.3 加密套件可設定?相反地,TLS 1.3 沒有需要權衡之處,因為其所有加密套件都能提供強大的安全性。這讓我們可以讓它們全部啟用,並根據連線的具體情況,在不需要開發人員介入的情況下,選用最快的加密套件。

重點整理

從 Go 1.17 開始,crypto/tls 會接管可用密碼組套件的選擇順序。使用定時更新的 Go 版本,此方法比讓潛在過時的用戶端選擇順序更安全,讓我們最佳化效能,並為 Go 開發人員減輕大量複雜度。

這與我們能決定時,盡量做出加密決策,而非將這些決策委任給開發人員的普遍理念一致,也符合我們的加密原則。希望其他 TLS 程式庫會採取類似的變更,讓精細的密碼組套件設定成為過去式。

下一篇文章:行為準則更新
上一篇文章:整理 Go 網路體驗
部落格索引