0%

《HTTPS 权威指南》读书笔记(02):协议

TLS 是一种密码学协议,用于保证两个团体之间的会话安全。会话是由任意数量的消息组成的。这里讨论的 TLS 协议版本为 TLS1.2。

记录协议

宏观上,TLS 以记录协议(record protocol)实现。每一条 TLS 记录以一个短标头起始。标头包含记录内容的类型(或子协议)、协议版本和长度。消息数据紧跟在标头之后。如下所示:

除了这些可见的字段,还会给每一个 TLS 记录指定唯一的 64 位序列号,但不会在线路上传输。任一端都有自身的序列号并跟踪来自另一端记录的数量。这些值是对抗重放攻击的一部分。

TLS 的主规格说明书定义了四个核心子协议:

  • 握手协议(handshake protocol)
  • 密钥规格变更协议(change cipher spec protocol)
  • 应用数据协议(application data protocol)
  • 警报协议(alert protocol)

握手协议

握手是 TLS 协议中最精密复杂的部分。在这个过程中,通信双方协商连接参数,并且完成身份验证。根据使用的功能的不同,整个过程通常需要交换 6-10 条消息。根据配置和支持的协议扩展不同,交换过程可能有多种变种。在使用中经常可以观察到以下三种流程:

  • 完整的握手,对服务器身份进行验证
  • 恢复之前的会话采用的简短握手
  • 对客户端和服务器都进行身份验证的握手

完整的握手

每一个 TLS 连接都会以握手开始。如果客户端此前并未与服务器建立会话,那么双方会执行一次完整的握手流程来协商 TLS 会话。握手过程中,客户端和服务器将进行以下四个主要步骤:

  • 交换各自支持的功能,对需要的连接参数达成一致
  • 验证出示的证书,或使用其他方式进行身份验证
  • 对将用于保护会话的共享主密钥达成一致
  • 验证握手消息并未被第三方团体修改

如下展示了最常见的 TLS 握手流程(即不需要身份验证的客户端与需要身份验证的服务器之间的握手):

  1. ClientHello: 客户端开始新的握手,并将自身支持的功能提交给服务器

  2. ServerHello: 服务器选择连接参数

  3. Certificate: 服务器发送其证书链(仅当需要服务器身份验证时)

  4. Server Key Exchange: 根据选择的密钥交换方式,服务器发送生成主密钥的额外信息

  5. Server Hello Done: 服务器通知自己完成了协商过程

  6. Client Key Exchange: 客户端发送生成主密钥所需的额外信息

  7. Change Cipher Spec: 客户端切换加密方式并通知服务器

  8. Encryped Handshake Message(Finished): 客户端计算发送和接收到的握手消息的 MAC 并发送。该消息内容会被加密

  9. Change Cipher Spec: 服务器切换加密方式并通知客户端

  10. Encryped Handshake Message(Finished): 服务器计算发送和接收到的握手消息的MAC并发送。该消息内容会被加密

以下在 Wireshark 中展示所抓取的握手报文,总共 4 个报文,这也很好地说明了 TLS 记录协议的含义:

假设没有出现错误,到这一步,连接就建立起来了,可以开始发送应用数据。接下来再详细解释握手消息的更多细节。

在一次新的握手流程中,ClientHello 消息总是第一条消息。这条消息将客户端的功能和首选项传送给服务器。ClientHello 消息包含以下关键元素:

  • Version:协议版本(protocol version)指示客户端支持的最佳协议版本
  • Random:在握手时,客户端和服务器都会提供随机数。这种随机性对每次握手都是独一无二的,在身份验证中起着举足轻重的作用。它可以防止重放攻击,并确认初始数据交换的完整性
  • Session ID:在第一次连接时,会话ID(session ID)字段是空的,这表示客户端并不希望恢复某个已存在的会话。在后续的连接中,这个字段可以保存会话的唯一标识。服务器可以借助会话ID在自己的缓存中找到对应的会话状态
  • Cipher Suites:密码套件(cipher suite)块是由客户端支持的所有密码套件组成的列表,该列表是按优先级顺序排列的
  • Compression:客户端可以提交一个或多个支持压缩的方法。默认的压缩方法是 null,代表没有压缩
  • Extensions:扩展(extension)块由任意数量的扩展组成,这些扩展会携带额外数据

