经过前两章的学习,我们知道了通信安全的定义以及TLS对其的实现~有了这些知识作为基础,我们现在可以正式的开始研究HTTPS和TLS协议了。嗯……现在才真正开始。
我记得之前大概聊过,当你在浏览器的地址栏输入一个URL地址会发生什么,大致是浏览器从URI中获取协议名和域名,获取默认端口号,再用DNS解析出IP地址,然后就可以三次握手与网站建立TCP连接了,然后就会立即进行报文的传递。
但是在HTTPS中,三次握手后,还不能立即发送报文,它还需要再用另外一个握手过程,在TCP上建立安全连接,之后才是收发HTTP报文。
这个握手过程与TCP类似,是HTTPS和TLS协议里最重要、最核心的部分,搞懂了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),它非常简单,就是一个“通知”,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。
其中握手协议是最最重要的,也是本章的重点。我们先来看张简要图:
我们来分析下上图,我们可以粗略的看到三种颜色,嗯……没错,我们还可以看到两次数据的往返。嗯……也没错。
首先,图中的每一个圆角框,就是一个record,就是一个记录。多个记录可以组合成一个TCP包发送。所以最多两次往返,4个消息,就可以完成握手,然后底下的紫色部分,就是加密后的信息了。
其次,在TLS握手完成之前,也就是出现ChangeCipherSpec之前,所有的消息都是明文传输的。欸?明文传输,那不会泄露交换的数据么?这个问题我就不回答了,回忆一下我们前两章讲的东西。
最后,我们简要的看词说话一下,首先客户端会发起一个消息,携带了TLS的版本号,客户端随机数,还有一个Cipher Suites,Cipher Suites是啥呢?就是密码套件。还记得我们之前说过,客户端会把它支持的密码套件发送给服务器,让服务器来选择一个。然后呢,服务器会把一大堆东西传给客户端,其中包括TLS版本、服务器的随机数、和Cipher Suite、然后还有其它的消息,比如服务器证书Certificate,比如PubKey,就是服务器的公钥,再有就是传一个Server完事的数据。下一波呢,客户端会传一个PubKey给服务器,还有ChangeCipherSpec以及结束标记发送给服务器。服务器也返回结束。接下来就开始加密传输了。
当然,这是简单的过程,大家先消化一下,接下来我们再看看详细的流程:
嗯……这张图确实有点大。不用怕啦,我们一点一点来分析,其实这张图就是我们本章要讲的重点了,都在这张图里了。
第一部分,很好理解,我相信大家都很熟悉了,就是TCP的三次握手嘛~不说了。
TCP握手完成之后,就是第二部分,也就是第一个来回。我就不截图了,大家看上面的就好了。浏览器会首先发一个“Client Hello”消息,也就是跟服务器“打招呼”。里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于后续生成会话密钥。
代码语言:javascript复制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”。
代码语言:javascript复制Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 0e6320f21bae50842e96…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
然后,服务器为了证明自己的身份,就把证书也发给了客户端(Server Certificate)。
接下来是一个关键的操作,因为服务器选择了 ECDHE 算法,所以它会在证书后发送“Server Key Exchange”消息,里面是椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。
代码语言:javascript复制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。
此刻,客户端拿到了服务器给的证书证明服务器它是它,但是客户端怎么知道这个证书是真的呢?嗯,就是我们之前讲的信任链的追溯了,确认证书的真实性后,再用证书公钥验证签名,就确认了服务器的身份。
然后,我们看第三部分,客户端按照密码套件的要求,也生产一个椭圆曲线的公钥(Client Params),用“Client Key Exchange”消息发给服务器。
代码语言:javascript复制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”,所以也就得不到主密钥。那,为啥要三个随机数来生成Master Secret呢?其实就是为了提高破解的复杂度。
那~~再多说两句,Master-Secret怎么计算出来的呢?
代码语言:javascript复制master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random ServerHello.random)
这是RFC中的计算公式。
这里的“PRF”就是伪随机数函数,它基于密码套件里的最后一个参数,比如这次的 SHA384,通过摘要算法来再一次强化“Master Secret”的随机性。主密钥有 48 字节,但它也不是最终用于通信的会话密钥,还会再用 PRF 扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。
有了主密钥和派生的会话密钥,握手就快结束了。客户端发一个“Change Cipher Spec”,然后再发一个“Finished”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。
服务器也是同样的操作,发“Change Cipher Spec”和“Finished”消息,双方都验证加密解密 OK,握手正式结束,后面就收发被加密的 HTTP 请求和响应了。
最后,我们来看最后一部分,怎么有点奇怪呢?客户端怎么在服务器返回握手结束的消息之前就发送HTTP加密数据了呢?
首先,我们上图中的握手过程,其实是TLS主流握手过程,这与传统的握手过程有两点不同。
第一,是使用ECDHE实现密钥交换,而不是RSA,所以会在服务器端发送”Server Key Exchange“消息。
第二,因为使用了 ECDHE,客户端可以不用等到服务器发回“Finished”确认握手完毕,立即就发出 HTTP 报文,省去了一个消息往返的时间浪费。这个叫“TLS False Start”,意思就是“抢跑”,和“TCP Fast Open”有点像,都是不等连接完全建立就提前发应用数据,提高传输的效率。
所以你看,关键就在于用了ECDHE来作为核心参数生成Master Secret。
二、双向认证
其实到这里TLS握手的核心就基本完事了。只不过大家发现一个问题没有,上图中,只有服务器传了Certificate,让客户端验证服务器的身份。而服务器并没有验证客户端的身份。这是因为通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。
但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现“双向认证”,这样会更加安全。
熟悉不?现在知道为啥你之前去银行的时候,银行会给你个U盾,在网站上操作转账啥的时候,都必须插上U盾才行,现在知道这个U盾是用来干啥的了吧?就是给你本地的电脑安装证书。当然,现在好像基本上不用U盾了,直接从可信的网站上下载证书就可以了。
双向认证的流程也没有太多变化,只是在“Server Hello Done”之后,“Client Key Exchange”之前,客户端要发送“Client Certificate”消息,服务器收到后也把证书链走一遍,验证客户端的身份。大家可以参照本章的图,自己理解一下噢~
三、小结
本篇,很重要,还有点复杂。大家要熟悉一下本章的两张握手图,理解TLS的握手过程。这个总结好像有点糊弄~~哈哈哈
哦对,大家还可以在Chrome浏览器里的Security中查看HTTPS的相关信息。