Go 部落格

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

Filippo Valsorda
2021年9月15日

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

密碼套件的工作原理

密碼套件可以追溯到 TLS 的前身安全套接字層 (SSL),它 稱它們為“密碼種類”。它們是諸如 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 中,我們開始在伺服器上主動 優先使用 ChaCha20Poly1305 密碼套件而不是 AES-GCM,當我們檢測到客戶端或伺服器缺少 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 體驗
部落格索引