TLS 协议

本文是对《HTTPS 权威指南:在服务器和 Web 应用上部署 SSL/TLS 和 PKI》第 2 章内容的整理,该书的本章内容比较详细的介绍了 TLS 1.2 的内容。

# 记录协议

TLS 以记录协议(record protocol)实现。

何为记录协议?没有找到比较权威的定义,书中描述了如下特点:

  • 消息传输。记录协议传输由其他协议层提交给它的不透明数据缓冲区。如果缓冲区超过记录的长度限制(16384 字节),记录协议会将其切分成更小的片段;反过来,同属一个子协议的多个小缓冲区可以组合成单独的记录
  • 加密以及完整性验证。一旦协商握手完成,记录协议会按照协商对数据进行加密和完整性验证
  • 压缩。记录协议还提供了数据压缩服务,但一般不使用,通常是由上层协议处理压缩
  • 扩展性。记录协议只关注数据传输和加密,而将所有其他特定转交给子协议,可以在此基础上扩展子协议

TLS 的 RFC 定义了 4 个核心子协议:

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

下文会重点对这 4 个核心子协议展开详细描述。

# 握手协议

谈起握手,难免会想到 TCP 的握手过程,三次协商,非常清晰。相较而言,TLS 的握手过程似乎没那么容易理清楚,一共有几次协商呢?我之前看到的资料有说 6 次,有说 9 次等等。实际上,根据使用的功能、配置、扩展的不同,协商的变种非常多,一般来说,通常需要交换 6~10 次消息。有 3 种常见的握手流程:

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

# 完整的握手(单向验证)

这是最常见的握手姿势(其他环境下的握手,懒得分析了,都差不多),时序图如下:

下表是对上述 10 次协商消息的说明:

序号 消息 必须 作用 说明
1 ClientHello 客户端开始新的握手,将自身支持的功能告诉服务器 包括:Version(指示客户端支持的最佳协议版本)、Random(32byte 随机数,用于后续身份验证,可能为空)、Session ID(会话 ID,第一次连接时,为空)、Cipher Suites(客户端所支持的密码套件列表)、Compression( 客户端支持的压缩算法,默认为 null,一般为默认值)、Extensions(扩展)
2 ServerHello 服务器选择连接参数 包括:Version(服务端所支持的协议版本)、Random(32byte 随机数)、Session ID(会话 ID)、Cipher Suite(服务端所选用的密码套件)、Extensions(扩展)
3 Certificate 服务器发送其证书链 认证信息,一般是服务器的 X.509 证书链,也可以是 PGP 密钥
4 ServerKeyExchange 根据选择密钥交换方式,服务器发送生成 master secret 的额外信息 携带密钥交换的额外数据。消息内容对于不同的密码套件会存在差异,譬如对于 RSA 密钥交换算法,根本不需要这个消息
5 ServerHelloDone 服务器告诉客户端自己已完成了协商过程 表明服务器已经将所有预计的握手消息发送完毕
6 ClientKeyExchange 客户端发送生成 master secret 所需的额外信息 携带密钥交换提供的所有信息。消息内容对于不同的密码套件会存在差异
7 ChangeCipherSpec 客户端切换加密方式并通知服务器 表明客户端已取得用以生成连接参数的所有信息,已经生成加密密钥(master secret),并将切换到加密模式
8 Finished 客户端计算发送和接收到的握手消息的 MAC 并发送 意味着握手已经完成,消息内容将加密
9 ChangeCipherSpec 服务器切换加密方式并通知客户端 表明服务端已取得用以生成连接参数的所有信息,已经生成加密密钥(master secret),并将切换到加密模式
10 Finished 服务器计算发送和接收到的握手消息的 MAC 并发送 意味着握手已经完成,消息内容将加密

将上述 10 次协商分成几部分来阐述。

首先是 ClientHello 和 ServerHello,这一个回合的交互用来建立基本的连接,并协商协议版本、密码套件、压缩方法,产生一个 Session ID,ClientHello.Extensions 和 ServerHello.Extensions 还可以用来扩展协商其他信息;ClientHello.Random 和 ServerHello.Random 在计算 master secret 时会用到。

P.S: 关于密码套件,下文会有说明。

实际的密钥交换在主要 3(Certificate)、4(ServerKeyExchange)、6(ClientKeyExchange)这三个消息中完成。

这几个消息的具体内容对于不同的密码套件会存在差异。

对于 Certificate 消息,一般都是服务器的 X.509 证书链(包括证书公钥),Client 可以对该证书进行验证,确保 Server 是合法受信任的,Certificate 也可能与 PGP 密钥有关,对 PGP 不熟,就不展开了...

对于 ServerKeyExchange,它的内容与双方协商的密钥交换方法有关。假如使用的是 RSA 密钥交换算法,客户端生成 master secret 后,使用 Certificate 中的公钥对其进行加密,然后发送给服务端就 ok 了,无需 Server 提供更多的密钥生成参数;换句话说,ServerKeyExchange 在 RAS 密钥交换算法中没有什么卵用。

P.S: 关于密钥交换算法,下文会有更多说明。

生成 master secret 后,Client 还得向 Server 发送两个消息:ChangeCipherSpec 和 Finished;作为回应,Server 也会向 Client 发送 ChangeCipherSpec 和 Finished 消息。

接下来以 Q & A 的形式,将握手流程搞得更清楚一点。

Q: 握手过程中,Server 连续向 Client 发送 ServerHello、Certificate、ServerKeyExchange、ServerHelloDone 这几个消息,可以把它们揉成一起吗?