ServerHello 消息的意义是将服务器选择的连接参数传送回客户端。这个消息的结构与 ClientHello 类似,只是每个字段只包含一个选项。

典型的 Certificate 消息用于携带服务器 X.509 证书链。证书链是以 ASN.1 DER 编码的一系列证书,一个接着一个组合而成。主证书必须第一个发送,中间证书按照正确的顺序跟在主证书之后。服务器必须保证它发送的证书与选择的算法套件一致,这就表明服务器可能会需要配置多个证书。Certificate 消息是可选的,因为并非所有套件都使用身份验证,也并非所有身份验证方法都需要证书。

ServerKeyExchange 消息的目的是携带密钥交换的额外数据。消息内容对于不同的协商算法套件都会存在差异。在某些场景中,服务器不需要发送任何内容,这意味着在这些场景中根本不会发送 ServerKeyExchange 消息。例如如下展示了 Server 所发送的 ECDH 密钥交换算法所传递的参数:

ServerHelloDone 消息表明服务器已经将所有预计的握手消息发送完毕。在此之后,服务器会等待客户端发送消息。

ClientKeyExchange 消息携带客户端为密钥交换提供的所有信息。这个消息受协商的密码套件的影响,内容随着不同的协商密码套件而不同。如下展示了 Client 所发送的 DCDH 密钥交换算法所传递的参数:

ChangeCiperSpec 消息表明发送端已经取得用以生成连接参数的足够信息,已生成加密密钥,并且切换到加密模式。客户端和服务器在条件成熟时都会发送该消息。准确来说,该消息的协议属于 Change Cipher Spec Protocol,不属于 Handshake Protocol

Finished 消息意味着握手已经完成。消息内容将加密,以便双方可以安全地交换验证整个握手完整性所需的数据。这个消息包含 verify_data 字段,它的值是握手过程中所有消息的散列值。这些消息在连接两端都按照各自所见的顺序排列,并以协商新得到的主密钥计算散列。因为 Finished 消息是加密的,并且它们的完整性由协商 MAC 算法保证,所以主动网络攻击者不能改变握手消息并对 vertify_data 的值造假。

客户端身份验证

尽管可以选择对任意一端进行身份验证,但人们几乎都启用了对服务器的身份验证。相比之下,服务器通过发送 CertificateRequest 消息请求对客户端进行身份验证。作为响应,客户端发送自己的 Certificate 消息并附上证书。此后,客户端发送 CertificateVerify 消息,证明自己拥有对应的私钥。注意,只有已经通过身份验证的服务器才允许请求客户端身份验证。

完整的握手如下所示:

服务器使用 CertificateRequest 消息请求对客户端进行身份验证,并将其接受的证书的公钥和签名算法传送给客户端。它也可以选择发送一份自己接受的证书颁发机构列表,这些机构都用其可分辨名称来表示。

客户端使用 CertificateVerify 消息证明自己拥有的私钥与之前发送的客户端证书中的公钥相对应。消息中包含一条到这一步为止的所有握手消息的签名。

会话恢复

完整的握手协议非常复杂,需要很多握手消息和两次网络往返才能开始发送客户端应用数据。此外,握手执行的密钥学操作通常需要密集的CPU处理。简短的握手方式可以节约很多消耗。

最初的会话恢复机制是,在一次完整协商的连接断开时,客户端和服务器都会将会话的安全参数保存一段时间。希望使用会话恢复的服务器为会话指定唯一的标识,称为会话 ID。服务器在 ServerHello 消息中将会话 ID 发回客户端。

希望恢复早先会话的客户端将适当的会话 ID 放入 ClientHello 消息,然后提交。服务器如果愿意恢复会话,就将相同的会话 ID 放入 ServerHello 消息返回,接着使用之前协商的主密钥生成一套新的密钥,再切换到加密模式,发送Finished消息。客户端收到会话已恢复的消息以后,也进行相同的操作。这样的结果是握手只需要一次网络往返。如下所示:

用来替代服务器会话缓存和恢复的方案是使用会话票证(sesession ticket)。使用这种方式,除了所有的状态都保持在客户端(与HTTP Cookie的原理类似)之外,其消息流与服务器会话缓存是一样的。

密钥交换

在 TLS 中,会话安全性取决于称为主密钥(master secret)的 48 字节共享密钥。密钥交换的目的是计算另一个值,即预主密钥(premaster secret),这个值是组成主密钥的来源。

TLS 支持许多密钥交换算法,能够支持各种证书类型、公钥算法和密钥生成协议。例如,下表展示了常用的秘钥交换算法:

使用哪一种密钥交换由协商的套件所决定。一旦套件决定下来,两端都能了解按照哪种算法继续操作。实际使用的密钥交换算法主要有以下 4 种:

  • RSA 是一种事实上的标准密钥交换算法,RSA 密钥交换是一种密钥传输(key transport)算法,这种算法由客户端生成预主密钥,并以服务器公钥加密传送给服务器。但是它有一个缺陷:它的设计使被动攻击者可以解码所有加密数据,只要攻击者能够访问服务器的私钥。因此,RSA 密钥交换正慢慢被其他支持前向保密(forward secrecy)的算法所替代
  • DHE_RSA:DHE 是一种密钥协定算法,进行协商的团体都对密钥生成产生作用,并对公共密钥达成一致。在TLS中,DHE 通常与 RSA 身份验证联合使用。它的优点是支持前向保密,缺点是执行缓慢
  • ECDHE_RSA 和 ECDHE_ECDSA:临时椭圆曲线 Diffie-Hellman(ephemeral elliptic curve Diffie-Hellman,ECDHE)密钥交换建立在椭圆曲线加密的基础之上。大家认可它执行很 快而且提供了前向保密。但是只有较新的客户端才能较好地支持。ECDHE 也是一种密钥协定算法,其理论原理与 DHE 类似。在 TLS 中,ECDHE 可以与 RSA 或者 ECDSA 身份验证一起使用

不论使用哪一种密钥交换,服务器都有机会发送 ServerKeyExchange 消息率先发话,并在该消息内发送其秘钥交换的额参数。服务器也会发送参数的签名用于身份验证。使用签名,客户端得以确认它正在与持有私钥对应证书中的公钥的团体进行通信。在某些算法内,服务器不发送任何信息。原因是在这些情况下,所有需要的信息已经通过其他消息得到。

客户端会发送 ClientKeyExchange 消息传送它的密钥交换参数,这个消息总是必需的。

具体可以参见上文展示的 ServerKeyExchangeClientKeyExchange 消息。

RSA 密钥交换

RSA 密钥交换的过程十分直截了当。客户端生成预主密钥(46字节随机数),使用服务器公钥对其加密,将其包含在ClientKeyExchange 消息中,最后发送出去。服务器只需要解密这条消息就能取出预主密钥。TLS 使用的是RFC 34471 定义的 RSAES-PKCS1-v1_5 加密方案。

因为 RSA 算法可以同时用于加密和数字签名,所以RSA密钥交换可以按照这种方式工作。其他流行的密钥类型,比如 DSA(DSS) 和 ECDSA,只能用于签名。

用于加密预主密钥的服务器公钥,一般会保持多年不变。任何能够接触到对应私钥的人都可以恢复预主密钥,并构建相同的主密钥,从而危害到会话安全性。攻击者会记录所有加密的流量,耐心等待有朝一日可以得到密钥。只要密钥泄露,就可以解密之前记录的所有流量了。

TLS 中其他常见的密钥交换方式都不受这个问题的影响,被称为支持前向保密。使用那些密钥交换时,每个连接使用的主密钥相互独立。

Diffie-Hellman 密钥交换

Diffie-Hellman(DH)密钥交换是一种密钥协定的协议,它使两个团体在不安全的信道上生成共享密钥成为可能。以这种方式协商共享密钥时不会受到被动攻击的威胁,但主动攻击者却可以劫持通信信道,冒充对端。这就是 DH 密钥交换通常与身份验证联合使用的原因

