You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
13 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 26 | 信任始于握手TLS1.2连接过程解析
经过前几讲的介绍,你应该已经熟悉了对称加密与非对称加密、数字签名与证书等密码学知识。
有了这些知识“打底”现在我们就可以正式开始研究HTTPS和TLS协议了。
## HTTPS建立连接
当你在浏览器地址栏里键入“**https**”开头的URI再按下回车会发生什么呢
回忆一下[第8讲](https://time.geekbang.org/column/article/100502)的内容你应该知道浏览器首先要从URI里提取出协议名和域名。因为协议名是“https”所以浏览器就知道了端口号是默认的443它再用DNS解析域名得到目标的IP地址然后就可以使用三次握手与网站建立TCP连接了。
在HTTP协议里建立连接后浏览器会立即发送请求报文。但现在是HTTPS协议它需要再用另外一个“握手”过程在TCP上建立安全连接之后才是收发HTTP报文。
这个“握手”过程与TCP有些类似是HTTPS和TLS协议里最重要、最核心的部分懂了它你就可以自豪地说自己“掌握了HTTPS”。
## TLS协议的组成
在讲TLS握手之前我先简单介绍一下TLS协议的组成。
TLS包含几个子协议你也可以理解为它是由几个不同职责的模块组成比较常用的有记录协议、警报协议、握手协议、变更密码规范协议等。
**记录协议**Record Protocol规定了TLS收发数据的基本单位记录record。它有点像是TCP里的segment所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个TCP包里一次性发出也并不需要像TCP那样返回ACK。
**警报协议**Alert Protocol的职责是向对方发出警报信息有点像是HTTP协议里的状态码。比如protocol\_version就是不支持旧版本bad\_certificate就是证书有问题收到警报后另一方可以选择继续也可以立即终止连接。
**握手协议**Handshake Protocol是TLS里最复杂的子协议要比TCP的SYN/ACK复杂的多浏览器和服务器会在握手过程中协商TLS版本号、随机数、密码套件等信息然后交换证书和密钥参数最终双方协商得到会话密钥用于后续的混合加密系统。
最后一个是**变更密码规范协议**Change Cipher Spec Protocol它非常简单就是一个“通知”告诉对方后续的数据都将使用加密保护。那么反过来在它之前数据都是明文的。
下面的这张图简要地描述了TLS的握手过程其中每一个“框”都是一个记录多个记录组合成一个TCP包发送。所以最多经过两次消息往返4个消息就可以完成握手然后就可以在安全的通信环境里发送HTTP报文实现HTTPS协议。
![](https://static001.geekbang.org/resource/image/69/6c/69493b53f1b1d540acf886ebf021a26c.png)
## 抓包的准备工作
这次我们在实验环境里测试TLS握手的URI是“/26-1”看了上面的图你就可以知道TLS握手的前几个消息都是明文的能够在Wireshark里直接看。但只要出现了“Change Cipher Spec”后面的数据就都是密文了看到的也就会是乱码不知道究竟是什么东西。
为了更好地分析TLS握手过程你可以再对系统和Wireshark做一下设置让浏览器导出握手过程中的秘密信息这样Wireshark就可以把密文解密还原出明文。
首先你需要在Windows的设置里新增一个系统变量“**SSLKEYLOGFILE**”设置浏览器日志文件的路径比如“D:\\http\_study\\www\\temp\\sslkey.log”具体的设置过程就不详细说了可以在设置里搜索“系统变量”
![](https://static001.geekbang.org/resource/image/70/42/70b36338611d5a249a7d2fc448f06d42.png)
然后在Wireshark里设置“Protocols-TLS”较早版本的Wireshark里是“SSL”在“(Pre)-Master-Secret log filename”里填上刚才的日志文件。
![](https://static001.geekbang.org/resource/image/02/e7/0274e31e74e92b61892ec11cc3cd58e7.png)
设置好之后,过滤器选择“**tcp port 443**”就可以抓到实验环境里的所有HTTPS数据了。
如果你觉得麻烦也没关系GitHub上有抓好的包和相应的日志用Wireshark直接打开就行。
## ECDHE握手过程
刚才你看到的是握手过程的简要图我又画了一个详细图对应Wireshark的抓包下面我就用这个图来仔细剖析TLS的握手过程。
![](https://static001.geekbang.org/resource/image/9c/1e/9caba6d4b527052bbe7168ed4013011e.png)
在TCP建立连接之后浏览器会首先发一个“**Client Hello**”消息,也就是跟服务器“打招呼”。里面有客户端的版本号、支持的密码套件,还有一个**随机数Client Random**,用于后续生成会话密钥。
```
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Random: 1cbf803321fd2623408dfe…
Cipher Suites (17 suites)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
```
这个的意思就是:“我这边有这些这些信息,你看看哪些是能用的,关键的随机数可得留着。”
作为“礼尚往来”服务器收到“Client Hello”后会返回一个“Server Hello”消息。把版本号对一下也给出一个**随机数Server Random**然后从客户端的列表里选一个作为本次通信使用的密码套件在这里它选择了“TLS\_ECDHE\_RSA\_WITH\_AES\_256\_GCM\_SHA384”。
```
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 0e6320f21bae50842e96…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
```
这个的意思就是“版本号对上了可以加密你的密码套件挺多我选一个最合适的吧用椭圆曲线加RSA、AES、SHA384。我也给你一个随机数你也得留着。”
然后服务器为了证明自己的身份就把证书也发给了客户端Server Certificate
接下来是一个关键的操作因为服务器选择了ECDHE算法所以它会在证书后发送“**Server Key Exchange**”消息,里面是**椭圆曲线的公钥Server Params**,用来实现密钥交换算法,再加上自己的私钥签名认证。
```
Handshake Protocol: Server Key Exchange
EC Diffie-Hellman Server Params
Curve Type: named_curve (0x03)
Named Curve: x25519 (0x001d)
Pubkey: 3b39deaf00217894e...
Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
Signature: 37141adac38ea4...
```
这相当于说:“刚才我选的密码套件有点复杂,所以再给你个算法的参数,和刚才的随机数一样有用,别丢了。为了防止别人冒充,我又盖了个章。”
之后是“**Server Hello Done**”消息,服务器说:“我的信息就是这些,打招呼完毕。”
这样第一个消息往返就结束了两个TCP包结果是客户端和服务器通过明文共享了三个信息**Client Random、Server Random和Server Params**。
客户端这时也拿到了服务器的证书,那这个证书是不是真实有效的呢?
这就要用到第25讲里的知识了开始走证书链逐级验证确认证书的真实性再用证书公钥验证签名就确认了服务器的身份“刚才跟我打招呼的不是骗子可以接着往下走。”
然后,客户端按照密码套件的要求,也生成一个**椭圆曲线的公钥Client Params**,用“**Client Key Exchange**”消息发给服务器。
```
Handshake Protocol: Client Key Exchange
EC Diffie-Hellman Client Params
Pubkey: 8c674d0e08dc27b5eaa…
```
现在客户端和服务器手里都拿到了密钥交换算法的两个参数Client Params、Server Params就用ECDHE算法一阵算算出了一个新的东西叫“**Pre-Master**”,其实也是一个随机数。
至于具体的计算原理和过程,因为太复杂就不细说了,但算法可以保证即使黑客截获了之前的参数,也是绝对算不出这个随机数的。
现在客户端和服务器手里有了三个随机数:**Client Random、Server Random和Pre-Master**。用这三个作为原始材料,就可以生成用于加密会话的主密钥,叫“**Master Secret**”。而黑客因为拿不到“Pre-Master”所以也就得不到主密钥。
为什么非得这么麻烦,非要三个随机数呢?
这就必须说TLS的设计者考虑得非常周到了他们不信任客户端或服务器伪随机数的可靠性为了保证真正的“完全随机”“不可预测”把三个不可靠的随机数混合起来那么“随机”的程度就非常高了足够让黑客难以猜测。
你一定很想知道“Master Secret”究竟是怎么算出来的吧贴一下RFC里的公式
```
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
```
这里的“PRF”就是伪随机数函数它基于密码套件里的最后一个参数比如这次的SHA384通过摘要算法来再一次强化“Master Secret”的随机性。
主密钥有48字节但它也不是最终用于通信的会话密钥还会再用PRF扩展出更多的密钥比如客户端发送用的会话密钥client\_write\_key、服务器发送用的会话密钥server\_write\_key等等避免只用一个密钥带来的安全隐患。
有了主密钥和派生的会话密钥,握手就快结束了。客户端发一个“**Change Cipher Spec**”,然后再发一个“**Finished**”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。
意思就是告诉服务器“后面都改用对称算法加密通信了啊用的就是打招呼时说的AES加密对不对还得你测一下。”
服务器也是同样的操作,发“**Change Cipher Spec**”和“**Finished**”消息双方都验证加密解密OK握手正式结束后面就收发被加密的HTTP请求和响应了。
## RSA握手过程
整个握手过程可真是够复杂的,但你可能会问了,好像这个过程和其他地方看到的不一样呢?
刚才说的其实是如今主流的TLS握手过程这与传统的握手有两点不同。
第一个使用ECDHE实现密钥交换而不是RSA所以会在服务器端发出“Server Key Exchange”消息。
第二个因为使用了ECDHE客户端可以不用等到服务器发回“Finished”确认握手完毕立即就发出HTTP报文省去了一个消息往返的时间浪费。这个叫“**TLS False Start**”意思就是“抢跑”和“TCP Fast Open”有点像都是不等连接完全建立就提前发应用数据提高传输的效率。
实验环境在440端口[https://www.chrono.com:440/26-1](https://www.chrono.com:440/26-1)实现了传统的RSA密钥交换没有“False Start”你可以课后自己抓包看一下这里我也画了个图。
![](https://static001.geekbang.org/resource/image/cb/d2/cb9a89055eadb452b7835ba8db7c3ad2.png)
大体的流程没有变只是“Pre-Master”不再需要用算法生成而是客户端直接生成随机数然后用服务器的公钥加密通过“**Client Key Exchange**”消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就可以生成主密钥。
## 双向认证
到这里TLS握手就基本讲完了。
不过上面说的是“**单向认证**”握手过程,只认证了服务器的身份,而没有认证客户端的身份。这是因为通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。
但为了防止账号、密码被盗有的时候比如网上银行还会使用U盾给用户颁发客户端证书实现“**双向认证**”,这样会更加安全。
双向认证的流程也没有太多变化,只是在“**Server Hello Done**”之后,“**Client Key Exchange**”之前,客户端要发送“**Client Certificate**”消息,服务器收到后也把证书链走一遍,验证客户端的身份。
## 小结
今天我们学习了HTTPS/TLS的握手内容比较多、比较难不过记住下面四点就可以。
1. HTTPS协议会先与服务器执行TCP握手然后执行TLS握手才能建立安全连接
2. 握手的目标是安全地交换对称密钥需要三个随机数第三个随机数“Pre-Master”必须加密传输绝对不能让黑客破解
3. “Hello”消息交换随机数“Key Exchange”消息交换“Pre-Master”
4. “Change Cipher Spec”之前传输的都是明文之后都是对称密钥加密的密文。
## 课下作业
1. 密码套件里的那些算法分别在握手过程中起了什么作用?
2. 你能完整地描述一下RSA的握手过程吗
3. 你能画出双向认证的流程图吗?
欢迎你把自己的学习体会写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。
![unpreview](https://static001.geekbang.org/resource/image/93/14/93d002084d9bf8283bab3e34e3f4bf14.png)