<audio id="audio" title="27 | 更好更快的握手：TLS1.3特性解析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ac/aa/ac1b38c2d8b6278fffc5ef099f7edeaa.mp3"></audio>

上一讲中我讲了TLS1.2的握手过程，你是不是已经完全掌握了呢？

不过TLS1.2已经是10年前（2008年）的“老”协议了，虽然历经考验，但毕竟“岁月不饶人”，在安全、性能等方面已经跟不上如今的互联网了。

于是经过四年、近30个草案的反复打磨，TLS1.3终于在去年（2018年）“粉墨登场”，再次确立了信息安全领域的新标准。

在抓包分析握手之前，我们先来快速浏览一下TLS1.3的三个主要改进目标：**兼容**、**安全与性能**。

## 最大化兼容性

由于1.1、1.2等协议已经出现了很多年，很多应用软件、中间代理（官方称为“MiddleBox”）只认老的记录协议格式，更新改造很困难，甚至是不可行（设备僵化）。

在早期的试验中发现，一旦变更了记录头字段里的版本号，也就是由0x303（TLS1.2）改为0x304（TLS1.3）的话，大量的代理服务器、网关都无法正确处理，最终导致TLS握手失败。

为了保证这些被广泛部署的“老设备”能够继续使用，避免新协议带来的“冲击”，TLS1.3不得不做出妥协，保持现有的记录格式不变，通过“伪装”来实现兼容，使得TLS1.3看上去“像是”TLS1.2。

那么，该怎么区分1.2和1.3呢？

这要用到一个新的**扩展协议**（Extension Protocol），它有点“补充条款”的意思，通过在记录末尾添加一系列的“扩展字段”来增加新的功能，老版本的TLS不认识它可以直接忽略，这就实现了“后向兼容”。

在记录头的Version字段被兼容性“固定”的情况下，只要是TLS1.3协议，握手的“Hello”消息后面就必须有“**supported_versions**”扩展，它标记了TLS的版本号，使用它就能区分新旧协议。

其实上一讲Chrome在握手时发的就是TLS1.3协议，你可以看一下“Client Hello”消息后面的扩展，只是因为服务器不支持1.3，所以就“后向兼容”降级成了1.2。

```
Handshake Protocol: Client Hello
    Version: TLS 1.2 (0x0303)
    Extension: supported_versions (len=11)
        Supported Version: TLS 1.3 (0x0304)
        Supported Version: TLS 1.2 (0x0303)

```

TLS1.3利用扩展实现了许多重要的功能，比如“supported_groups”“key_share”“signature_algorithms”“server_name”等，这些等后面用到的时候再说。

## 强化安全

TLS1.2在十来年的应用中获得了许多宝贵的经验，陆续发现了很多的漏洞和加密算法的弱点，所以TLS1.3就在协议里修补了这些不安全因素。

比如：

- 伪随机数函数由PRF升级为HKDF（HMAC-based Extract-and-Expand Key Derivation Function）；
- 明确禁止在记录协议里使用压缩；
- 废除了RC4、DES对称加密算法；
- 废除了ECB、CBC等传统分组模式；
- 废除了MD5、SHA1、SHA-224摘要算法；
- 废除了RSA、DH密钥交换算法和许多命名曲线。

经过这一番“减肥瘦身”之后，TLS1.3里只保留了AES、ChaCha20对称加密算法，分组模式只能用AEAD的GCM、CCM和Poly1305，摘要算法只能用SHA256、SHA384，密钥交换算法只有ECDHE和DHE，椭圆曲线也被“砍”到只剩P-256和x25519等5种。

减肥可以让人变得更轻巧灵活，TLS也是这样。

算法精简后带来了一个意料之中的好处：原来众多的算法、参数组合导致密码套件非常复杂，难以选择，而现在的TLS1.3里只有5个套件，无论是客户端还是服务器都不会再犯“选择困难症”了。

<img src="https://static001.geekbang.org/resource/image/ee/65/eeeb1d30acbc0e69541ce0620346b765.jpg" alt="">

这里还要特别说一下废除RSA和DH密钥交换算法的原因。

