真实世界的密码学(二)(3)

本文涉及的产品
.cn 域名,1个 12个月
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 真实世界的密码学(二)

真实世界的密码学(二)(2)https://developer.aliyun.com/article/1508666

8.6 使用 HKDF 进行密钥派生

PRNG 并不是唯一可以用来从一个秘密派生更多秘密(换句话说,拉伸密钥)的构造。从一个秘密派生多个秘密实际上是密码学中如此频繁的模式,以至于这个概念有自己的名字:密钥派生。所以让我们看看这是什么意思。

密钥派生函数(KDF)在许多方面类似于 PRNG,除了以下列表中指出的一些微妙之处。这些差异在图 8.6 中总结。

  • KDF 并不一定需要一个均匀随机的秘密(只要有足够的熵)。 这使得 KDF 可以从密钥交换输出中派生秘密,产生高熵但有偏差结果的密钥(参见第五章)。结果的秘密反过来是均匀随机的,因此您可以在需要均匀随机密钥的构造中使用这些密钥。
  • KDF 通常用于需要参与者多次重新派生相同密钥的协议中。 在这个意义上,KDF 被期望是确定性的,而 PRNG 有时通过频繁地使用更多熵重新种子化自身来提供向后保密性。
  • KDF 通常不被设计用来产生大量随机数。相反,通常用于派生有限数量的密钥。


图 8.6 密钥派生函数(KDF)和伪随机数发生器(PRNG)是两个类似的构造。主要区别在于 KDF 不期望输入是完全均匀随机的秘密(只要具有足够的熵)并且通常不用于生成太多的输出。

最流行的 KDF 是基于 HMAC 的密钥派生函数(HKDF)。您在第三章中学到了 HMAC(基于哈希函数的 MAC)。HKDF 是建立在 HMAC 之上的轻量级 KDF,并在 RFC 5869 中定义。因此,人们可以使用不同的哈希函数来使用 HKDF,尽管它最常用于 SHA-2。HKDF 被指定为两个不同的函数:

  • HKDF-Extract—从一个秘密输入中移除偏差,产生一个均匀随机的秘密。
  • HKDF-Expand—产生任意长度和均匀随机的输出。与伪随机数发生器一样,它期望一个均匀随机的秘密作为输入,因此通常在 HKDF-Extract 之后运行。


图 8.7 HKDF-Expand 是由 HKDF 指定的第二个函数。它接受一个可选的info字节串和一个需要均匀随机的输入秘密。使用相同的输入秘密与不同的info字节串会产生不同的输出。输出的长度由length参数控制。

首先让我们看一下 HKDF-Extract,我在图 8.7 中进行了说明。从技术上讲,哈希函数足以使输入字节串的随机性均匀化(请记住,哈希函数的输出应该是不可区分于随机的),但是 HKDF 更进一步,接受一个额外的输入:。对于密码哈希,盐区分了同一协议中对 HKDF-Extract 的不同用法。虽然这个盐是可选的,如果不使用,则设置为全零字节串,但建议您使用它。此外,HKDF 不期望盐是一个秘密;它可以被所有人,包括对手,知道。HKDF-Extract 不使用哈希函数,而是使用一个 MAC(具体来说是 HMAC),巧合的是,它有一个接受两个参数的接口。

现在让我们看看 HKDF-Expand,我在图 8.8 中进行了说明。如果您的输入秘密已经是均匀随机的,您可以跳过 HKDF-Extract 并使用 HKDF-Expand。


图 8.8 HKDF-Extract 是由 HKDF 指定的第一个函数。它接受一个可选的盐,该盐用作 HMAC 中的密钥,以及可能不是均匀随机的输入秘密。使用相同的输入秘密与不同的盐会产生不同的输出。

与 HKDF-Extract 类似,HKDF-Expand 还接受一个名为info的附加和可选的自定义参数。虽然盐旨在在 HKDF(或 HKDF-Extract)的相同协议中的调用之间提供一些域分隔,但info旨在用于区分您的 HKDF(或 HKDF-Expand)版本与其他协议。您还可以指定您需要多少输出,但请记住,HKDF 不是 PRNG,并且不设计为导出大量的密钥。HKDF 受您使用的哈希函数的大小限制;更准确地说,如果您使用 SHA-512(产生 512 位输出)与 HKDF,则对于给定的密钥和一个info字节字符串,您限于 512 × 255 位 = 16,320 字节的输出。

多次使用相同的参数调用 HKDF 或 HKDF-Expand,除了输出长度之外,会产生相同的输出截断为不同长度的请求(请参阅图 8.9)。此属性称为相关输出,在罕见情况下,可能会令协议设计人员感到惊讶。记住这一点是很好的。


图 8.9 HKDF 和 HKDF-Expand 提供相关输出,这意味着使用不同输出长度调用该函数会将相同结果截断为所请求的长度。

大多数密码库将 HKDF-Extract 和 HKDF-Expand 组合成单个调用,如图 8.10 所示。通常,在使用 HKDF 之前,请务必阅读手册(在本例中为 RFC 5869)。


图 8.10 HKDF 通常以单个函数调用的形式实现,该函数同时结合了 HKDF-Extract(从输入密钥中提取均匀随机性)和 HKDF-Expand(生成任意长度的输出)。

HKDF 并不是从一个秘密中导出多个秘密的唯一方法。更为朴素的方法是使用哈希函数。由于哈希函数不期望均匀随机的输入并产生均匀随机的输出,因此它们适合这项任务。然而,哈希函数并不完美,因为它们的接口不考虑域分隔(没有自定义字符串参数),并且它们的输出长度是固定的。最佳做法是在可以使用 KDF 时避免使用哈希函数。尽管如此,一些被广泛接受的算法确实使用哈希函数来实现这一目的。例如,您在第七章学到的 Ed25519 签名方案就是使用 SHA-512 对 256 位密钥进行哈希以产生两个 256 位密钥。

这些函数真的会产生随机输出吗?

理论上,哈希函数的属性并不代表输出是均匀随机的;这些属性仅仅规定了哈希函数应该具备抗碰撞、抗原像和抗第二原像的特性。然而,在现实世界中,我们到处使用哈希函数来实现随机预言机(正如你在第二章学到的那样),因此,我们假设它们的输出是均匀随机的。这与 MAC(在理论上不应产生均匀随机输出,不像第三章中介绍的 PRF 那样)也是一样的,但在实践中,大多数情况下确实如此。这就是为什么 HMAC 被用于 HKDF 的原因。在本书的其余部分,我会假设流行的哈希函数(如 SHA-2 和 SHA-3)和流行的 MAC(如 HMAC 和 KMAC)产生随机输出。

我们在第二章看到的扩展输出函数(XOFs)也可以用作 KDF!记住,XOF

  • 不期望均匀随机输入
  • 可以产生一个实际上无限大的均匀随机输出

