Go 部落格

crypto/tls 中的自動密碼套件排序

Filippo Valsorda
2021 年 9 月 15 日

Go 標準庫提供了 crypto/tls,這是一個健壯的傳輸層安全 (TLS) 實現,TLS 是網際網路上最重要的安全協議,也是 HTTPS 的基礎組成部分。在 Go 1.17 中,我們透過自動化密碼套件的優先順序順序,使其配置更加容易、更安全、更高效。

密碼套件的工作原理

密碼套件的歷史可以追溯到 TLS 的前身 Secure Socket Layer (SSL),它稱其為“cipher kinds”。它們是那些看起來令人望而生畏的識別符號,例如 TLS_RSA_WITH_AES_256_CBC_SHATLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,它們詳細說明了在 TLS 連線中用於金鑰交換、證書認證和記錄加密的演算法。

密碼套件是在 TLS 握手期間進行協商的:客戶端在其第一個訊息 Client Hello 中傳送它支援的密碼套件列表,伺服器從該列表中選擇一個,並將其選擇告知客戶端。客戶端按照自己的偏好順序傳送支援的密碼套件列表,伺服器可以根據其配置自由地從中進行選擇。最常見的情況是,伺服器會根據客戶端偏好順序或伺服器偏好順序,選擇列表中第一個雙方都支援的密碼套件。

密碼套件實際上只是眾多協商引數中的一個——支援的曲線/群組和簽名演算法透過各自的擴充套件進行額外協商——但它們是最複雜、最出名的引數,也是多年來開發者和管理員被訓練得對此有意見的唯一引數。

在 TLS 1.0–1.2 中,所有這些引數在一個複雜的相互依賴網路中互動:例如,支援的證書取決於支援的簽名演算法、支援的曲線和支援的密碼套件。在 TLS 1.3 中,這一切都得到了極大的簡化:密碼套件僅指定對稱加密演算法,而支援的曲線/群組控制金鑰交換,支援的簽名演算法應用於證書。

一項複雜的選擇被推給了開發者

大多數 HTTPS 和 TLS 伺服器將密碼套件的選擇和偏好順序委託給伺服器操作員或應用程式開發者。這是一個複雜的選擇,由於多種原因需要最新的專業知識。

一些舊的密碼套件包含不安全的元件,一些需要極其仔細和複雜的實現才能安全,還有一些只有在客戶端應用某些緩解措施或甚至擁有特定硬體時才安全。除了單個元件的安全性外,不同的密碼套件可以為整個連線提供截然不同的安全屬性,因為不帶 ECDHE 或 DHE 的密碼套件不提供前向保密性——即連線不能用未來洩露的證書金鑰進行追溯或被動解密。最後,支援的密碼套件的選擇會影響相容性和效能,在不瞭解生態系統最新情況的情況下進行更改可能導致舊客戶端無法連線、增加伺服器消耗的資源或耗盡移動客戶端的電量。

這種選擇如此深奧和精妙,以至於有專門的工具來指導操作員,例如出色的 Mozilla SSL 配置生成器

我們是如何走到這一步的,為什麼會這樣?

首先,單個密碼元件過去更容易出現故障。2011 年,當 BEAST 攻擊破壞了 CBC 密碼套件,使得只有客戶端能夠緩解攻擊時,伺服器轉向優先使用不受影響的 RC4。2013 年,當 RC4 被發現已失效時,伺服器又回到了 CBC。當 Lucky Thirteen 表明由於其逆向 MAC-then-encrypt 設計,實現 CBC 密碼套件極其困難時……嗯,當時沒有其他替代方案,所以實現不得不小心翼翼地繞過困難來實現 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 中,當我們檢測到客戶端或伺服器缺乏對 AES-GCM 的硬體支援時,我們開始主動在伺服器端優先使用 ChaCha20Poly1305 密碼套件而非 AES-GCM。這是因為沒有專門的硬體支援(例如 AES-NI 和 CLMUL 指令集),AES-GCM 很難高效且安全地實現。

最近釋出的 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 Web 體驗
部落格索引