上一讲用Wireshark抓包时你一定看到了，浏览器默认会使用ECDHE而不是RSA做密钥交换，这是因为它不具有“**前向安全**”（Forward Secrecy）。

假设有这么一个很有耐心的黑客，一直在长期收集混合加密系统收发的所有报文。如果加密系统使用服务器证书里的RSA做密钥交换，一旦私钥泄露或被破解（使用社会工程学或者巨型计算机），那么黑客就能够使用私钥解密出之前所有报文的“Pre-Master”，再算出会话密钥，破解所有密文。

这就是所谓的“**今日截获，明日破解**”。

而ECDHE算法在每次握手时都会生成一对临时的公钥和私钥，每次通信的密钥对都是不同的，也就是“一次一密”，即使黑客花大力气破解了这一次的会话密钥，也只是这次通信被攻击，之前的历史消息不会受到影响，仍然是安全的。

所以现在主流的服务器和浏览器在握手阶段都已经不再使用RSA，改用ECDHE，而TLS1.3在协议里明确废除RSA和DH则在标准层面保证了“前向安全”。

## 提升性能

HTTPS建立连接时除了要做TCP握手，还要做TLS握手，在1.2中会多花两个消息往返（2-RTT），可能导致几十毫秒甚至上百毫秒的延迟，在移动网络中延迟还会更严重。

现在因为密码套件大幅度简化，也就没有必要再像以前那样走复杂的协商流程了。TLS1.3压缩了以前的“Hello”协商过程，删除了“Key Exchange”消息，把握手时间减少到了“1-RTT”，效率提高了一倍。

那么它是怎么做的呢？

其实具体的做法还是利用了扩展。客户端在“Client Hello”消息里直接用“**supported_groups**”带上支持的曲线，比如P-256、x25519，用“**key_share**”带上曲线对应的客户端公钥参数，用“**signature_algorithms**”带上签名算法。

服务器收到后在这些扩展里选定一个曲线和参数，再用“key_share”扩展返回服务器这边的公钥参数，就实现了双方的密钥交换，后面的流程就和1.2基本一样了。

我为1.3的握手过程画了一张图，你可以对比1.2看看区别在哪里。

<img src="https://static001.geekbang.org/resource/image/4d/b0/4d1df4d07dbb1c2500fc4ea69ecf7ab0.png" alt="">

除了标准的“1-RTT”握手，TLS1.3还引入了“0-RTT”握手，用“pre_shared_key”和“early_data”扩展，在TCP连接后立即就建立安全连接发送加密消息，不过这需要有一些前提条件，今天暂且不说。

## 握手分析

目前Nginx等Web服务器都能够很好地支持TLS1.3，但要求底层的OpenSSL必须是1.1.1，而我们实验环境里用的OpenSSL是1.1.0，所以暂时无法直接测试TLS1.3。

不过我在Linux上用OpenSSL1.1.1编译了一个支持TLS1.3的Nginx，用Wireshark抓包存到了GitHub上，用它就可以分析TLS1.3的握手过程。

<img src="https://static001.geekbang.org/resource/image/7a/db/7a2bc39fdbb421cf72a01e887e9156db.png" alt="">

在TCP建立连接之后，浏览器首先还是发一个“**Client Hello**”。

因为1.3的消息兼容1.2，所以开头的版本号、支持的密码套件和随机数（Client Random）结构都是一样的（不过这时的随机数是32个字节）。

```
Handshake Protocol: Client Hello
    Version: TLS 1.2 (0x0303)
    Random: cebeb6c05403654d66c2329…
    Cipher Suites (18 suites)
        Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
        Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
        Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
    Extension: supported_versions (len=9)
        Supported Version: TLS 1.3 (0x0304)
        Supported Version: TLS 1.2 (0x0303)
    Extension: supported_groups (len=14)
        Supported Groups (6 groups)
            Supported Group: x25519 (0x001d)
            Supported Group: secp256r1 (0x0017)
    Extension: key_share (len=107)
        Key Share extension
            Client Key Share Length: 105
            Key Share Entry: Group: x25519
            Key Share Entry: Group: secp256r1

```