此外,KMAC(第三章介绍的 MAC)没有我之前提到的相关输出问题。实际上,KMAC 的长度参数随机化了算法的输出,有效地起到了额外的定制字符串的作用。

最后,存在低熵输入的边缘情况。例如,考虑密码,相对于 128 位密钥,密码可能相对容易猜测。用于哈希密码的基于密码的密钥派生函数(在第二章中介绍)也可以用于派生密钥。

8.7 管理密钥和秘密

好了,一切顺利,我们知道如何生成加密随机数,也知道如何在不同类型的情况下派生秘密。但我们还没有摆脱困境。

现在我们正在使用所有这些加密算法,我们最终需要维护大量的秘密密钥。我们如何存储这些密钥?我们如何防止这些极度敏感的秘密被泄露?如果一个密钥被泄露了,我们该怎么办?这个问题通常被称为密钥管理

加密是将一系列问题转化为密钥管理问题的工具

—Lea Kissner(2019,mng.bz/eMrJ

虽然许多系统选择将密钥留在使用它们的应用程序附近,但这并不意味着应用程序在出现问题时没有任何补救措施。为了应对可能发生的违规行为或泄漏密钥的漏洞,大多数严肃的应用程序采用了两种深度防御技术:

  • 密钥轮换— 通过为密钥(通常是公钥)关联到期日期,并定期用新密钥替换你的密钥,你可以从可能的妥协中“恢复”。到期日期和轮换频率越短,你就可以更快地替换可能已知给攻击者的密钥。
  • 密钥吊销 — 密钥轮换并不总是足够的,当你听说密钥已被泄露时,你可能希望立即取消一个密钥。因此,一些系统允许你在使用密钥之前询问该密钥是否已被吊销。(你将在下一章关于安全传输中了解更多信息。)

自动化通常是使用这些技术成功的不可或缺的部分,因为一个运转良好的机器在危机时更容易正常工作。此外,你还可以将特定角色与密钥关联起来,以限制妥协的后果。例如,你可以在某个虚构的应用程序中区分两个公钥,公钥 1 仅用于签署交易,而公钥 2 仅用于进行密钥交换。这样,与公钥 2 关联的私钥的妥协不会影响交易签署。

如果不想让密钥留在设备存储介质上,硬件解决方案可以防止密钥被提取。你将在第十三章关于硬件密码学中了解更多信息。

最后,应用程序有许多方式可以委托密钥管理。这在提供密钥存储密钥链的移动操作系统中经常发生,这些系统将为你保留密钥,甚至执行加密操作!

存在一些云应用程序可以访问云密钥管理服务。这些服务允许应用程序委托创建秘密密钥和加密操作,并避免考虑攻击这些方式的许多方法。尽管如此,与硬件解决方案一样,如果应用程序受到妥协,它仍然可以向委托服务发出任何类型的请求。

注意:并没有银弹,你仍应考虑如何检测和应对妥协。

密钥管理是一个棘手的问题,超出了本书的范围,所以我不会过多讨论这个话题。在下一节中,我将介绍试图避免密钥管理问题的加密技术。

8.8 使用阈值密码学去分散信任

密钥管理是一个广阔的研究领域,投资其中可能会令人烦恼,因为用户并不总是有资源来实施最佳实践,也没有空间中可用的工具。幸运的是,密码学为那些想减轻密钥管理负担的人提供了一些东西。我将首先讨论的是秘密共享(或秘密分割)。秘密分割允许你将一个秘密分成多个部分,可以在一组参与者之间共享。在这里,秘密可以是任何你想要的东西:对称密钥、签名私钥等等。

通常,一个称为经销商的人生成秘密,然后将其拆分并将不同的部分分享给所有参与者,然后删除秘密。最著名的秘密分享方案由 Adi Shamir(RSA 的共同发明人之一)发明,称为Shamir 的秘密分享(SSS)。我在图 8.11 中说明了这个过程。


图 8.11 给定一个密钥和一些份额* n ,Shamir 的秘密分享方案创建与原始密钥大小相同的 n *部分密钥。

当时机成熟并且需要秘密来执行一些加密操作(加密、签名等)时,所有股东都需要将他们的私密份额归还给负责重建原始秘密的经销商。这种方案防止了攻击者针对单个用户,因为每个份额本身都是无用的,而是迫使攻击者在利用密钥之前先妥协所有参与者!我在图 8.12 中说明了这一点。


图 8.12 Shamir 的秘密分享方案用于分割* n 部分密钥以重构原始密钥需要所有 n *部分密钥。

该方案算法背后的数学实际上并不难理解!所以让我在这里花几段文字给你一个简化的想法。

想象一条二维空间中的随机直线,假设其方程为—* y * = * ax * + * b *—是秘密。通过让两个参与者持有线上的两个随机点,他们可以合作恢复线方程。该方案推广到任何次数的多项式,因此可以用于将秘密分割成任意数量的份额。这在图 8.13 中有所说明。


图 8.13 Shamir 的秘密分享方案背后的想法是将定义曲线的多项式视为秘密,将曲线上的随机点视为部分密钥。要恢复定义曲线的次数为* n 的多项式,需要知道曲线上的 n * + 1 个点。例如,* f x )= 3 * x * + 5 是 1 次,因此您需要任何两个点( x f x ))来恢复多项式,而 f x *)= 5 * x * ² + 2 * x * + 3 是 2 次,因此您需要任何三个点来恢复多项式。

秘密分割是一种常用的技术,因其简单性而被广泛采用。然而,为了有用,密钥份额必须收集到一个地方,以便在每次用于加密操作时重新创建密钥。这会创建一个窗口期,其中秘密变得容易受到盗窃或意外泄漏的机会,有效地使我们回到了一个单点故障模型。为了避免这种单点故障问题,在不同场景中存在几种有用的加密技术。

例如,想象一个只有被 Alice 签署的财务交易才能被接受的协议。这给 Alice 带来了很大的负担,她可能害怕成为攻击者的目标。为了减少对 Alice 攻击的影响,我们可以改变协议,接受(在同一交易中)来自 n 个不同公钥的 n 个签名,其中包括 Alice 的签名。攻击者必须破坏所有 n 个签名才能伪造有效交易!这种系统被称为 多重签名(通常缩写为 multi-sig),在加密货币领域被广泛采用。

然而,天真的多重签名方案可能会增加一些烦人的开销。实际上,在我们的示例中,交易的大小随所需签名数量的增加而线性增长。为了解决这个问题,一些签名方案(如 BLS 签名方案)可以将多个签名压缩成一个。这被称为 签名聚合。一些多重签名方案甚至通过允许将 n 个公钥聚合成一个单一公钥来进一步压缩。这种技术被称为 分布式密钥生成(DKG),是一种称为 安全多方计算 的密码学领域的一部分,我将在第十五章中介绍。

