:::info 日期:2021 年 09 月 15 日
作者:Filippo Valsorda
原文链接:https://go.dev/blog/tls-cipher-suites :::

Go 标准库提供了 crypto/tls,它是传输层安全 (TLS) 的强大实现,它是 Internet 上最重要的安全协议,也是 HTTPS 的基本组件。 在 Go 1.17 中,我们通过自动化密码套件的优先级顺序使其配置更简单、更安全、更高效。

密码套件如何工作

密码套件可以追溯到 TLS 的前身安全套接字层 (SSL),后者将其称为“密码种类”。 它们是像 TLS_RSA_WITH_AES_256_CBC_SHA 和 TLS_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。 当 Lucky13 明确表示,由于其向后的 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 密码套件都是安全的,因此应用程序开发人员和服务器运营商根本不必担心它们。 事实上,像 BoringSSL 和 Go 的 crypto/tls 这样的一些 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 密钥 交换。 (后者只是使用证书的公钥对连接的秘密进行加密,如果将来证书被泄露,就可以解密。)

  1. AEAD 模式优于 CBC 进行加密。

即使我们确实为 Lucky13 实施了部分对策(我在 2015 年对 Go 标准库的第一次贡献!),CBC 套件是正确的噩梦,因此所有其他更重要的事情都相同,我们选择 AES-GCM 和 取而代之的是 ChaCha20Poly1305。

  1. 3DES、CBC-SHA256 和 RC4 仅在没有其他可用的情况下按优先顺序使用。

3DES 具有 64 位块,这使得它在足够流量的情况下从根本上容易受到生日攻击。 3DES 列在 InsecureCipherSuites 下,但为了兼容性默认启用。 (控制偏好顺序的另一个好处是,我们可以在默认情况下保持不太安全的密码套件启用,而不必担心应用程序或客户端选择它们,除非作为最后的手段。这是安全的,因为没有依赖可用性的降级攻击 使用较弱的密码套件来攻击支持更好替代方案的对等方。)
CBC 密码套件容易受到 Lucky13 式侧信道攻击,我们仅部分实施了上面讨论的针对 SHA-1 哈希的复杂对策,而不是针对 SHA-256。 CBC-SHA1 套件具有兼容性值,证明额外的复杂性是合理的,而 CBC-SHA256 套件则没有,因此默认情况下禁用它们。
RC4 具有可实际利用的偏差,可以在没有侧信道的情况下实现明文恢复。 没有比这更糟糕的了,因此默认情况下禁用 RC4。

  1. ChaCha20Poly1305 优于 AES-GCM 进行加密,除非双方都对后者有硬件支持。

正如我们上面所讨论的,如果没有硬件支持,AES-GCM 很难有效和安全地实现。 如果我们检测到没有本地硬件支持或(在服务器上)客户端没有优先考虑 AES-GCM,我们会选择 ChaCha20Poly1305。

  1. AES-128 优于 AES-256 进行加密。

AES-256 拥有比 AES-128 更大的密钥,这通常是好的,但它也执行更多轮核心加密功能,使其更慢。 (AES-256 中的额外轮次与密钥大小的变化无关;它们试图为密码分析提供更大的余量。)较大的密钥仅在多用户和后量子设置中有用,这与 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 库将采用类似的更改,从而使精细的密码套件配置成为过去。