26 信任始于握手:TLS1.2连接过程解析
经过前几讲的介绍,你应该已经熟悉了对称加密与非对称加密、数字签名与证书等密码学知识。
有了这些知识“打底”,现在我们就可以正式开始研究HTTPS和TLS协议了。
HTTPS建立连接
当你在浏览器地址栏里键入“https”开头的URI,再按下回车,会发生什么呢?
回忆一下第8讲的内容,你应该知道,浏览器首先要从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协议。

抓包的准备工作
这次我们在实验环境里测试TLS握手的URI是“/26-1”,看了上面的图你就可以知道,TLS握手的前几个消息都是明文的,能够在Wireshark里直接看。但只要出现了“Change Cipher Spec”,后面的数据就都是密文了,看到的也就会是乱码,不知道究竟是什么东西。
为了更好地分析TLS握手过程,你可以再对系统和Wireshark做一下设置,让浏览器导出握手过程中的秘密信息,这样Wireshark就可以把密文解密,还原出明文。
首先,你需要在Windows的设置里新增一个系统变量“SSLKEYLOGFILE”,设置浏览器日志文件的路径,比如“D:\http_study\www\temp\sslkey.log”(具体的设置过程就不详细说了,可以在设置里搜索“系统变量”)。

然后在Wireshark里设置“Protocols-TLS”(较早版本的Wireshark里是“SSL”),在“(Pre)-Master-Secret log filename”里填上刚才的日志文件。

设置好之后,过滤器选择“tcp port 443”,就可以抓到实验环境里的所有HTTPS数据了。
如果你觉得麻烦也没关系,GitHub上有抓好的包和相应的日志,用Wireshark直接打开就行。
ECDHE握手过程
刚才你看到的是握手过程的简要图,我又画了一个详细图,对应Wireshark的抓包,下面我就用这个图来仔细剖析TLS的握手过程。

在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里的公式:
这里的“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)实现了传统的RSA密钥交换,没有“False Start”,你可以课后自己抓包看一下,这里我也画了个图。

大体的流程没有变,只是“Pre-Master”不再需要用算法生成,而是客户端直接生成随机数,然后用服务器的公钥加密,通过“Client Key Exchange”消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就可以生成主密钥。
双向认证
到这里TLS握手就基本讲完了。
不过上面说的是“单向认证”握手过程,只认证了服务器的身份,而没有认证客户端的身份。这是因为通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。
但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用U盾给用户颁发客户端证书,实现“双向认证”,这样会更加安全。
双向认证的流程也没有太多变化,只是在“Server Hello Done”之后,“Client Key Exchange”之前,客户端要发送“Client Certificate”消息,服务器收到后也把证书链走一遍,验证客户端的身份。
小结
今天我们学习了HTTPS/TLS的握手,内容比较多、比较难,不过记住下面四点就可以。
- HTTPS协议会先与服务器执行TCP握手,然后执行TLS握手,才能建立安全连接;
- 握手的目标是安全地交换对称密钥,需要三个随机数,第三个随机数“Pre-Master”必须加密传输,绝对不能让黑客破解;
- “Hello”消息交换随机数,“Key Exchange”消息交换“Pre-Master”;
- “Change Cipher Spec”之前传输的都是明文,之后都是对称密钥加密的密文。
课下作业
- 密码套件里的那些算法分别在握手过程中起了什么作用?
- 你能完整地描述一下RSA的握手过程吗?
- 你能画出双向认证的流程图吗?
欢迎你把自己的学习体会写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,也欢迎把文章分享给你的朋友。