DKG 让 n 个参与者在计算公钥时不需要在过程中明文存储相关私钥(与 SSS 不同,没有经销商)。如果参与者想要签署一条消息,他们可以协作使用每个参与者的私密份额来创建签名,这些签名可以使用他们之前创建的公钥进行验证。再次强调,私钥在物理上从未存在,避免了 SSS 存在的单点故障问题。因为你在第七章看到了 Schnorr 签名,图 8.14 展示了简化的 Schnorr DKG 方案背后的直觉。


图 8.14 Schnorr 签名方案可以分散为分布式密钥生成方案。

最后,请注意

  • 我提到的每种方案都可以在只有 n 个参与者中的阈值 m 参与协议时运行。这对于大多数现实世界系统必须容忍一些恶意或不活跃的参与者非常重要。
  • 这些类型的方案可以与其他非对称加密算法一起使用。例如,使用阈值加密,一组参与者可以协作地对一条消息进行非对称解密。

我在图 8.15 中回顾了所有这些示例。


图 8.15 对将我们对一个参与者的信任分割为多个参与者的现有技术进行了回顾。

阈值方案是密钥管理领域的一个重要新范式,跟踪它们的发展是一个好主意。NIST 目前有一个阈值密码学组,组织研讨会,并有意在长远的未来标准化原语和协议。

摘要

  • 如果一个数字是与该集合中的所有其他数字相比以相等的概率选择的,则从集合中均匀且随机地获取一个数字。
  • 熵是衡量字节串具有多少随机性的度量标准。高熵指的是均匀随机的字节串,而低熵指的是容易猜测或预测的字节串。
  • 伪随机数生成器(PRNGs)是一种算法,它以均匀随机的种子生成(实际上)几乎无限数量的随机性,如果种子足够大,则可以用于加密目的(例如作为加密密钥)。
  • 要获取随机数,应该依赖于编程语言的标准库或其知名的加密库。如果这些不可用,操作系统通常提供接口来获取随机数:
  • Windows 提供了BCryptGenRandom系统调用。
  • Linux 和 FreeBSD 提供了getrandom系统调用。
  • 其他类 Unix 操作系统通常有一个名为/dev/urandom的特殊文件,显示出随机性。
  • 密钥派生函数(KDF)在希望从偏向但熵值高的秘密派生密钥的场景中非常有用。
  • HKDF(基于 HMAC 的密钥派生函数)是最广泛使用的 KDF,基于 HMAC。
  • 密钥管理是保持秘密的领域,主要包括找到存储秘密的位置、积极地过期和轮换秘密、确定秘密被泄露时该做什么等。
  • 为了减轻密钥管理的负担,可以将一个参与者的信任分散到多个参与者中。

第二部分:协议:密码学的配方

你现在要进入本书的第二部分,这一部分将充分利用你在第一部分学到的知识。可以这样理解:如果你在第一部分学到的密码学原语是密码学的基本成分,那么你现在要学习的就是一些配方。而要做的菜有很多!虽然凯撒大帝可能只对加密他的通信感兴趣,但如今的密码学无处不在,要跟踪这一切是相当困难的。

在第 9、10 和 11 章中,我会告诉你最有可能遇到密码学的地方以及密码学是如何用于解决现实问题的;也就是说,密码学是如何加密通信以及如何对协议参与者进行认证的。在很大程度上,这就是密码学的内容。参与者可能众多也可能少数,由比特或者血肉构成。你很快就会意识到,现实世界的密码学涉及到各种权衡,并且根据不同的背景,解决方案也会有所不同。

第 12 和 13 章带你进入两个迅速发展的密码学领域:加密货币和硬件密码学。前者的话题被大多数密码学书籍所忽视。(我相信这本书,《现实世界的密码学》,是第一本包含加密货币章节的密码学书籍。)后者,硬件密码学,也经常被忽视;密码学家常常假设他们的原语和协议在受信任的环境中运行,而这种情况越来越少见。硬件密码学是关于推动密码学运行的边界,并在攻击者越来越接近你的时候提供安全保障。

在第 14 和第十五章中,我涉及到了最前沿的内容:还未出现但即将出现的内容以及现阶段已经存在的内容。你将了解到后量子密码学,这是一个取决于我们作为人类是否发明出可扩展的量子计算机而可能有用的密码学领域。这些基于量子物理领域新范式的量子计算机可能会彻底改变研究,并且,也许甚至会打破我们的加密…… 你还将了解到我所称之为“下一代密码学”的内容,这些密码学原语很少被使用,但随着这些原语被研究、变得更加高效并被应用设计者采用,你很可能会更频繁地看到它们。最后,在第十六章中,我就现实世界的密码学做了一些最终的备注,并就伦理问题发表了一些看法。

第九章:安全传输

这一章涵盖了

  • 安全传输协议
  • 传输层安全协议(TLS)
  • 噪声协议框架

今天加密通信最大的使用量可能是为了加密通信。毕竟,加密学就是为了这个目的而发明的。为了做到这一点,应用程序通常不直接使用像认证加密这样的加密原语,而是使用更复杂的协议来抽象加密原语的使用。我将这些协议称为安全传输协议,因为没有更好的术语。

在本章中,你将了解到最广泛使用的安全传输协议:传输层安全协议(TLS)。我也会简要介绍其他安全传输协议以及它们与 TLS 的区别。

9.1 SSL 和 TLS 安全传输协议

为了理解为什么传输协议(用于加密机器间通信的协议)是必要的,让我们通过一个激励场景来走一遍。当你在浏览器中输入,比如说,http://example.com,你的浏览器会使用多个协议来连接到一个网络服务器并获取你请求的页面。其中一个是超文本传输协议(HTTP),你的浏览器用它来告诉另一边的网络服务器它感兴趣的是哪个页面。HTTP 使用的是一种人类可读的格式。这意味着你可以查看正在通过网络发送和接收的 HTTP 消息,并且不需要任何其他工具就可以阅读它们。但这对于你的浏览器来与网络服务器通信还不够。

HTTP 消息被封装到其他类型的消息中,称为TCP 帧,这些帧在传输控制协议(TCP)中定义。TCP 是一个二进制协议,因此,它不是人类可读的:你需要一个工具来理解 TCP 帧的字段。TCP 消息进一步被封装到 Internet 协议(IP)中,并且 IP 消息进一步被封装到其他东西中。这被称为Internet 协议套件,因为它是许多书籍的主题,我不会进一步深入讨论这个。

回到我们的场景,因为存在保密性问题,我们需要谈论一下。任何坐在你的浏览器和example.com的网络服务器之间的线上的人都有一个有趣的位置:他们可以被动地观察和读取你的请求以及服务器的响应。更糟糕的是,中间人攻击者也可以主动篡改和重新排序消息。这并不好。

