---
传输层安全（TLS）
---

#### 目录

1. 前言
2. 加密、身份验证与完整性
3. TLS 握手
4. TLS 会话恢复
   * 会话标识符
   * 会话记录单
5. 信任链与证书颁发机构
6. 证书撤销
7. TLS 记录协议
8. 针对 TLS 的优化建议
   * 计算成本
   * 会话缓存与无状态恢复
   * TLS 压缩
   * 证书链的长度

#### 前言

SSL（安全套接字层）协议最初是网景公司为了保障网上交易安全而开发的，该协议通过加密来保护客户个人资料，通过认证和完整性检查来确保交易安全。为达到这个目标，SSL 协议在直接位于 TCP 上一层的应用层被实现。SSL 不会影响上层协议（如 HTTP、电子邮件、即时通讯），但能够保证上层协议的网络通信安全。

![](https://i.loli.net/2019/06/24/5d107f3b25c1119330.png)

在正确使用 SSL 的情况下，第三方监听者只能推断出连接的端点、加密类型，以及发送数据的频率和大致数量，不能实际读取或修改任何数据。

IETF 后来在标准化 SSL 协议时，将其改名为 TLS（传输层安全）。很多人会混用 TLS 和 SSL，但严格来讲它们并不相同，因为它们指代的协议版本不同。

#### 加密、身份验证与完整性

TLS 协议的目标是为在它之上运行的应用提供三个基本服务：加密、身份验证和数据完整性。从技术角度讲，并不是所有情况下都要同时使用这三个服务。比如，可以接受证书但不验证其真实性，而前提是你非常清楚这样做有什么安全风险且有防范措施。实践中，安全的 Web 应用都会利用这三个服务。

* 加密

  混淆数据的机制。

* 身份验证

  验证身份标识有效性的机制。

* 完整性

  检测消息是否被篡改或伪造的机制。

为了建立加密的安全数据通道，连接双方必须就加密数据的密钥套件和密钥协商一致。TLS 协议规定了一套严密的握手程序用于交换这些信息，握手机制中设计最巧妙的地方，就是其使用的公钥密码系统（也称 “非对称加密” ），这套系统可以让通信双方不必事先 “认识” 即可商定共享的安全密钥，而且协商过程还是通过非加密通道完成的。

握手过程中，TLS 协议还允许通信两端互相验明正身。在浏览器中，验证机制允许客户端验证服务器就是它想联系的那个，而不是通过名字或 IP 地址伪装的目标。这个验证首先需要建立 “认证机构信任连”（CA）。此外，服务器也可以选择验证客户端的身份。

除了密钥协商和身份验证，TLS 协议还提供了自己的消息分帧机制，使用 MAC（消息认证码）签署每一条消息。MAC 算法是一个单向加密的散列函数（本质上是一个校验和），密钥由连接双方协商确定。只要发送 TLS 记录，就会生成一个 MAC 值并附加到该消息中。接收端通过计算和验证这个 MAC 值来判断消息的完整性和可靠性。

上述三种机制为 Web 通信构建了一个安全的环境。所有现代 Web 浏览器都支持多种加密套件，能够验证客户端和服务器，并能对每条记录进行消息完整性检查。

#### TLS 握手

客户端与服务器在通过 TLS 交换数据之前，必须协商建立加密信道。协商内容包括 TLS 版本、加密套件，必要时还会验证证书。然鹅，协商过程的每一步都需要一个分组在客户端和服务器之间往返一次，因而所有 TLS 连接启动时都要经历一定的延迟。

![](https://i.loli.net/2019/06/25/5d11d4435342785483.png)

* 0 ms：TLS 在可靠的传输层（TCP）之上运行，这意味着首先必须完成 TCP 的 “三次握手”，即一次完整的往返。
* 56 ms：TCP 连接建立之后，客户端再以纯文本形式发送一些规格说明，比如它所运行的 TLS 协议的版本，它所支持的加密套件列表，以及它支持或希望使用的另外一些 TLS 选项。
* 84 ms：然后，服务器取得 TLS 协议版本以备将来通信使用，从客户端提供的加密套件列表中选择一个，再附上自己的证书，将响应发送回客户端。作为可选性，服务器也可以发送一个请求，要求客户端提供证书以及其他 TLS 扩展参数。
* 112 ms：假设两端经过协商确定了共同的版本和加密套件，客户端也高高兴兴的把自己的证书提供给了服务器。然后，客户端会生成一个新的对称密钥，用服务器的公钥来加密，加密后发送给服务器，告诉服务器可以开始加密通信了。到目前为止，除了用服务器公钥加密的新对称密钥之外，所有数据都以明文形式发送。
* 140 ms：最后，服务器解密出客户端发来的对称密钥，通过验证消息的 MAC 检测消息完整性，再返回给客户端一个加密的 “Finished” 消息。
* 168 ms：客户端用它之前生成的对称密钥解密这条消息，验证 MAC，如果一切顺利，则建立信道并开始发送应用数据。

> 新 TLS 连接要完成一次 “完整的握手” 需要两次网络往返。另外，还可以使用 “简短握手”，只需要一次往返，详细信息见下一小节 “TLS 会话恢复”。

协商建立 TLS 安全信道是一个复杂的过程，很容易出错。好在服务器和浏览器会替我们做好这些工作，我们要做的就是提供和配置证书。

总之，尽管我们的 Web 应用还不一定参与上诉过程，但最重要的是知道每一个 TLS 连接在 TCP 握手基础上最多还需要两次额外的往返。这些都会增加实际交换数据之前的等待时间。如果考虑不周，通过 TLS 交付数据很可能会引入几百甚至几千 ms 的网络延迟。

#### TLS 会话恢复

完整 TLS 握手会带来额外的延迟和计算量，从而给所有依赖安全通信的应用造成严重的性能损失。为了挽回某些损失，TLS 提供了恢复功能，即在多个连接间共享协商后的安全密钥。

##### 会话标识符

最早的 “会话标识符” 机制是在 SSL 2.0 中引入的，支持服务器创建 32 字节的会话标识符，并在上一节我们讨论的完整的 TLS 协商期间作为其 “ServerHello” 消息的一部分发送。

在内部，服务器会为每个客户端保存一个会话 ID 和协商后的会话参数。相应地，客户端也可以保存会话 ID 信息，并将该 ID 包含在后续会话的 “ClientHello” 消息中，从而告诉服务器自己还记着上次握手协商后的加密套件和密钥呢，这些都可以重用。假设客户端和服务器都可以在自己的缓存中找到共享的会话 ID 参数，那么就可以进行简短握手。否则，就要重新启动一次全新的会话协商，生成新的会话 ID。

![](https://i.loli.net/2019/06/26/5d130ae1e1db456746.png)

借助会话标识符可以节省一次往返，还可以省掉用于协商共享加密密钥的公钥加密计算。由于重用了之前协商过得会话数据，就可以迅速建立一个加密连接，而且同样安全。

> 实际应用中，大多数 Web 应用会尝试与同一个主机建立多个连接，以便并行取得资源。在这种情况下，会话恢复就成为减少延迟及两端计算量的必备优化手段。
>
> 大多数现代浏览器在打开到相同服务器的新连接之前，都会有意等待上一个 TLS 连接完成。这样，后续的 TLS 连接就可以重用第一个 SSL 会话，从而避免重新握手造成的损失。

由于服务器必须为每个客户端都创建和维护一段会话缓存，“会话标识符” 机制在实践中也会遇到问题，特别是对那些每天都要 “接待” 几万甚至几百万独立连接的服务器来说，问题就更多了。由于每个打开的 TLS 连接都要占用内存，因此需要一套会话 ID 缓存和清除策略，对于拥有很多服务器而且为获得最佳性能必须使用共享 TLS 会话缓存的热门站点而言，部署这些策略绝非易事。

当然，这些问题并非无解，今天的很多高流量站点都在成功的使用会话标识符。只不过对于服务器部署来说，为确保会话缓存的良性循环，必须对会话标识符进行周密的规划和系统的设计。

##### 会话记录单

为了解决上述服务器端部署 TLS 会话缓存的问题，“会话记录单” 机制出台了，该机制不用服务器保存每个客户端的会话状态。相反，如果客户端表明其支持会话记录单，则服务器可以在完整 TLS 握手的最后一次交换中添加一条 “新会话记录单” 记录，包含只有服务器知道的安全密钥加密过的所有回话数据。

然后，客户端将这个会话记录单保存起来，在后续会话的 ClientHello 消息中，可以将其包含在 SessionTicket 扩展中。这样，所有会话数据只保存在客户端，而由于数据被加密过，且密钥只有服务器知道，因此仍然是安全的。

我们这里所说的会话标识符和会话记录单机制，也经常被人称为 “会话缓存” 或 “无状态恢复” 机制。无状态恢复机制的优点主要是消除了服务器端的缓存负担，通过要求客户端在与服务器建立新连接时提供会话记录单简化了部署。

#### 信任链与证书颁发机构

身份验证是建立每个 TLS 连接必不可少的部分。毕竟，加密信道两端可以是任何机器，包括攻击者的机器。为此，必须确保我们与之交谈的计算机是可信任的，否则之前的工作都是徒劳。

假设张三信任李四，李四信任王五，那么通过信任的传递，张三也会信任王五，这就是一个信任链。

Web 以及浏览器中的身份验证与上述过程相同，这就意味着此时此刻你应该问自己：我的浏览器信任谁？这个问题至少有三个答案。

* 手工指定证书

  所有浏览器和操作系统都提供了一种手工倒入信任证书的机制。至于如何获得证书和验证完整性完全由你自己来定。

* 证书颁发机构

  CA（证书颁发机构）是被证书接受者和依赖证书的一方共同信任的第三方。

* 浏览器和操作系统

  每个操作系统和大多数浏览器都会内置一个知名证书颁发机构的名单。因此，你也会信任操作系统及浏览器提供商提供和维护的可信任机构。

实践中，保存并手工验证每个网站的密钥是不现实的。现实中最常见的方案就是让证书颁发机构替我们做这些事：浏览器指定可信任的证书颁发机构（根 CA），然后验证他们部署的每个站点的责任就转移到他们头上，他们会审计和验证这些站点的证书没有被滥用或冒充。持有 CA 证书的站点的安全性如果遭到破坏，那撤销该证书也是证书颁发机构的责任。

![](https://i.loli.net/2019/06/26/5d13116a9859732416.png)

#### 证书撤销

有时候，出于种种原因，证书颁发者需要撤销或作废证书，比如证书的私钥不再安全、证书颁发机构本身被冒名顶替，或者其他各种正常的原因，像以旧换新或所属关系更替等。为此，证书本身包含如何检测其是否过期的指令。为确保信任链不被破坏，通信的任何一端都可以根据嵌入的指令和签名检查链条中每个证书的状态。

##### 证书撤销名单（CRL）

CRL 是 RFC 5280 规定的一种检查所有证书状态的简单机制：每个证书颁发机构维护并定期发布已撤销证书的序列号名单。这样，任何想验证证书的人都可以下载撤销名单，检查相应证书是否榜上有名。如果有，说明证书已经被撤销了。

CRL 文件本身可以定期发布、每次更新时发布，或通过 HTTP 或其他文件传输协议来提供访问。这个名单同样由证书颁发机构签名，通常允许被缓存一定时间。实践中，这种机制效果很好，但也存在一些问题：

* CRL 名单会随着要撤销的证书增多而变成，每个客户端都必须取得包含所有序列号的完整名单
* 没有办法立即更新刚刚被撤销的证书序列号，比如客户端先缓存了 CRL，之后某证书被撤销，那到缓存过期之前，该证书将一直被视为无效

##### 在线证书状态协议（OCSP）

为解决 CRL 机制的上述问题，RFC 2560 定义了 OCSP，提供了一种实时检查证书状态的机制。与 CRL 包含被撤销证书的序列号不同，OCSP 支持验证端直接查询证书数据库中的序列号，从而验证证书链是否有效。总之，OCSP 占用带宽更少，支持实时验证。

然而，没有什么机制是完美无缺的，实时 OCSP 查询也带来了一些问题：

1. 证书颁发机构必须处理实时查询
2. 证书颁发机构必须确保随时随地的可以访问
3. 客户端在进一步协商之前阻塞 OCSP 请求
4. 由于证书颁发机构知道客户端要访问哪个站点，因此实时 OCSP 请求可能会泄露客户端的隐私

#### TLS 记录协议

与位于其下的 IP 或 TCP 层没有什么不同，TLS 会话中交换的所有数据同样使用规格明确的协议进行分帧。TLS 记录协议负责识别不同的消息类型（握手、警告或数据，通过 “内容类型” 字段），以及每条消息的安全和完整性验证。

![](https://i.loli.net/2019/06/26/5d13269e4c2d968524.png)

交付应用数据的典型流程如下：

* 记录协议接收应用数据
* 接收到的数据被切分为块：最大为每条记录 2^14 字节，即 16 KB
* 压缩应用数据（可选）
* 添加 MAC
* 使用商定的加密套件加密数据

以上几步完成后，加密数据就会被交给 TCP 层传输。接收端的流程相同，顺序相反：使用商定的加密套件解密数据、验证 MAC、提取并把数据转交给上层的应用。

同样，值得庆幸的是以上过程都是由 TLS 层帮我们处理，而且对大多数应用都是完全透明的。

#### 针对 TLS 的优化建议

鉴于网络协议的分层架构，在 TLS 之上运行应用与直接通过 TCP 通信没有什么不同。正因为如此，只需要对应用进行很少改动甚至不用改动就可以让它基于 TLS 通信。

不过，你还应该关心 TLS 部署运维的一些方法，比如服务器的部署方式和地理位置、TLS 记录及内存缓存区大小、是否支持简短握手，等等。

##### 计算成本

建立和维护加密信道给两端带来了额外的计算复杂度。特别的，首先有一个非对称（公钥）加密，然后在握手期间，确定共享密钥，作为对后续 TLS 记录加密的对称密钥。

公钥加密与对称加密相比，需要更大的计算工作量。因此，在 Web 发展早期，通常都需要专门的硬件来进行 “SSL” 卸载。好在现在不这样了，现代硬件突飞猛进的发展为减少这种损失提供了强力支持，原先需要专门硬件来做的工作，今天直接通过 CPU 就能完成。

##### 会话缓存与无状态恢复

无论什么情况下，在接近用户的地方终止连接都有助于减少延迟，但有延迟终归快不过没有延迟。启用 TLS 会话缓存和无状态恢复可以完全消除 “回头客” 的往返时间。

SSL 2.0 引入的会话标识符机制是 TLS 会话缓存的基础，目前已经得到大多数客户端和浏览器的广泛支持。然鹅，如果你在自己的服务器上配置 SSL/TLS，千万不能主观认为该机制默认是开启的。实际上，在大多数服务器的默认配置下它是禁用的，所以考量一下是否需要支持。此外，如果客户端和服务器都支持会话记录单，则所有会话数据都将保存在客户端，性能更佳。但是，由于会话记录单还是相对新的 TLS 扩展，并非所有客户端都支持它。实践中，为了取得最优结果，应该做好两手准备：再支持的客户端中使用会话记录单，而在不支持的客户端中使用会话标识符。这两种手段不会相互干扰，而是会很好的协同工作。

##### TLS 压缩

TLS 还有一个内置的小功能，就是支持对记录协议传输的数据进行无损压缩。压缩算法在 TLS 握手期间商定，压缩操作在对记录加密执行之前。然鹅，处于如下原因，实践中往往需要禁用服务器上的 TLS 压缩功能：

* 2012 年公布的 “CRIME” 攻击会利用 TLS 压缩恢复加密认证 cookie，让攻击者实时会话劫持
* 传输级的 TLS 压缩不关心内容，可能会再次压缩已经压缩过得数据

双重压缩会浪费服务器和客户端的 CPU 时间，而且暴露的安全漏洞也很严重，因此请禁用 TLS 压缩。

##### 证书链的长度

验证信任链需要浏览器遍历链条中的每个节点，从站点证书开始递归验证父证书，直至信任的根证书。所以，减少不必要的证书！

