背景
今天逛 medium,发现一篇介绍 SSL/TLS 的文章。讲到握手的时候,突然联想到 TCP 的握手。两个都是握手,还都是网络传输和协议相关的领域。在今年的核心调子:结构化。即知识和思维成体系的影响下,都是网络传输和网络协议领域的两个握手,多好的体系知识的例子啊,一定要放在一起梳理下。遂有了这篇文章。
我们主要阐述 TLS 为主,但是会在 意义目的、结构关系、时间顺序 三个维度上两者一起看,更利于掌握和理解,形成自己的知识结构和体系。就像树突一样,不断的链接在一起
SSL/TLS vs TCP 的目的
SSL/TLS的意义:安全的传输数据:数据安全性
TCP的意义 :可靠的传输数据:数据可靠性
SSL/TLS vs TCP 的位置
在网络体系结构图(如下图)中,SSL/TLS 在应用层;TCP 在运输(传输)层。即
SSL/TLS 在 Http 的 上方
TCP 在 Http 的 下方
发生的时间先后关系
当一台机器向另一台机器发起网络请求时,通过网络体系层次结构,我们知道,先进行TCP连接,在进行Http连接。所以从时间上,先进行 TCP 握手,成功后,再进行 SSL/TLS 握手。
上面从整体概览了 SSL/TLS 和 TCP。下面我们主要说下 SSL/TLS
SSL/TLS 是什么
SSL(安全套接字层:Secure Socket Layer)及其后继者 TLS (传输层安全性:Transport Layer Security)是用于在联网计算机之间建立经过身份验证和加密的链接的协议。具体而言,原来的Http在计算机间联网是明文的,很容易造成传输数据泄漏。所以,在 Http 上加了一层协议,对数据进行加密后传输,这层协议就是 SSL/TLS 。先有的 SSL,后产生的TLS,TLS 取代了 SSL。
TLS (传输层安全性)于1999年发布,是继 SSL(安全套接字层) 认证和加密协议。最新版本是 TLS 1.3(2018年),TLS 1.2 至今仍在广泛使用
SSL 与 TLS 的关系
Netscape 开发了名为安全套接字层(Secure Socket Layer,SSL)的上一代加密协议,TLS 由此演变而来。TLS 1.0 版实际上最初作为 SSL 3.1 版开发,但在发布前更改了名称,以表明它不再与 Netscape 关联。由于这个历史原因,TLS 和 SSL 这两个术语有时会互换使用。
NOTE: 以下就不用 SSL 这个名称了,统一使用 TLS
TLS 和 HTTPS 有什么区别?
HTTPS 是在 HTTP 协议基础上实施 TLS 加密,所有网站以及其他部分 web 服务都使用该协议。因此,任何使用 HTTPS 的网站都使用 TLS 加密。
TLS 握手
TLS 是一种在保护互联网通信安全的加密协议。TLS 握手是启动 TLS 加密通信会话的过程的描述。在 TLS 握手过程中,通信双方要交换消息以相互确认,彼此验证,确立它们将使用的 TLS 版本和密码套件¹(一组加密算法),并生成一致的会话密钥。会话密钥用于 Http协议的通信。这个握手过程也是网络上的两方(例如浏览器和Web服务器)之间的协商过程。TLS 握手是 HTTPS 工作原理的基础部分。
当用户访问一个 HTTPS 的网站时,这时就会发生 TLS 握手。当然,终端访问也一样。例如:$ curl https://www.baidu.com
TLS 握手主要做四件事
- 确定TLS版本
- 确定加密套件,即使用的加密算法
- 获取公钥,同时验证服务器
- 生成会话密钥
NOTE
需要说明的是:握手过程使用的非对称加密算法,而握手后使用会话密钥进行通信时,使用的是对称加密算法
TLS 握手过程
我们通过一张图展示这个握手过程
这个图从人角度出发,形成的握手交互图。文章后面还有从机器角度的握手交互图
上面的过程图是模拟人的思维来进行的;而下面是一张贴近真实机器请求响应的TLS 过程图,其中每一个「框」都是一个记录,多个记录组合成一个 TCP 包发送。所以,最多经过两次消息往返(4 个消息)就可以完成握手,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。(来源于网上)
wireshark 查看 TLS 握手过程
下面我们使用 wireshake 看看真实的请求中,TLS是如何进行握手的,以及握手时传的信息都是啥样的
1. 保持 wireshake 监听状态
这里假设你熟悉 wireshake 的基本操作
终端执行命令:
- $ curl https://www.baidu.com
过滤框输入:
- ip.addr = 110.242.68.3 and tls
效果如下图
我们结合这个实例,看 info
那列
1.〔客户端 -> 服务端〕— 握手协议:Client Hello
主要做三件事
❶ TLS版本:可以看到版本为1.2
❷ 生成的随机数:bdeecfc713fca80c12908cce6f1f91566736f856b7c58d6e29fb158d15db8639
❸ 密码套件列表:可以看到列表的长度是46,即46个密码套件
1 | Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030) |
2.〔服务端 -> 客户端〕— 握手协议:Server Hello
主要做三件事
❶ 确认TLS版本:可以看到版本为1.2
❷ 生成的随机数:625f446320207799678035ac0d11d1c8e3ff0c678b34f0362b1ac2b803f0769f // 用于生成master secret
❸ 选中了一个密码套件:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 // 密码套件包含了 TLS 握手用到的 所有加密算法
密码套件组成:
TLS 握手过程会用到的三个算法,而密码套件指定了 TLS 握手用到的这些加密算法
- ECDHE(EC Diffie-Hellman) 算法:对于 ECDHE 来说,预主密钥是双方通过椭圆曲线算法生成的,双方各自生成临时公私钥对,保留私钥,将公钥发给对方,然后就可以用自己的私钥以及对方的公钥通过椭圆曲线算法来生成预主密钥,所以 ECDHE 生成预主密钥。而主密钥是由预主密钥、ClientHello random 和 ServerHello random 通过 PRF 函数生成的。
- RSA 算法:用于 Server Key Exchange 阶段,此阶段的签名 Signature 是使用 RSA 算法,通过服务端的私钥加密生成的,客户端使用 Server Hello 阶段的证书中的的公钥来验证签名 Signature
- AES 算法:在握手成功后,用于对传输的数据进行加密
可以看到 Server Hello 中的证书信息和浏览器锁头标识中的证书信息是一样的
3.〔服务端 -> 客户端〕— 握手协议:这里是三个握手协议:Certificate, Server Key Exchange, Server Hello Done
❶ 握手协议:Certificate:把证书(包含公钥)下发给客户端
包含:
Certificate: 证书;
subjectPublicKey:公钥
当你用浏览器打开 https://www.baidu.com 时,有个锁头的标识,你点开看下证书,如下图
正如文章开头处的疑问:客户端是如何验证服务端「证书」的合法性的呢?⁴
❷ 握手协议:Server Key Exchange:将 ECDH 参数、签名发给客户端
1 | Handshake Protocol: Server Key Exchange |
EC Diffie-Hellman(ECDH) 算法是一个密钥交换算法。该算法解决了密钥在双方不直接传递密钥的情况下完成密钥交换。该算法也是一个密钥协商算法,双方最终协商出一个共同的密钥,而这个密钥不会通过网络传输。
算法原理如下图1
2
3
4
5
6
7我们来看DH算法交换密钥的步骤。假设甲乙双方需要传递密钥,他们之间可以这么做:
甲首选选择一个素数p,例如509,底数g,任选,例如5,随机数a,例如123,然后计算A=gᵃ mod p,结果是215,然后,甲发送p=509,g=5,A=215给乙;
乙方收到后,也选择一个随机数b,例如,456,然后计算B=gᵇ mod p,结果是181,乙再同时计算s=Aᵇ mod p,结果是121;
乙把计算的B=181发给甲,甲计算s=Bᵃ mod p的余数,计算结果与乙算出的结果一样,都是121。
所以最终双方协商出的密钥s是121。注意到这个密钥s并没有在网络上传输。而通过网络传输的p,g,A和B是无法推算出s的,因为实际算法选择的素数是非常大的。
如果我们把a看成甲的私钥,A看成甲的公钥,b看成乙的私钥,B看成乙的公钥,DH算法的本质就是双方各自生成自己的私钥和公钥,私钥仅对自己可见,然后交换公钥,并根据自己的私钥和对方的公钥,生成最终的密钥secretKey,DH算法通过数学定律保证了双方各自计算出的secretKey是相同的。
算法原理详见 密钥交换算法
- Pubkey
对应到 TLS 上,EC Diffie-Hellman Server Params 中的 Pubkey 就是 A; EC Diffie-Hellman Client Params 中 Pubkey 就是 B。客户端和服务端相互发送各自Pubkey,用于后面生成 “会话密钥(共享密钥)”
NOTE: 注意区分证书里的公钥和这里的Pubkey,详见Public key on Server Key Exchange Generation vs Public key on Server’s Certificate
- Signature
Signature 使用 RSA 算法。Signature = SHA256(client_random + server_random + server_params(curve_info + pub_key)),即服务端使用 私钥 对 client_random + server_random + server_params(curve_info + pub_key) 加密,生成签名 Signature。还记得 Server Hello 那个握手吗,那个握手把 公钥 发给了客户端。所以,在下面的握手 Client Key Exchange 中,就可以使用 公钥 来解密签名 Signature。所以,如果Signature验证通过,可以确保它们是合法的,即可以阻止中间人攻击
❸ 握手协议:Handshake Protocol: Server Hello Done
仅仅是一个「通知」,表明客户端要的信息服务端已给
4.〔客户端 -> 服务端〕— 握手协议:这里是三个握手协议:Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message
在进行此次握手前,客户端要做两件事,只有通过了才进行握手
- 客户端需要验证上一次握手时服务端给的证书,验证通过后,会解析出公钥,用于解密上面握手(Server Key Exchange)中的签名 Signature,目地是阻止中间人攻击
这里衍生出一个问题:如何验证证书
- 验证服务端给的 Signature,客户端如何验证的呢
结合以上的阐述,你应该已经有答案了。即通过证书中的公钥,解密Signature
❶ 握手协议:Client Key Exchange
同样,服务器会将 Diffie-Hellman 算法的 Client Params 参数发给客户端
1 | Handshake Protocol: Client Key Exchange |
这里的 Pubkey 同 Server Key Exchange 握手协议中的 Pubkey。
此时,客户端和服务器都有了 client_params、server_params 参数,所以双方就都可以使用 ECDH(EC Diffie-Hellman) 算法,推导出 “预主密钥”(pre_master_secret)。
然后,客户端和服务端就可以使用”预主密钥”(pre_master_secret),client_random + server_random,通过 AES 算法,得出”主密钥” master_secret。
1 | master_secret = PRF(pre_master_secret, "master secret", |
得到主密钥之后,再将其扩展为一个安全字节序列。1
2
3
4key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
然后分别切分为MAC密钥、对称加密的key和IV。1
2
3
4
5
6client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]
通过这个计算之后,得出会话密钥 session_key。
在每个TLS握手中创建的4个会话密钥是:
- “客户端写入密钥(client_write_key)”
- “服务器写入密钥(server_write_key)“
- “客户端写入MAC密钥(client_write_MAC_key)”
- “服务器写入MAC密钥(client_write_MAC_key)”
客户端写入密钥是客户端用来加密其消息的密钥。客户端写入密钥是对称密钥,客户端和服务器都有。这使服务器可以使用相同的密钥解密来自客户端的消息。
服务器写入密钥与客户端写入密钥相同,只是它处在服务器端。概括来讲:从客户端到服务器的消息使用客户端写入密钥加密,服务器使用客户端写入密钥解密它们。服务器到客户端的消息使用服务器写入密钥加密,客户端使用服务器写入密钥来解密它们。(整个过程由客户端设备或浏览器处理;用户本身不必执行任何加密或解密操作。)
MAC(消息身份验证代码)密钥用于对消息进行数字签名。服务器使用服务器写入MAC密钥对消息进行签名,并且当客户端收到消息时,它可以对照自己的服务器MAC密钥记录检查使用的MAC密钥,以确保其合法性。客户端则使用客户端写入MAC密钥签署消息。
每个新的通信会话和新的TLS握手都会创建一组4个全新的会话密钥。会有一个不同的客户端写入密钥、服务器写入密钥等等,但是每次都会创建这4种类型的密钥。
❷ 握手协议:Change Cipher Spec 变更密码规范协议
一个「通知」,客户端告诉服务端。意思是告诉对方,从现在起,后续的数据都将使用加密传输。
❸ 握手协议:Encrypted Handshake Message
一个「通知」,表明此刻开始是加密后的消息了。也是一次校验,验证下按约定生成的会话密钥是否正确
5.〔服务端 -> 客户端〕— 握手协议:这里是两个握手协议:Change Cipher Spec, Encrypted Handshake Message
❶ 握手协议:Change Cipher Spec 变更密码规范协议
一个「通知」,告诉客户端。从现在起,后续的数据都将使用加密传输。
❷ 握手协议:Encrypted Handshake Message
一个「通知」,表明此刻开始是加密后的消息了。也是一次校验,验证下各自按约定生成的会话密钥是否正确
经过了 SSL 握手后,服务端的身份认证成功,协商出了加密算法为 AES,密钥为 xxxxx(客户端和服务端拿三个随机值用相同算法计算出来的,并没有明文传输)。一切准备就绪。
SSL 握手成功,已经可以对接下来的数据加密了,接下来各种应用层协议都可以加密传输。
6.〔客户端 -> 服务端〕— 应用数据协议(Application Data Protocol): http-over-tls
此时就是客户端传输给服务端的经过加密后的消息了
1 | Encrypted Application Data: 000000000000000177019f75823977b85ee4357006374d828c3caaaa15821b1fe5dacbd9… |
7/8.〔服务端 -> 客户端〕— 应用数据协议(Application Data Protocol): http-over-tls
此时就是服务端传输客户端给的经过加密后的消息了
1 | Encrypted Application Data²: 00000000000000015e0bd6689e934efca74a065706ac68b7bae6292d9579dcc8f038dba1… |
9.〔客户端 -> 服务端〕— Encrypted Alert
1 | Transport Layer Security |
decryption_failed_RESERVED(21)
SSL通信在断开连接时均为发送Encrypted Alert信息给客户端告知要关闭ssl会话了
1 | This “alert” is used in SSL/TLS for notifying to close the connection. So it’s quit normal to see “Encrypted Alert” at the end of a SSL/TLS session. Normally when there is no more data to send, the sender sends this TLS Alert. |
10.〔服务端 -> 客户端〕— Encrypted Alert
和客户端一样
现在,整个 TLS 的握手过程就结束了。此时,我们从机器角度,结合真实的tls请求,梳理如下图(来源于网络)
周边知识点
⁴处:在 TLS 握手过程中,服务端会发给客户端证书,而客户端会验证证书。那怎么验证的呢
在 TLS 握手过程中
Diffie-Hellman算法原理详见 密钥交换算法
我们小结下协商过程:
1 | 客户端:Hi,服务端,我这边支持这些算法,这是我本次的随机数。 |
java 代码演示 TLS
sample-code-illustrating-secure-socket-connection-client-and-server
附录
¹什么是密码套件?
密码套件是一组用于建立安全通信连接的加密算法。(加密算法是对数据执行的一组数学运算,以使数据显得随机。)广泛使用的密码套件有多种,而且 TLS 握手的一个重要组成部分就是对这个握手使用哪一密码套件达成一致意见。
²³处的数据是加密的,不直观。为了学习,我们怎样能看到加密前的信息呢
需要设置下wireshake,详见
使用 Wireshark 调试 HTTP/2 流量