想象一下,每次在互联网上购物时您的信用卡信息泄露,每次登录网站时密码被盗,每次向朋友发送图片和私人消息时被窃取等等。这足以让足够多的人感到恐慌,以至于在 1990 年代,TLS 的前身——安全套接字层(SSL)协议诞生了。虽然 SSL 可以用于不同类型的情况,但它最初是由网页浏览器构建和用于的。因此,它开始与 HTTP 一起使用,将其扩展为超文本传输安全协议(HTTPS)。现在,HTTPS 允许浏览器将其与访问的不同网站之间的通信安全地连接起来。

9.1.1 从 SSL 到 TLS

尽管 SSL 并不是唯一尝试保护网络的协议,但它吸引了大部分关注,并且随着时间的推移,已成为事实上的标准。但这并不是整个故事。在第一个 SSL 版本和我们今天使用的之间,发生了很多事情。所有版本的 SSL(最后一个是 SSL v3.0)由于设计不良和加密算法不佳的组合而被破解。(许多攻击已在 RFC 7457 中总结。)

在 SSL 3.0 之后,该协议正式转移到了互联网工程任务组(IETF),这是负责发布请求评论(RFCs)标准的组织。SSL 的名称被更改为 TLS,TLS 1.0 于 1999 年作为 RFC 2246 发布。TLS 的最新版本是 TLS 1.3,规定在 RFC 8446 中,并于 2018 年发布。与其前身不同,TLS 1.3 源自行业和学术界之间的紧密合作。然而,如今,互联网仍然在许多不同版本的 SSL 和 TLS 之间分裂,因为服务器更新速度缓慢。

注意 关于 SSL 和 TLS 这两个名称存在很多混淆。该协议现在被称为TLS,但许多文章甚至库仍然选择使用术语SSL

TLS 已经不仅仅是保护网络的协议;它现在在许多不同的场景和各种类型的应用程序和设备中被用作保护通信的协议。因此,在本章中学到的关于 TLS 的知识不仅对网络有用,而且对任何需要保护两个应用程序之间通信的场景都有用。

9.1.2 在实践中使用 TLS

人们如何使用 TLS?首先让我们定义一些术语。在 TLS 中,想要保护通信的两个参与者被称为客户端服务器。它的工作方式与其他网络协议(如 TCP 或 IP)相同:客户端是发起连接的一方,服务器是等待连接被发起的一方。一个 TLS 客户端通常是由

  • 一些配置—客户端配置了它想要支持的 SSL 和 TLS 版本,愿意使用的加密算法来保护连接,可以对服务器进行身份验证的方式等。
  • 它想要连接的服务器的一些信息 — 至少包括 IP 地址和端口,但对于 Web,通常会使用完全合格的域名(如 example.com)。

有了这两个参数,客户端就可以与服务器建立连接以建立一个安全的 会话,这是客户端和服务器都可以用来相互分享加密消息的通道。在某些情况下,安全会话可能无法成功创建并在中途失败。例如,如果攻击者试图篡改连接,或者服务器的配置与客户端不兼容(稍后详细介绍),客户端将无法建立安全会话。

TLS 服务器通常要简单得多,因为它只需要一个配置,这与客户端的配置类似。然后服务器等待客户端连接以建立一个安全会话。在实践中,在客户端使用 TLS 可以像下面的清单所示那样简单(即,如果你使用像 Golang 这样的编程语言)。

清单 9.1 Golang 中的 TLS 客户端

import "crypto/tls"
func main() {
    destination := "google.com:443"                           // ❶
     TLSconfig := &tls.Config{}                               // ❷
     conn, err := tls.Dial("tcp", destination, TLSconfig)
    if err != nil {
        panic("failed to connect: " + err.Error())
    }
    conn.Close()
}

❶ 完全合格的域名和服务器的端口(443 是 HTTPS 的默认端口)。

❷ 空配置作为默认配置。

客户端如何知道它建立的连接确实是与 google.com 而不是某个冒名顶替者?默认情况下,Golang 的 TLS 实现使用您操作系统的配置来确定如何对 TLS 服务器进行身份验证。(本章后面,您将了解 TLS 中身份验证的确切工作原理。)在服务器端使用 TLS 也非常简单。下面的清单展示了这是多么简单。

清单 9.2 Golang 中的 TLS 服务器

import (
    "crypto/tls"
    "net/http"
)
func hello(rw http.ResponseWriter, req *http.Request) {
    rw.Write([]byte("Hello, world\n"))
}
func main() {
    config := &tls.Config{                                 // ❶
         MinVersion: tls.VersionTLS13,                     // ❶
     }                                                     // ❶
    http.HandleFunc("/", hello)                            // ❷
    server := &http.Server{                                // ❸
         Addr:      ":8080",                               // ❸
         TLSConfig: config,                                // ❸
     }
    cert := "cert.pem"
    key := "key.pem"
    err := server.ListenAndServeTLS(cert, key)             // ❹
     if err != nil {
        panic(err)
    }
}

❶ TLS 1.3 服务器的稳定最小配置

❷ 提供一个显示“Hello, world”的简单页面。

❸ 在端口 8080 上启动一个 HTTPS 服务器。

❹ 包含证书和私钥的一些 .pem 文件(稍后详细介绍)

Golang 及其标准库在这方面为我们做了很多工作。不幸的是,并非所有语言的标准库都提供易于使用的 TLS 实现,如果提供的话,也并非所有 TLS 库都提供默认安全的实现!因此,根据库的不同,配置 TLS 服务器并不总是直截了当的。在下一节中,你将了解 TLS 的内部工作原理及其不同的微妙之处。

注意 TLS 是在 TCP 之上运行的协议。为了保护 UDP 连接,我们可以使用 DTLS(D 代表 数据报,即 UDP 消息的术语),它与 TLS 非常相似。因此,本章中我忽略了 DTLS。

9.2 TLS 协议是如何工作的?

正如我之前所说,如今 TLS 是保护应用程序之间通信的事实标准。在本节中,您将了解 TLS 在表面下如何工作以及它在实践中的使用方式。您会发现这一节对于学习如何正确使用 TLS 以及理解大多数(如果不是全部)安全传输协议如何工作非常有用。您还将了解为什么重新设计或重新实现这些协议是困难的(并且强烈不建议)。

在高层次上,TLS 分为两个阶段,如下列表所示。图 9.1 说明了这个概念。

  • 握手阶段—两个参与者之间协商并创建了安全通信。
  • 后握手阶段—两个参与者之间的通信被加密。


图 9.1 在高层次上,安全传输协议首先在握手阶段创建安全连接。之后,安全连接两侧的应用程序可以安全通信。

此时,由于您在第六章学习了混合加密,您应该对这两个步骤的工作原理有以下(正确的)直觉:

  • 握手本质上只是一个密钥交换过程。 握手最终导致两个参与者就一组对称密钥达成一致。
  • 后握手阶段纯粹是关于在参与者之间加密消息。 这个阶段使用经过认证的加密算法和在握手结束时产生的密钥集。