抛开算法的细节,DH 的诀窍是使用了一种正向计算简单、逆向计算困难的数学函数,即使交换中某些因子已被知晓,情况也是一样。DH 密钥交换需要 6 个参数: 其中两个(dh_p和dh_g)称为域参数,由服务器选取。协商过程中,客户端和服务器各自生成另外两个参数,相互发送其中一个参数(dh_Ys和dh_Yc)到对端。再经过计算,最终得到共享密钥。

临时 Diffie-Hellman(ephemeral Diffie-Hellman,DHE)密钥交换中没有任何参数被重复使用。与之相对,在一些 DH 密钥交换方式中,某些参数是静态的,并被嵌入到服务器和客户端的证书中。这样的话,密钥交换的结果是一直不变的共享密钥,就无法具备前向保密的能力。

椭圆曲线 Diffie-Hellman 密钥交换

临时椭圆曲线 Diffie-Hellman(elliptic curve Diffie-Hellman,ECDH)密钥交换原理与 DH 相似,但它的核心使用了不同的数学基础。ECDHE 基于椭圆曲线(elliptic curve,EC)。

ECDH 密钥交换发生在一条由服务器定义的特定的椭圆曲线上。这条曲线代替了 DH 中域参数的角色。ECDH 支持静态的秘钥交换,但实际使用时,只使用了这种临时的变种(ECDHE)。

密钥交换由服务器发起,它选择一条椭圆曲线和公开参数(EC point)并提交。然后客户端提交自己的公开参数。在那以后,就可以计算预主密钥。

身份验证

在 TLS 中,为了避免重复执行密码操作造成巨大开销,身份验证与密钥交换紧紧捆绑在一起。大多数场景中,身份验证的基础是证书支持的公钥密码(最常见的是RSA,有时也用ECDSA)。

  • 在 RSA 密钥交换的过程中,客户端生成一个随机值作为预主密钥,并以服务器公钥加密后发送出去。拥有对应私钥的服务器解码消息得到预主密钥。这里所包含的身份验证原理很清楚:只有拥有对应私钥的服务器才能取得预主密钥,构造正确的会话密钥,并生成正确的Finished 消息

  • 在 DHE 和 ECDHE 的交换过程中,服务器为密钥交换提供自己的参数,并使用自己的私钥签名。客户端持有对应的公钥(从已验证的证书中获得),可以验证参数是否真正出自期望的服务器

加密

TLS 可以使用各种方法加密数据,比如使用 3DES、AES、ARIA、CAMELLIA、RC4 或者 SEED 等算法。目前使用最为广泛的加密算法是 AES。TLS 支持三种加密类型:序列密码、分组密码和已验证的加密。在 TLS 中,完整性验证(准确地说,是提供了身份验证的完整性检查)是加密处理的一部分:它要么在协议级中显式处理,要么由协商的密码隐式处理。

序列加密

使用序列密码时,加密由两步组成:

  • 第一步,计算 MAC 值,范围包含记录序列号、标头、明文。MAC 包含标头能确保未进行加密的标头不会遭受篡改。MAC 包括序列号,能确保消息不被重放
  • 加密明文和 MAC,生成密文

如下展示了这种加密过程:

分组加密

使用分组密码时,加密会涉及更多内容,因为需要为分组加密的特性准备解决方案。具体来说,需要以下几个步骤:

  • 计算序列号、标头和明文的 MAC
  • 构造填充,确认加密前的数据长度是分组大小(通常16字节)的整数倍
  • 生成一个长度与分组大小一致的不可预期的初始向量(initialization vector,IV)。IV 能保证加密是不确定的
  • 使用 CBC 分组模式加密明文、MAC 和填充
  • 将IV和密文一起发送

如下展示了这种加密过程:

这种处理方式被称为先计算 MAC,再加密(MAC-then-encrypt)。另一种处理安排方式的提案称为先加密,再计算 MAC(encrypt-then-MAC),最近才被公开提出。在这种替代方案中,首先对明文和填充进行加密,再将结果交给 MAC 算法。这可以保证主动网络攻击者不能操纵任何加密数据。

已验证的加密