注意“Client Hello”里的扩展，“**supported_versions**”表示这是TLS1.3，“**supported_groups**”是支持的曲线，“**key_share**”是曲线对应的参数。

这就好像是说：

“还是照老规矩打招呼，这边有这些这些信息。但我猜你可能会升级，所以再多给你一些东西，也许后面用的上，咱们有话尽量一口气说完。”

服务器收到“Client Hello”同样返回“Server Hello”消息，还是要给出一个**随机数**（Server Random）和选定密码套件。

```
Handshake Protocol: Server Hello
    Version: TLS 1.2 (0x0303)
    Random: 12d2bce6568b063d3dee2…
    Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
    Extension: supported_versions (len=2)
        Supported Version: TLS 1.3 (0x0304)
    Extension: key_share (len=36)
        Key Share extension
            Key Share Entry: Group: x25519, Key Exchange length: 32

```

表面上看和TLS1.2是一样的，重点是后面的扩展。“**supported_versions**”里确认使用的是TLS1.3，然后在“**key_share**”扩展带上曲线和对应的公钥参数。

服务器的“Hello”消息大概是这个意思：

“还真让你给猜对了，虽然还是按老规矩打招呼，但咱们来个‘旧瓶装新酒’。刚才你给的我都用上了，我再给几个你缺的参数，这次加密就这么定了。”

这时只交换了两条消息，客户端和服务器就拿到了四个共享信息：**Client Random**和**Server Random**、**Client Params**和**Server Params**，两边就可以各自用ECDHE算出“**Pre-Master**”，再用HKDF生成主密钥“**Master Secret**”，效率比TLS1.2提高了一大截。

在算出主密钥后，服务器立刻发出“**Change Cipher Spec**”消息，比TLS1.2提早进入加密通信，后面的证书等就都是加密的了，减少了握手时的明文信息泄露。

这里TLS1.3还有一个安全强化措施，多了个“**Certificate Verify**”消息，用服务器的私钥把前面的曲线、套件、参数等握手数据加了签名，作用和“**Finished**”消息差不多。但由于是私钥签名，所以强化了身份认证和和防窜改。

这两个“Hello”消息之后，客户端验证服务器证书，再发“Finished”消息，就正式完成了握手，开始收发HTTP报文。

虽然我们的实验环境暂时不能抓包测试TLS1.3，但互联网上很多网站都已经支持了TLS1.3，比如[Nginx](https://www.nginx.com/)、[GitHub](https://github.com/)，你可以课后自己用Wireshark试试。

在Chrome的开发者工具里，可以看到这些网站的TLS1.3应用情况。

<img src="https://static001.geekbang.org/resource/image/44/3c/44d8c3349ffdea5a1e4e13d222bc743c.png" alt="">

## 小结

今天我们一起学习了TLS1.3的新特性，用抓包研究了它的握手过程，不过TLS1.3里的内容很多，还有一些特性没有谈到，后面会继续讲。

1. 为了兼容1.1、1.2等“老”协议，TLS1.3会“伪装”成TLS1.2，新特性在“扩展”里实现；
1. 1.1、1.2在实践中发现了很多安全隐患，所以TLS1.3大幅度删减了加密算法，只保留了ECDHE、AES、ChaCha20、SHA-2等极少数算法，强化了安全；
1. TLS1.3也简化了握手过程，完全握手只需要一个消息往返，提升了性能。

## 课下作业

1. TLS1.3里的密码套件没有指定密钥交换算法和签名算法，那么在握手的时候会不会有问题呢？
1. 结合上一讲的RSA握手过程，解释一下为什么RSA密钥交换不具有“前向安全”。
1. TLS1.3的握手过程与TLS1.2的“False Start”有什么异同？

欢迎你把自己的学习体会写在留言区，与我和其他同学一起讨论。如果你觉得有所收获，也欢迎把文章分享给你的朋友。

<img src="https://static001.geekbang.org/resource/image/ab/64/ab532f0074ddb136cd96c76c3a385164.png" alt="unpreview">