大多数传输安全协议都是这样工作的,这些协议的有趣部分总是在握手阶段。接下来,让我们看看握手阶段。

9.2.1 TLS 握手

正如您所见,TLS(以及大多数传输安全协议)分为两部分:握手后握手阶段。在本节中,您将首先了解握手。握手本身有四个方面,我想告诉您:

  • 协商—TLS 高度可配置。客户端和服务器都可以配置为协商一系列 SSL 和 TLS 版本以及一组可接受的加密算法。握手的协商阶段旨在在客户端和服务器的配置之间找到共同点,以便安全连接两个对等方。
  • 密钥交换—握手的整个目的是在两个参与者之间执行密钥交换。要使用哪种密钥交换算法?这是客户端/服务器协商过程的一部分决定的事情之一。
  • 认证—正如您在第五章中学到的关于密钥交换的知识,中间人攻击者可以轻易冒充密钥交换的任何一方。因此,密钥交换必须经过认证。例如,您的浏览器必须有一种方式来确保它正在与 google.com 通信,而不是您的互联网服务提供商(ISP)。
  • 会话恢复—由于浏览器经常连接到同一网站,密钥交换可能成本高昂,可能会减慢用户体验。因此,TLS 集成了快速跟踪安全会话而无需重新进行密钥交换的机制。

这是一个全面的列表!像闪电一样快,让我们从第一项开始。

TLS 中的协商:选择哪个版本和哪些算法?

TLS 中的大部分复杂性来自协议的不同部分的协商。臭名昭著的是,这种协商也是 TLS 历史上许多问题的根源。像 FREAK、LOGJAM、DROWN 等攻击利用旧版本中存在的弱点来破坏协议的更近期版本(有时甚至在服务器不支持旧版本的情况下!)。虽然并非所有协议都具有版本控制或允许协商不同算法,但 SSL/TLS 是为网络设计的。因此,SSL/TLS 需要一种方式来与可能更新缓慢的旧客户端和服务器保持向后兼容性。

这就是今天网络上发生的事情:你的浏览器可能是最新的,支持 TLS 版本 1.3,但当访问一些旧网页时,很可能其背后的服务器只支持 TLS 版本 1.2 或 1.1(或更糟糕)。反之亦然,许多网站必须支持旧浏览器,这意味着支持旧版本的 TLS(因为一些用户仍停留在过去)。

旧版 SSL 和 TLS 安全吗?

大多数 SSL 和 TLS 版本都存在安全问题,除了 TLS 版本 1.2 和 1.3。为什么不只支持最新版本(1.3)并结束呢?原因在于一些公司支持无法轻松更新的旧客户端。由于这些要求,通常会发现库实施对已知攻击的缓解措施,以安全地支持旧版本。不幸的是,这些缓解措施通常太复杂,难以正确实施。

例如,像 Lucky13 和 Bleichenbacher98 这样的著名攻击一再被安全研究人员在各种 TLS 实现中重新发现,这些实现先前曾试图修复这些问题。虽然可以减轻对旧版 TLS 的一些攻击,但我建议不要这样做,而且我不是唯一一个这样告诉你的人。2021 年 3 月,IETF 发布了 RFC 8996:“淘汰 TLS 1.0 和 TLS 1.1”,从而正式宣布了淘汰。

协商始于客户端向服务器发送第一个请求(称为ClientHello)。ClientHello 包含一系列支持的 SSL 和 TLS 版本,客户端愿意使用的一套加密算法,以及可能与握手的其余部分或应用程序相关的其他信息。加密算法套件包括

  • 一个或多个密钥交换算法——TLS 1.3 定义了用于协商的以下算法:ECDH 与 P-256、P-384、P-521、X25519、X448,以及 FFDH 与 RFC 7919 中定义的群。这些内容在第五章中有介绍。TLS 的先前版本也提供了 RSA 密钥交换(在第六章中介绍),但它们已在最新版本中删除。
  • 握手的不同部分需要两个或更多数字签名算法——TLS 1.3 规定了 RSA PKCS#1 版本 1.5 和更新的 RSA-PSS,以及更近期的椭圆曲线算法如 ECDSA 和 EdDSA。这些内容在第七章有介绍。请注意,数字签名是用散列函数指定的,这使得你可以协商使用,例如,RSA-PSS 与 SHA-256 或 SHA-512。
  • 用于 HMAC 和 HKDF 的一个或多个散列函数——TLS 1.3 指定了 SHA-256 和 SHA-384,这是 SHA-2 散列函数的两个实例。(你在第二章学习过 SHA-2。)这种散列函数的选择与数字签名算法使用的散列函数无关。作为提醒,HMAC 是你在第三章学习的消息认证码,而 HKDF 是我们在第八章介绍的密钥派生函数。
  • 一个或多个经过身份验证的加密算法——这些可以包括 128 位或 256 位密钥的 AES-GCM,ChaCha20-Poly1305 和 AES-CCM。这些内容在第四章有介绍。

然后服务器以ServerHello消息回复,其中包含从客户端的选择中精选出的每种类型的加密算法。下图描述了这个响应。


如果服务器无法找到支持的算法,它将中止连接。但在某些情况下,服务器不必中止连接,而是可以要求客户端提供更多信息。为此,服务器将以一条称为HelloRetryRequest的消息回复,要求提供缺失的信息。然后客户端可以重新发送其 ClientHello,这次带上额外请求的信息。

TLS 和前向安全密钥交换

密钥交换是 TLS 握手中最重要的部分!没有它,显然就没有对称密钥的协商。但是要进行密钥交换,客户端和服务器必须首先交换各自的公钥。

在 TLS 1.2 和之前的版本中,客户端和服务器只有在双方同意使用哪种密钥交换算法后才开始密钥交换。这发生在协商阶段。TLS 1.3 通过尝试同时进行协商和密钥交换来优化这个流程:客户端推测选择一个密钥交换算法,并在第一条消息(ClientHello)中发送一个公钥。如果客户端未能预测服务器选择的密钥交换算法,则客户端回退到协商的结果,并发送包含正确公钥的新 ClientHello。以下步骤描述了这种情况可能是什么样子。我在图 9.2 中说明了这种差异。

  1. 客户端发送一个 TLS 1.3 ClientHello 消息,宣布它可以执行 X25519 或 X448 密钥交换。它还发送了一个 X25519 公钥。
  2. 服务器不支持 X25519,但支持 X448。它向客户端发送一个 HelloRetryRequest,宣布它只支持 X448。
  3. 客户端发送相同的 ClientHello,但是使用 X448 公钥。
  4. 握手继续进行。


图 9.2 在 TLS 1.2 中,客户端在发送公钥之前等待服务器选择要使用的密钥交换算法。在 TLS 1.3 中,客户端推测服务器将选择哪种密钥交换算法,并在第一条消息中预先发送一个(或多个)公钥,可能避免额外的往返。