A: 当然可以,本文开头描述记录协议的特点时讲到,对于同一个子协议的消息,如果内容过短,记录协议会将它们组合成一条记录;同样,如果消息内容过长,记录协议会将它拆分为多条记录(多次发送);换句话说,是否将这些连续发送的消息揉成一起,还得看缓冲区的大小和消息内容的长度;用户无需操心。

Q: 为毛 ChangeCipherSpec 是一个独立的协议,而不是握手协议的一部分?

A: StackExchange 里有一个相关的问题:Why is change cipher spec an independent protocol content type and not part of Handshake Messages。其中的回答描述得非常清楚,结合这个答案,我的阐述是:ChangeCipherSpec 的作用是告诉另一端,接下来的消息将使用已经生成的 master secret 来加密。因此,需要确保 ChangeCipherSpec 之后的消息不会和之前的消息组合成一条记录,因为加密方式不一样嘛! ChangeCipherSpec 是一个单独的子协议 这个事实能够解决这个问题,因为记录协议组合多个消息的前提是,这些消息同属一个子协议。

Q: Finished 消息有什么用?为啥最后 Server 还要向 Client 发送 ChangeCipherSpec 和 Finished 消息呢?

A: Finished 消息的数据其实非常简单,它包含一个 vertify_data 字段,该字段值的计算公式可以简单描述为:

// PRF(pseudorandom function,伪随机函数),该算法在密码套件中说明
// master_secret,主密钥
// finished_label,标签,一个字符串
// Hash(handshake_messages),即握手过程中所有消息的散列值
vertify_data = PRF(master_secret, finished_label, Hash(handshake_messages))

两端的 PRF 算法是一样的,但会使用不同的标签(finished_label),对于 server,对应"server finished",对于 client,对应"client finished"

vertify_data 字段有什么用呢?该字段用于验证密钥交换和认证过程都是 ok 了;RFC5246的描述是:

Once a side has sent its Finished message and received and validated the Finished message from its peer, it may begin to send and receive application data over the connection.

可以看到,两端只有在发出 Finished 消息、接收对方的 Finished 消息并且验证通过后,才可以在信道中发送和接收应用层数据。

P.S: 似乎 RFC 并没有强调发出 Finished 消息和接收 Finished 的次序...

# 计算 Master Secret

握手阶段的所有协商,最终目的是计算得到 master secret,后者用于传输应用层数据时的加密算法(对称加密算法)的密钥,粗略描述计算过程很简单:

// 48 bytes
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)

# 密码套件

之前的第三弹 理解 HTTPS 对密码套件(Cipher Suite)已有所描述,这一部分再补充一下。下图描述了密码套件的名称构成:

图中的这个套件在不同版本下的 TLS,有不同的解释,对于 TLS 1.2 而言,它描述的信息包括:

  • 密钥交换:ECDHE_RSA
  • 加密:AES_128_GCM
  • MAC(Message Authentication Code,用于计算消息摘要,与完整性保护有关):SHA256
  • PRF(用于计算 master secret):SHA256

# 密钥交换

TLS 1.2 支持的密钥交换算法有很多,譬如 RSA、Diffie-Hellman(DH)、椭圆曲线 Diffie-Hellman 等,对加密而言,我完全是门外汉,本文只是简单介绍 RSA 和 DH 这两种密钥交换算法,目的仍然是帮助更好理解 TLS 协议。

# RSA 密钥交换

RSA 本身是公钥加密算法(非对称加密算法),其密钥交换的核心思想非常简单:客户端产生一个 48 字节的 pre master secret,然后使用 server 的证书的公钥对其进行加密,然后传给 server,server 收到后,使用私钥进行解密;之后,双方使用相同的 pre master secret,在之前协商的 PRF 计算得到 master secret。RFC 的描述是:

When RSA is used for server authentication and key exchange, a 48- byte pre_master_secret is generated by the client, encrypted under the server's public key, and sent to the server. The server uses its private key to decrypt the pre_master_secret. Both parties then convert the pre_master_secret into the master_secret, as specified above.

RSA 非常直接简单,但是存在一个非常严重的弱点。用于加密 pre master secret 的公钥,一般会保持多年不变。如果私钥被窃取了,那么攻击者就可以恢复得到 pre master secret,从而危害会话的安全性。

# Diffie-Hellman 密钥交换

Diffie-Hellman(DH)密钥交换是一种密钥协定的协议,它使两端在不安全的信道上生成共享密钥称为可能。

抛开算法细节,DH 密钥交换需要 6 个参数,其中包括两个域参数(使用 g、p 标记),一般由 server 提供;协商过程中,客户端和服务器各自生成两个参数,对于客户端,使用 c 和 C 标记,对于服务器,使用 s 和 S 标记,c 和 s 是私密的,C 和 S 分别由 c 和 s 计算得来,需要在协商过程中传给对方。对于 TLS 而言,DH 最终产生的目标数即上文提到的 pre master secret,如下图:

DH 密钥交换算法的安全性取决于域参数的质量,域参数由 server 提供,client 对之无能为力。另外,DH 算法是计算密集型任务,容易受到阻塞性攻击,即攻击者请求大量的密钥,受攻击者得花费相当多的资源去做没啥意义的计算,因此 DH 密钥交换通常与身份验证联合使用,以避免中间人攻击。

P.S: 关于 Diffie-Hellman 的更多内容,参考Diffie-Hellman