已验证的密码将加密和完整性验证合二为一,全名是使用关联数据的已验证加密(authenticated encryption with associated data,AEAD)。表面上,它看起来是序列密码和分组密码的交叉。它不用填充,也不用初始向量,而是使用一个特殊的值,称为 nonce(在加密通信中仅使用一次的密钥),这个值必须唯一。加密过程比使用分组密码要简单一些:

  • 生成一个唯一的 64 位 nonce
  • 使用已验证加密算法加密明文,同时也将序列号和记录标头作为完整性验证依据的额外数据交给算法
  • 将 nonce 和密文一起发送

如下展示了已验证加密的过程:

已验证加密被认为是当前TLS中可用的加密模式中最好的一种,因为它可以避免 MAC-then-encrypt 方式带来的问题。

重新协商

大部分 TLS 连接都以握手作为起点,经过应用数据的交换,最后关闭会话。但如果请求重新协商,就会发起一次新的握手,对新的连接安全参数达成一致。

  • 协议允许客户端在任意时间简单地发送新的 ClientHello 消息请求重新协商,就如同建立一个全新的连接一样,这被称为客户端发起的重新协商(client-initiated renegotiation)
  • 如果服务器希望重新协商,它会发送 HelloRequest 协议消息给客户端。这个消息通知客户端停止发送应用数据,并开始新的握手,这被称为服务器发起的重新协商

正如原本设计的那样,重新协商并不安全,并且可被主动网络攻击者以很多方式滥用。它的弱点于 2009 年被发现,然后通过引进 renegotiation_info 扩展得以修正。

应用数据协议

应用数据协议携带着应用消息,只以 TLS 的角度考虑的话,这些就是数据缓冲区。记录层使用当前连接安全参数对这些消息进行打包、碎片整理和加密。

警报协议

警报的目的是以简单的通知机制告知对端通信出现异常状况。它通常会携带 close_notify 异常,在连接关闭时使用,报告错误。严重程度为 fatal 的消息会立即终止当前连接并使会话失效。

关闭连接

关闭连接警报(closure alert)用于以有序的方式关闭 TLS 连接。一旦一端决定关闭连接,就会发送一个 close_notify 警报。另一端收到这个警报以后,会丢弃任何还未写出的数据,并发送自己的 close_notify 警报。在警报之后到来的任何消息都将被忽略。

关闭协议虽然简单,但是可以避免截断攻击。

密码操作

接下来会对协议中一些重要的方面进行简单讨论:伪随机函数、构建主密钥和生成连接密钥。

伪随机函数

在 TLS 中,伪随机函数(pseudorandom function,PRF)用于生成任意数量的伪随机数据。PRF 使用一个秘钥、一颗种子和一个唯一标签。从 TLS1.2 开始,所有算法套件都需要明确指定他们所使用的 PRF。

主密钥

密钥交换过程的输出是预主密钥。对这个值进行进一步加工,就是使用 PRF 生成48字节(384位)主密钥

1
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)

因为使用不同的密钥交换方法,得到的预主密钥长度可能不同,所以需要执行这个步骤。同时,因为客户端和服务器的随机字段被用作种子,所以主密钥实际上也是随机的。且与协商握手绑定。

密钥生成

连接所需的密钥材料是用单一的 PRF 调用、基于主密钥和客户端、服务器的随机数生成的:

1
key_block = PRF(master_secret, "key expansion", server_random + client_random)

密钥块的长度根据协商的参数而有所不同。密钥块分为六个密钥:两个 MAC 密钥、两个加密密钥和两个初始向量(只在必要时生成,序列密码不会使用 IV)。

  • 不同的密钥用于不同的操作,这样可以预防当共享相同密钥时,密钥学基元之间出现不可预见的交互
  • 同样,因为客户端和服务器都拥有各自的一组密钥,由其中一方产生的消息不会被解释成是由另一方产生的。这个设计决策使协议更加可靠

当恢复会话时,在生成密钥块时使用相同的主密钥,但 PRF 以当前握手时客户端和服务器的随机值进行种子设定。因为每次握手时的随机值都不同,所以密钥每次也不同。

密码套件