TLS 1.3 中充满了这样的优化,对于网络来说非常重要。事实上,全球许多人拥有不稳定或缓慢的连接,保持非应用通信的最低限度是非常重要的。此外,在 TLS 1.3 中(与之前的 TLS 版本不同),所有密钥交换都是临时的。这意味着对于每个新会话,客户端和服务器都会生成新的密钥对,然后在密钥交换完成后将其丢弃。这为密钥交换提供了前向保密性:客户端或服务器的长期密钥泄露不会允许攻击者解密此会话,只要临时私钥被安全删除。

想象一下,如果一个 TLS 服务器在与客户端执行每次密钥交换时都使用单个私钥会发生什么。通过执行临时密钥交换并在握手结束后立即摆脱私钥,服务器可以防止此类攻击者。我在图 9.3 中进行了说明。


图 9.3 在 TLS 1.3 中,每个会话都以临时密钥交换开始。如果服务器在某个时间点被攻破,之前的会话不会受到影响。

练习

如果服务器的私钥在某个时间点被泄露,那么中间人攻击者将能够解密所有先前记录的对话。你明白这是如何发生的吗?

一旦临时公钥交换完成,就会执行密钥交换,并且可以推导出密钥。TLS 1.3 在不同时间点推导出不同的密钥,以使用独立密钥加密不同的阶段。

前两条消息,即 ClientHello 和 ServerHello,在此时不能加密,因为此时没有交换公钥。但是在此之后,一旦密钥交换发生,TLS 1.3 就会加密握手的其余部分。(这与之前的 TLS 版本不同,之前的版本没有加密任何握手消息。)

为了推导出不同的密钥,TLS 1.3 使用与协商的哈希函数的 HKDF。在密钥交换的输出上使用 HKDF-Extract 来消除任何偏差,而使用不同的 info 参数与 HKDF-Expand 来推导出加密密钥。例如,tls13 c hs traffic(表示“客户端握手流量”)用于推导出客户端在握手期间加密到服务器的对称密钥,而 tls13 s ap traffic(表示“服务器应用流量”)用于推导出服务器在握手之后加密到客户端的对称密钥。请记住,未经身份验证 的密钥交换是不安全的!接下来,您将看到 TLS 如何解决此问题。

TLS 身份验证和 web 公钥基础设施

经过一些协商和密钥交换之后,握手必须继续。接下来发生的是 TLS 的另一个最重要的部分 —— 身份验证。在密钥交换的第五章中,您看到拦截密钥交换并冒充密钥交换的一方或双方是微不足道的。在本节中,我将解释您的浏览器如何通过密码验证确保它正在与正确的网站通信,而不是与冒充者通信。但首先,让我们退一步。实际上,TLS 1.3 握手分为三个不同的阶段(如图 9.4 所示):

  1. 密钥交换 —— 此阶段包含提供一些协商并执行密钥交换的 ClientHelloServerHello 消息。此阶段之后的所有消息,包括握手消息,在此阶段之后都将被加密。
  2. 服务器参数 —— 此阶段的消息包含来自服务器的附加协商数据。这是不必包含在服务器的第一条消息中的协商数据,但是可以受益于加密。
  3. 身份验证 —— 此阶段包括来自服务器和客户端的身份验证信息。


图 9.4 TLS 1.3 握手分为三个阶段:密钥交换阶段、服务器参数阶段以及(最后)身份验证阶段。

在网络上,TLS 中的身份验证通常是单向的。只有浏览器验证例如 google.com 是否确实是 google.com,但是 google.com 不验证您是谁(或至少不是作为 TLS 的一部分)。

双向认证的 TLS

客户端认证通常通过应用层进行,最常见的方式是通过一个表单要求您输入凭据。也就是说,如果服务器在服务器参数阶段请求,客户端认证也可以在 TLS 中发生。当连接的双方都经过认证时,我们称之为相互认证的 TLS(有时缩写为 mTLS)。

客户端认证与服务器认证的方式相同。这可以在服务器认证之后的任何时候发生(例如,在握手期间或在握手后阶段)。

现在让我们回答一个问题,“当连接到 google.com 时,您的浏览器如何验证您确实正在与 google.com 握手?”答案是通过使用web 公钥基础设施(web PKI)

在第七章关于数字签名中,您了解了公钥基础设施的概念,但让我简要地重新介绍一下这个概念,因为它在理解 Web 运作方式方面非常重要。Web PKI 有两个方面。首先,浏览器必须信任一组我们称之为证书颁发机构(CAs)的根公钥。通常,浏览器要么使用一组硬编码的受信任公钥,要么依赖操作系统提供它们。

web PKI

对于 Web,存在数百家由世界各地不同公司和组织独立运行的这些 CA。这是一个相当复杂的系统,这些 CA 有时也可以签署中间 CA 的公钥,而中间 CA 反过来也有权签署网站的公钥。因此,像证书颁发机构浏览器论坛(CA/Browser Forum)这样的组织制定规则,并决定何时新组织可以加入受信任公钥集合,或者何时 CA 不再可信并必须从该集合中移除。

其次,想要使用 HTTPS 的网站必须有一种方式从这些 CA 那里获取认证(对其签名公钥的签名)。为了做到这一点,网站所有者(或者我们过去常说的网站管理员)必须向 CA 证明他们拥有特定的域名。

注意:为自己的网站获取证书过去需要支付费用。现在情况已经不同了,因为像 Let’s Encrypt 这样的 CA 提供免费证书。

要证明你拥有 example.com,例如,CA 可能会要求你在 example.com/some_path/file.txt 上托管一个包含为你的请求生成的一些随机数字的文件。以下漫画展示了这个交换过程。


在此之后,CA 可以对网站的公钥提供签名。由于 CA 的签名通常有效期数年,我们称其为长期签名公钥(与临时公钥相对)。更具体地说,CA 实际上并不签署公钥,而是签署证书(稍后详细介绍)。证书包含长期公钥,以及一些额外重要的元数据,如网页的域名。

为了向您的浏览器证明其正在与 google.com 通信,服务器在 TLS 握手的一部分发送一个证书链。该链包括

  • 其自身的叶子证书,包含(其他内容)域名(google .com,例如),谷歌的长期签名公钥,以及 CA 的签名
  • 从签署谷歌证书的中间 CA 证书链到签署最后一个中间 CA 的根 CA 的一系列中间 CA 证书

这有点冗长,所以我在图 9.5 中进行了说明。


图 9.5 Web 浏览器只需信任相对较小的一组根 CA 即可信任整个网络。这些 CA 存储在所谓的信任存储中。为了让浏览器信任网站,该网站必须将其叶子证书签名为这些 CA 之一。有时根 CA 只签署中间 CA,然后中间 CA 签署其他中间 CA 或叶子证书。这就是所谓的 Web PKI。

