Go 博客
crypto/tls 中的自动密码套件排序
Go 标准库提供了 crypto/tls
,这是传输层安全 (TLS) 的一个健壮实现,TLS 是互联网上最重要的安全协议,也是 HTTPS 的基本组成部分。在 Go 1.17 中,我们通过自动执行密码套件的优先级顺序,使其配置变得更轻松、更安全、更高效。
密码套件的工作原理
密码套件可以追溯到 TLS 的前身安全套接字层 (SSL),它 将其称为“密码种类”。它们是看起来很吓人的标识符,比如 TLS_RSA_WITH_AES_256_CBC_SHA
和 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
,它们详细说明了用于交换密钥、验证证书和加密 TLS 连接中记录的算法。
密码套件在 TLS 握手期间进行协商:客户端在其第一条消息(客户端问候)中发送其支持的密码套件列表,服务器从该列表中选择一个,并将选择结果传达给客户端。客户端按自己的优先级顺序发送支持的密码套件列表,服务器可以自由地从列表中选择任何套件。最常见的是,服务器会根据其配置,选择客户端或服务器优先级顺序中的第一个互相同意的密码套件。
密码套件实际上只是许多协商参数之一,支持的曲线/组和签名算法也通过自己的扩展进行协商,但它们是最复杂和最著名的参数,也是多年来开发人员和管理员被训练用来表达意见的唯一参数。
在 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 排序逻辑 遵循以下规则
-
ECDHE 比静态 RSA 密钥交换更可取。
密码套件最重要的属性是启用前向保密。我们没有实现“经典”有限域 Diffie-Hellman,因为它很复杂、速度慢、更弱,并且在 TLS 1.0–1.2 中细微地被破坏,这意味着优先考虑椭圆曲线 Diffie-Hellman 密钥交换而不是传统的静态 RSA 密钥交换。(后者只是使用证书的公钥加密连接的密钥,如果将来证书被泄露,就可以解密。)
-
AEAD 模式比 CBC 更适合用于加密。
即使我们确实对 Lucky13 实施了部分对策 (我 2015 年对 Go 标准库的第一个贡献!),CBC 套件 是一场噩梦,很难做到正确,所以如果所有其他更重要的内容都相等,我们选择 AES-GCM 和 ChaCha20Poly1305。
-
3DES、CBC-SHA256 和 RC4 只有在没有其他可用选项的情况下才会使用,按该优先级顺序。
3DES 具有 64 位块,这使得它在流量足够的情况下,本质上容易受到 生日攻击。3DES 列在
InsecureCipherSuites
下,但默认情况下它处于启用状态以确保兼容性。(控制优先级顺序的另一个好处是,我们可以负担得起将安全性较低的密码套件默认情况下保持启用状态,而不用担心应用程序或客户端会选择它们,除非作为最后手段。这是安全的,因为没有依赖于弱密码套件可用性的降级攻击来攻击支持更好替代方案的对等方。)CBC 密码套件容易受到 Lucky13 风格的侧信道攻击,我们只对上述针对 SHA-1 哈希讨论的 复杂 对策进行了部分实施,而不是针对 SHA-256。CBC-SHA1 套件具有兼容性价值,证明了额外的复杂性,而 CBC-SHA256 套件没有,因此它们默认情况下处于禁用状态。
RC4 具有 实际上可利用的偏差,这会导致在没有侧信道的情况下恢复明文。它不可能比这更糟糕了,所以 RC4 默认情况下处于禁用状态。
-
ChaCha20Poly1305 比 AES-GCM 更适合用于加密,除非双方都具有对后者的硬件支持。
如上所述,AES-GCM 很难在没有硬件支持的情况下高效且安全地实现。如果我们检测到没有本地硬件支持,或者(在服务器上)客户端没有优先考虑 AES-GCM,我们选择 ChaCha20Poly1305。
-
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 体验
博客索引