TLS 为实现所需的安全属性提供了非常大的灵活性。它是一个创造实际密码协议的框架。虽然以往版本将某些加密基元硬编码到了协议中,但 TLS 1.2 是完全可配置的。密码套件是一组选定的加密基元和其他参数,它可以精确定义如何实现安全。套件大致由以下这些属性定义:

  • 密钥交换方法
  • 身份验证方法
  • 加密算法
  • 加密密钥大小
  • 密码模式(可应用时)
  • MAC算法(可应用时)
  • PRF
  • 用于 Finished 消息的散列函数
  • verify_data 结构的长度

密码套件都倾向于使用较长的描述性名称,并且相当一致:它们都由密钥交换方法、身份验证方法、密码定义以及可选的 MAC 或 PRF 算法组合而成。如下所示:

虽然套件名称所表达的安全性参数并不充分,但是还可以推断出其中的最重要的那些参数。密码套件并未完全掌控其安全参数,它们只是定义了最关键的身份验证和密钥交换算法,而对这些算法的实际参数并没有控制能力(比如密钥和参数强度)。

扩展

TLS 扩展是一种通用目的的扩展机制,使用这种机制可以在不修改协议本身的条件下为 TLS 协议增加新的功能。扩展以扩展块的形式添加在 ClientHello 或 ServerHello 末尾中。扩展块由一个个扩展组成,每个扩展标头是 2 字节扩展类型,扩展的格式和期望的行为由每个扩展自己决定。自从扩展被引入 TLS,它就成为了 TLS 演进的主要载体。

以下是一些常见的扩展。

应用层协议协商

应用层协议协商(application layer protocol negotiation,ALPN)扩展能够在 TLS 连接上协商不同的应用层协议。例如通过 ALPN 协商使用 HTTP/1.1 还是 HTTP/2。

支持 ALPN 的客户端在 application_layer_protocol_negotiation 扩展中提交自己支持的应用
层协议列表给服务器。兼容的服务器会决定使用的协议并使用相同扩展向客户端通知其决定。

ALPN 使用明文传输,使得中间设备可以检查他们并根据所得信息为流量路由。

证书透明度

证书透明度(certificate transparency)的目的是通过保持所有公开的服务器证书来改进互联网 PKI。

椭圆曲线功能

elliptic_curves 扩展在 ClientHello 中列出支持的曲线名称,使服务器可以在其中选择一条双方都支持的曲线。ec_point_formats 扩展可以在协商时对椭圆曲线顶点进行可选压缩。

心跳

心跳(Heartbeat)是一个协议扩展,添加了支持连接保活的功能(检查对端是否仍然可用), 以及为 TLS 和 DTLS 发现路径最大传输单元(path maximum transmission unit,PMTU)。客户端和服务器通过心跳扩展互相通告支持心跳。

次协议协商

次协议协商(next protocol negotiation, NPN)扩展最初是为了配合 SPDY 协议而设计的,但是由于 NPN 有一定的复杂性,最终 TLS 工作组选择了与之竞争的 ALPN 提案。

安全重新协商

renegotiation_info 扩展以验证重新协商的双方仍是先前完成握手的两个团体方式来改进 TLS。

服务器名称指示

服务器名称指示(server name indication,SNI)通过 server_name 扩展实现,它可以为客户端提供一种机制,利用这种机制客户端可以告知服务器它希望与之建立连接的服务器的名称。这个扩展为安全虚拟主机提供支持:它为服务器提供了足够的信息,让服务器可以在所有可用的安全虚拟主机中寻找匹配的证书,如果没有这个机制,每个 IP 地址上只能部署一张证书。

会话票证

会话票证(session ticket)引入了一种新的会话恢复机制,这种机制不需要任何服务器端存储。其思想是服务器取出它的所有会话数据并进行加密,再以票证的方式发回客户端。在接下来的连接中,客户端将票证提交回服务器,服务器检查通过后再使用其中的信息恢复会话。

签名算法

signature_algoritms 扩展使客户端可以通告自己支持的各种签名和散列算法。这个套件是可选的,如果未设置,服务器会根据客户端提供的密码套件推断其支持的签名算法。

OCSP stapling

客户端使用 status_request 扩展指示支持 OSCP stapling。服务器使用该特性发送最新的证书吊销信息给客户端。

Reference