服务器通过 TLS 消息和客户端发送证书链,就好像要求客户端进行身份验证一样。随后,服务器可以使用其经过认证的长期密钥对来签署所有已接收和先前发送的握手消息,这称为CertificateVerify消息。图 9.6 回顾了这个流程,其中只有服务器对自己进行身份验证。

CertificateVerify 消息中的签名向客户端证明了服务器目前所见的内容。如果没有此签名,中间人攻击者可以拦截服务器的握手消息,并替换 ServerHello 消息中包含的服务器的临时公钥,从而使攻击者能够成功冒充服务器。请花点时间理解在 CertificateVerify 签名存在的情况下,攻击者为何不能替换服务器的临时公钥。


图 9.6 握手的身份验证部分始于服务器向客户端发送证书链。证书链以叶子证书开始(包含网站的公钥和附加元数据,如域名),并以浏览器信任的根证书结束。每个证书都包含上面证书的签名。

故事时间

几年前,我被聘请来审查一个大公司制作的自定义 TLS 协议。结果他们的协议让服务器提供了一个不包含临时密钥的签名。当我告诉他们这个问题时,整个房间沉默了整整一分钟。这当然是一个重大错误:一个能够拦截自定义握手并用自己的密钥替换临时密钥的攻击者将成功冒充服务器。

这里的教训是重复造轮子很重要。安全传输协议很难正确实现,如果历史已经表明了什么,那就是它们可能以许多意想不到的方式失败。相反,你应该依赖于成熟的协议如 TLS,并确保你使用的是一个受到大量公众关注的流行实现。

最后,为了正式结束握手,连接的双方都必须在身份验证阶段发送一个 Finished 消息。Finished 消息包含一个由 HMAC 生成的认证标签,用于与会话协商的哈希函数。这允许客户端和服务器告诉对方,“这些是我在这个握手过程中发送和接收的所有消息的顺序。”如果握手被中间人攻击者拦截和篡改,这个完整性检查允许参与者检测并中止连接。这尤其有用,因为一些握手模式没有签名(稍后详细介绍)。

在继续谈握手的不同方面之前,让我们先来看看 X.509 证书。它们是许多密码协议的重要细节。

通过 X.509 证书进行身份验证

虽然在 TLS 1.3 中证书是可选的(您始终可以使用普通密钥),但许多应用程序和协议,不仅仅是网络,都大量使用它们来认证额外的元数据。具体来说,使用了 X.509 证书标准第 3 版。

X.509 是一个相当古老的标准,旨在足够灵活,可以用于多种场景:从电子邮件到网页。X.509 标准使用了一种称为 Abstract Syntax Notation One (ASN.1) 的描述语言来指定证书中包含的信息。在 ASN.1 中描述的数据结构如下所示:

Certificate  ::=  SEQUENCE  {
    tbsCertificate       TBSCertificate,
    signatureAlgorithm   AlgorithmIdentifier,
    signatureValue       BIT STRING  }

你可以把它看作是一个包含三个字段的结构:

  • tbsCertificate — 待签名的证书。这包含了想要认证的所有信息。对于网络,这可以包含域名(例如 google.com)、公钥、过期日期等。
  • signatureAlgorithm — 用于签署证书的算法。
  • signatureValue — 来自 CA 的签名。

练习

signatureAlgorithmsignatureValue 不包含在实际的证书 tbsCertificate 中。你知道为什么吗?

您可以通过使用 HTTPS 连接到任何网站,然后使用浏览器功能观察服务器发送的证书链来轻松检查 X.509 证书中的内容。请参见图 9.7 以获取示例。


图 9.7 使用 Chrome 的证书查看器,我们可以观察到谷歌服务器发送的证书链。根 CA 是 Global Sign,这是您的浏览器信任的。在链中,一个名为 GTS CA 101 的中间 CA 由于其证书包含来自 Global Sign 的签名而受到信任。反过来,谷歌的叶子证书,适用于*.google.com(google.com,mail.google.com 等),包含来自 GTS CA 101 的签名。

您可能会遇到以.pem 文件形式存在的 X.509 证书,其中包含一些被 base64 编码的内容,周围包含一些人类可读的提示,说明 base64 编码的数据包含的内容(这里是一个证书)。以下代码片段表示.pem 格式证书的内容:

-----BEGIN CERTIFICATE-----
MIIJQzCCCCugAwIBAgIQC1QW6WUXJ9ICAAAAAEbPdjANBgkqhkiG9w0BAQsFADBC
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMRMw
EQYDVQQDEwpHVFMgQ0EgMU8xMB4XDTE5MTAwMzE3MDk0NVoXDTE5MTIyNjE3MDk0
NVowZjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcT
[...]
vaoUqelfNJJvQjJbMQbSQEp9y8EIi4BnWGZjU6Q+q/3VZ7ybR3cOzhnaLGmqiwFv
4PNBdnVVfVbQ9CxRiplKVzZSnUvypgBLryYnl6kquh1AJS5gnJhzogrz98IiXCQZ
c7mkvTKgCNIR9fedIus+LPHCSD7zUQTgRoOmcB+kwY7jrFqKn6thTjwPnfB5aVNK
dl0nq4fcF8PN+ppgNFbwC2JxX08L1wEFk2LvDOQgKqHR1TRJ0U3A2gkuMtf6Q6au
3KBzGW6l/vt3coyyDkQKDmT61tjwy5k=
-----END CERTIFICATE-----

如果您解码被BEGIN CERTIFICATEEND CERTIFICATE包围的 base64 内容,您将得到一个Distinguished Encoding Rules(DER)编码的证书。DER 是一种确定性(只有一种编码方式)的二进制编码,用于将 X.509 证书转换为字节。所有这些编码对新手来说通常相当令人困惑!我在图 9.8 中总结了所有这些。


图 9.8 在左上角,使用 ASN.1 表示法编写了一个 X.509 证书。然后将其转换为可以通过 DER 编码进行签名的字节。由于这不是可以轻松复制或被人类识别的文本,因此进行了 base64 编码。最后一步是使用 PEM 格式将 base64 数据包装在一些方便的上下文信息中。

DER 只编码信息为“这是一个整数”或“这是一个字节数组”。在编码后,ASN.1 中描述的字段名称(如tbsCertificate)将丢失。因此,如果没有原始 ASN.1 描述每个字段真正含义的知识,解码 DER 就毫无意义。像 OpenSSL 这样的便捷命令行工具允许您解码和将 DER 编码的证书内容翻译成人类术语。例如,如果您下载 google.com 的证书,您可以使用以下代码片段在终端中显示其内容。

$ openssl x509 -in google.pem -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            0b:54:16:e9:65:17:27:d2:02:00:00:00:00:46:cf:76
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Google Trust Services, CN = GTS CA 1O1
        Validity
            Not Before: Oct  3 17:09:45 2019 GMT
            Not After : Dec 26 17:09:45 2019 GMT
        Subject: C = US, ST = California, L = Mountain View, O = Google LLC,