- 彩色的沙漠 👍(92) 💬(25)
浏览留言区,留言区同学和我有一样的疑问“Client Params 和 Server Params 都可以被截获,为何中间人没法通过这两个信息计算 Pre-Master Secret 呢?” 我去网上找了关于ECDHE握手过程,这个可以帮助大家更好的理解ECDHE,具体过程如下: (1):客户端随机生成随机值Ra,计算Pa(x, y) = Ra * Q(x, y),Q(x, y)为全世界公认的某个椭圆曲线算法的基点。将Pa(x, y)发送至服务器。 (2):服务器随机生成随机值Rb,计算Pb(x,y) = Rb * Q(x, y)。将Pb(x, y)发送至客户端。 (3):客户端计算Sa(x, y) = Ra * Pb(x, y);服务器计算Sb(x, y) = Rb *Pa(x, y) (4):算法保证了Sa = Sb = S,提取其中的S的x向量作为密钥(预主密钥)。 @引用 --------------------- 作者:Mrpre 来源:CSDN 原文:https://blog.csdn.net/mrpre/article/details/78025940 版权声明:本文为博主原创文章,转载请附上博文链接!
2019-08-01 - J.Smile 👍(55) 💬(7)
之前面试阿里第二轮的时候,面试官就问我关于ssl握手的问题,其实我觉得像这种问题回答不出来也很正常,毕竟这么复杂的流程谁能记得住呢?使用现成的nginx+ssl的配置已经可以解决大多数问题了。 ------------------------------------------------- 总结下TLS的握手过程: 第一阶段:C/S两端共享Client Random、Server Random 和 Server Params信息 客户端--->服务器: 客户端的版本号、支持的密码套件,还有一个随机数(Client Random) 服务端--->客户端: 客户端的版本号、选择的客户端列表的密码套件如:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384、随机数随机数(Server Random) 服务端--->客户端: 服务端证书(Server Certificate) 服务端--->客户端: 发送Server Key Exchange类型的请求,携带椭圆曲线的公钥(Server Params)用以实现密钥交换算法,另附私钥签名 服务端--->客户端: 发送完毕 第二阶段:证书验证 前验条件:客户端证书链逐级验证、证书公钥验证签名,服务端身份验证成功(证书合法) 客户端--->服务端 发送Client Key Exchange类型的请求,携带椭圆曲线的公钥(Client Params)用以实现秘钥交换算法 第三阶段:主密钥生成 客户端、服务端分别使用Client Params、Server Params通过ECDHE算法计算出随机值pre-master,然后用 Client Random、Server Random 和 Pre-Master三个值作为原材料,用PRF伪随机数函数(利用密码套件的摘要算法再次强化结果 值maser secert的随机性)计算出主密钥Master Secret, 主密钥并不是会话秘钥,还会再用PRF扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key) 客户端--->服务端: 客户端发一个“Change Cipher Spec”,然后再发一个“Finished”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证. 服务端--->客户端: 服务器也是同样的操作,发“Change Cipher Spec”和“Finished”消息,双方都验证加密解密 OK,握手正式结束.
2020-01-19 - 全麦小面包 👍(21) 💬(1)
老师,我通过nslookup获得百度的一个ip为180.101.49.11,然后用https://180.101.49.11访问,浏览器会提示建立的连接不安全。在chrome浏览器的security页面中,连接走的还是TLS , 但该网页缺失认证。我的理解是,证书在访问网页的时候就返回了,但证书只能证明某个公钥是属于某个域名的,不能证明公钥是否归属某个IP,是不是这样呢?
2019-08-30 - 看,有只猪 👍(19) 💬(3)
老师,你看看我这样理解对吗? ECDHE中,没有采用服务器公钥来加密数据,而是采用交换两端的椭圆曲线公钥来保证pre_master的安全性 RSA中pre_master由客户端生成,采用服务器公钥加密pre_master来保证pre_master的安全性
2019-08-27 - 亚洲舞王.尼古拉斯赵四 👍(17) 💬(1)
第二个问题:客户端使用tcp链接明文将自己的随机数、密码套件、tls版本号发送给服务端,服务端根据自己支持的密码套件从客户端的密码套件中选取一个最合适的密码套件,协商出tls版本,将协商好的密码套件、tls版本以及自己的随机数明文告诉客户端,并将自己的证书发送给客户端,并结束 客户端收到证书之后去ca一级一级验证证书的有效性,验证通过后,客户端使用随机数生成pre-master并 用服务器的公钥进行加密传给服务端,服务端使用自己的私钥进行解密,使用解密后的值与客户端随机数,自己的随机数进行计算,得出master secret;这时候,客户端使用三个值也能计算出master secret,客户端告诉服务器我之后都使用加密进行通信了,结束;服务端也告诉客户端,我也要开始使用加密通信了,over 接下来两个人使用计算出来的master secret进行消息加密,两人balabala,并使用master secret进行解密
2019-08-07 - magicnum 👍(14) 💬(3)
比如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:使用ECDHE进行密钥交换(文中已经讲了,用它算出Pre_Master,成会话密钥Master Secret。密钥交换过程中应该会使用到非对称加密中的公钥加密),RSA进行身份验证(私钥加密公钥解密),使用128位GCM分组工作模式的AES进行消息和会话密钥的对称加密(加密真正的消息),使用SHA256摘要算法(如HMAC、PRM)对数据签名,保证数据完整性
2019-07-26 - 追风筝的人 👍(13) 💬(1)
在非对称加密算法中,公钥是公开信息,不需要保密,那用私钥加密,公钥解密的话(验证签名过程),其他知道公钥的人也可以解密,怎么确认发送者的身份?
2020-05-11 - 乐潇游 👍(8) 💬(4)
“主密钥有 48 字节,但它也不是最终用于通信的会话密钥,还会再用 PRF 扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。”这个没太理解,这个不一样的会话密钥,在对称加密算法中怎么解密呢?
2020-10-22 - lesserror 👍(8) 💬(1)
老师,以下问题,麻烦回答一下: 1.为了更好地分析 TLS 握手过程,你可以再对系统和 Wireshark 做一下设置,让浏览器导出握手过程中的秘密信息,这样 Wireshark 就可以把密文解密,还原出明文。这不是明文传输的嘛? Wireshark 就可以把密文解密这句话是不是有问题啊? 2.浏览器直接发送的TLS1.2的版本,那么为什么只发这个,不发TLS1.3的呢? 3.这里服务器是不是有两套公私钥,一个是证书的公私钥,一个是椭圆曲线的公私钥匙。服务器在证书后发送“Server Key Exchange”消息之后的签名用的是证书的私钥加密的?
2019-12-18 - 刘政伟 👍(8) 💬(1)
老师,还是没有明白服务端/客户端发送public key的用途是什么,麻烦老师再重点说一下,感谢!
2019-08-17 - Ben 👍(7) 💬(1)
有个疑问没有想清楚:client在发送“client key exchange”消息之前需要把client的证书发送给server吗? 我在想server发送给client的“server key exchange”消息需要签名认证,那么client发送给server的“client key exchange”难道不需要签名认证吗? 如果需要签名认证的话,那么是不是就需要先把client的证书发送给server做验证。
2019-12-10 - 钱 👍(6) 💬(1)
😅分水岭来了,我看了三遍还是没太明白,先跳过,小本本记一下,之后在回头看看。
2020-04-04 - 乘风破浪 👍(5) 💬(1)
为了验证双向验证,抓了一下招商银行u盾,奇怪的是没有抓到任何客户端证书相关的消息。其它和ecdhe流程一致。 验证百度首页,它用的是tls1.2,过程大师说的连接过程一致,一点小区别是,记录Server Heelo几个记录,分散在几个tcp包里,不知道是基于什么考虑?这么做,不是浪费资源吗? tls连接过程,貌似不是很复杂,主要是有些细节需要琢磨。 简单说,就是交换对称的秘钥,并验证对方的身份。 交换对称秘钥,是由3个数算出来的 pre-master,c,s 其中pre-master根据ecdhe或rsa有区别 ecdhe,由临时产生的公钥算出来的,虽然是明文,但算法可以保证黑客拿到也算不出。 验证身份就是验证对方传过来的证书 rsa, 客户端产生随机数,rsa公钥加密传到对端,这种方式不具备前向安全。 一个疑问,请问大师,客户端client exchange中传递的public key甚至都没有签名,就一个明文,这怎么保证不被黑客篡改利用?
2021-02-22 - Teresa 👍(5) 💬(3)
有几个疑问一直想不明白,还请老师赐教: 1.客户端怎么验证服务端发过来的证书就是可信的?客户端在验证服务端发过来得证书得时候,客户端是直接拿取自己系统内得CA根证书,根证书里包含解密一级证书的根公钥(或者他自己就是公钥?),毕竟是证书链,所以它会先拿证书链表的第一个结点,通过刚才的根公钥进行解密(这个解密是他自己有一套算法吗?RSA或者ECC?它解密的过程还需要其他的参数吗?),解密出来的东西是一级证书包含的公钥和描述信息,然后用解密出来的公钥解密第二个结点,解出服务器的公钥和描述信息。只要最后能解析出来一个公钥和其他的描述就认为验证成功了吗? 2.我看整个流程下来,后面就没有解出来的证书公钥什么事了,所以这个证书的公钥在整个流程中还有其他的作用吗? 3.在server key exchange 那步,服务端给出的私钥签名认证是什么的私钥?客户端用它来做什么?
2020-04-27 - 俊伟 👍(5) 💬(3)
握手过程:1.首先客户端发起连接,发送client hello。里面内容包括tls版本号,客户端用于加密的随机数,客户端支持的安全套件信息。 2.服务端收到客户端的请求,发送server hello。包括服务端生成的用于加密的随机数,选择的客户端的加密套件,也就是TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384。然后是server certificate发送服务端的证书信息。之后是server key exchange,里面包含生成对称加密的参数,也就是ECDHE算法生成的公钥,然后服务器用选择的摘要算法(SHA512)将整体信息进行摘要计算,用公钥进行加密,这样做是使用公钥来表示身份防止篡改。然后server hello done结束。 3.客户端收到请求之后,先根据服务端发送的证书链进行服务端的公钥认证,确认身份。确认身份无误后,根据根据自己本地ECDHE算法生成的Pubkey和服务端的server params也就是服务端的pubkey,生成pre master 。然后根据上两步得到的两个随机数生成master secret。计算的同时发送client key exchange,也就是客户端的参数,ECDHE算法生成的公钥。这里由于是单项认证,客户端无需使用生成摘要表明身份。然后发送change cipher spec和finished。 4.服务端收到客户端的请求之后,也类似与客户端,根据收到的client params生成pre master,然后再生成master secret。然后发送change cipher spec和finished。最终完成安全握手连接。 综上: 1.选择的加密套件,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384中,ECDHE的任务是进行密钥交互,RSA的任务是身份认证与不可否认。AES是265位的密钥,GCM分组模式,它的任务是保存通信安全。最后SHA384的任务是进行摘要计算,保证完整性。有点类似于设计模式里面的单一职责原则,每种算法负责一种职责。 2.RSA加密过程与此类似,只是没有server key exchange。而是由客户端生成pre master, 发送client key exchange请求到服务端。参考了答疑篇,这样生成的密钥不具有向前安全。由于密钥是客户端固定生成的,随着时间的增加被破解的风险会越来越高。
2020-01-10