CN = *.google.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:74:25:79:7d:6f:77:e4:7e:af:fb:1a:eb:4d:41:
                    b5:27:10:4a:9e:b8:a2:8c:83:ee:d2:0f:12:7f:d1:
                    77:a7:0f:79:fe:4b:cb:b7:ed:c6:94:4a:b2:6d:40:
                    5c:31:68:18:b6:df:ba:35:e7:f3:7e:af:39:2d:5b:
                    43:2d:48:0a:54
                ASN1 OID: prime256v1
                NIST CURVE: P-256
[...]

尽管如此,X.509 证书颇具争议。在 2012 年,一组研究人员将验证 X.509 证书戏称为“世界上最危险的代码”。这是因为 DER 编码是一个难以正确解析的协议,而 X.509 证书的复杂性可能导致许多错误具有潜在的破坏性。因此,我不建议任何现代应用程序使用 X.509 证书,除非必须使用。

预共享密钥和 TLS 中的会话恢复,或者如何避免密钥交换

密钥交换可能是昂贵的,有时是不必要的。例如,您可能有两台只连接到彼此的机器,并且您可能不想为了保护它们之间的通信而处理公钥基础结构。TLS 1.3 提供了一种使用预共享密钥(PSKs)避免这种开销的方法。PSK 只是客户端和服务器都知道的一个密钥,可以用来为会话导出对称密钥。

在 TLS 1.3 中,PSK 握手的工作原理是使客户端在其 ClientHello 消息中宣布它支持一系列 PSK 标识符。如果服务器识别其中一个 PSK ID,它可以在其响应中(ServerHello 消息)表示如此,然后双方可以避免进行密钥交换(如果他们想要的话)。通过这样做,认证阶段被跳过,使得握手结束时的 Finished 消息成为防止中间人攻击的重要手段。

客户端随机和服务器随机

一个热心的读者可能已经注意到,临时公钥为会话带来了随机性,如果没有它们,握手结束时的对称会话密钥可能始终相同。为不同的会话使用不同的对称密钥非常重要,因为您不希望这些会话被关联起来。更糟糕的是,由于会话之间的加密消息可能不同,这可能导致使用 nonce 重用及其灾难性后果(见第四章)。

为了减轻这一点,客户端 Hello 和服务器 Hello 消息都有一个random字段,为每个新会话随机生成(通常称为客户端随机服务器随机)。由于这些随机值用于在 TLS 中导出对称密钥,因此它有效地为每个新连接的会话对称密钥进行了随机化。

PSK 的另一个用例是会话恢复。会话恢复是指重用从先前会话或连接创建的密钥的过程。如果您已经连接到 google.com 并已验证其证书链,执行了密钥交换,同意了共享密钥等等,为什么在几分钟或几小时后重新访问时还要再做一次这个过程呢?TLS 1.3 提供了一种在成功执行握手后生成 PSK 的方法,该方法可用于后续连接,以避免必须重新执行完整的握手。

如果服务器想提供此功能,它可以在后握手阶段的任何时候发送一个新的会话票证消息。服务器可以通过几种方式创建所谓的会话票证。例如,服务器可以发送一个与数据库中相关信息关联的标识符。这不是唯一的方式,但由于这种机制相当复杂,而且大多数情况下并不必要,所以我在本章中不会深入讨论更多。接下来,让我们看看 TLS 中最简单的部分——通信最终如何加密。

9.2.2 TLS 1.3 如何加密应用数据

一旦握手完成并派生了对称密钥,客户端和服务器都可以相互发送加密的应用程序数据。TLS 还确保这样的消息不能被重播或重新排序!为了做到这一点,认证加密算法使用的 nonce 从一个固定值开始,并在每个新消息中递增。如果消息被重播或重新排序,nonce 将与预期值不同,解密将失败。当发生这种情况时,连接将被终止。

隐藏明文长度

正如您在第四章中学到的,加密并不总是隐藏被加密内容的长度。TLS 1.3 附带了记录填充,您可以配置为在加密之前使用随机数量的零字节填充应用程序数据,从而有效地隐藏消息的真实长度。尽管如此,可能存在去除添加噪声的统计攻击,并且不容易缓解它们。如果您确实需要这种安全属性,您应该参考 TLS 1.3 规范。

从 TLS 1.3 开始,如果服务器决定允许,客户端可以在 ClientHello 消息之后的第一系列消息中发送加密数据。这意味着浏览器不一定需要等到握手结束才开始向服务器发送应用数据。这种机制称为早期数据0-RTT(零往返时间)。它只能与 PSK 的组合一起使用,因为它允许在 ClientHello 消息期间派生对称密钥。

注意 这个特性在 TLS 1.3 标准制定过程中引起了很大争议,因为被动攻击者可以重放观察到的 ClientHello,然后是加密的 0-RTT 数据。这就是为什么只能使用 0-RTT 来传输可以安全重播的应用程序数据。

对于网络,浏览器将每个 GET 查询视为幂等,这意味着 GET 查询不应更改服务器端的状态,只能用于检索数据(与 POST 查询不同)。当然,并不总是如此,应用程序可以随心所欲。因此,如果您面临是否使用 0-RTT 的决定,最简单的方法就是不要使用它。

真实世界的密码学(二)(4)https://developer.aliyun.com/article/1508668

相关文章
|
安全 数据安全/隐私保护
【密码学】一文读懂线性反馈移位寄存器
在正式介绍线性反馈移位寄存器(LFSR)之前,先来看一个小故事,相传在遥远的古代,住着4个奇怪的人。
1646 0
【密码学】一文读懂线性反馈移位寄存器
|
2月前
|
安全 算法 量子技术
密码学:保护信息的艺术与科学
【8月更文挑战第31天】
85 0
|
6月前
|
算法 安全 Java
真实世界的密码学(二)(1)
真实世界的密码学(二)
73 3
|
6月前
|
Web App开发 安全 算法
真实世界的密码学(二)(4)
真实世界的密码学(二)
90 2
|
6月前
|
算法 安全 Linux
真实世界的密码学(二)(2)
真实世界的密码学(二)
87 2
|
6月前
|
安全 算法 网络安全
真实世界的密码学(四)(1)
真实世界的密码学(四)
57 2
|
6月前
|
存储 安全 算法
真实世界的密码学(一)(2)
真实世界的密码学(一)
104 0
|
6月前
|
存储 算法 安全
真实世界的密码学(一)(3)
真实世界的密码学(一)
94 0
|
6月前
|
算法 安全 数据库
真实世界的密码学(一)(4)
真实世界的密码学(一)
110 0
|
6月前
|
算法 安全 网络安全
真实世界的密码学(一)(1)
真实世界的密码学(一)
43 0
下一篇
无影云桌面