这么理解TLS协议,以及TLS协议的握手过程

如今HTTPS已被广泛使用,但作为程序员的我们,真的理解这个'S'了吗?如果还没有,这篇入门级介绍或许能帮到你。

在TLS(更早期的版本是SSL)出现之前,很多公司为了保证自家产品客户端与服务端信令交互数据安全,或通过基于TCP协议,自定义支持加解密的二进制应用协议(用于APP),或通过加密HTTP协议请求&响应的Body以确保数据安全。

无论哪种,通常都是采用对称加密算法加密。APP或者WEB前端在打包时将密钥写入,WEB前端直接通过查看JS加解密代码就能获取密钥,APP也能通过反编译获取。也有是通过请求服务端提供的密钥获取接口获取密钥,但这个接口又是明文的。

若要使用对称加密,就需要解决对称加密密钥的安全交付问题,防止被黑客获取利用。因此,对称加密通常用于服务端与服务端之间的openapi交互,由提供接口的一方提供密钥给另一方,密钥通常由人工对接获取。

例如,微信支付openapi采用的签名检验算法,就是一种对称加密检验身份的算法。并且算法生成的token通常会设置很短的过期时间,避免被暴力破解,过期后需要重新生成token。

非对称加密是,用一个密钥加密的内容,需要用另一个密钥解密,即用公钥加密,用私钥解密,用私钥加密用公钥解密。

如果网站采用非对称加密,则由服务端生成一对公钥和私钥,在用户访问网站时,由服务端将公钥发给客户端App或浏览器,客户端App或浏览器拿这个公钥用于加密请求数据,服务端拿私钥解密请求数据,并用私钥加密响应数据。

TLS协议支持双向认证和单向认证,但一般都只使用单向认证。单向认证是只需要客户端验证服务端的真实性,避免用户访问钓鱼网站。双向认证则是服务端也要验证客户端的真实性(本文不讨论)。

使用非对称加密,需要客户端在开始传输加密数据之前,先向网站服务器取得公钥,也就是TLS握手过程中的一步。

使用非对称加密后,就算数据被劫持,由于黑客没有私钥,也无法解密客户端发送给服务器的数据。

虽然黑客无法解密数据,但因为黑客也能拿到公钥,可以解密服务端的响应(窃听),可以伪造成客户端向服务端发送请求(假传圣旨)。

另外,黑客也能伪装成服务端,客户端用黑客的公钥向黑客的网站完成TLS握手。

那么,怎么解决公钥被黑客利用问题呢。很简单,对称加密和非对称加密混合使用不就好了吗。

例如,客户端随机生成一个对称加密的密钥(只是举例),通过服务端公钥加密,再发给服务端,那这个密钥就只有服务端用私钥能解密出来。后续,客户端和服务端就可以使用协商好的对称加密密钥,加密数据再传输了,黑客将再无法伪造数据(假传圣旨),也读不懂客户端和服务端交互的数据(窃听)。

但是,依然存在黑客伪造成服务端的问题。如黑客开发一个钓鱼网站,域名只与某银行网站相差一个字母,让用户误以为就是银行网站,当用户访问这个域名时,客户端与钓鱼网站也能完成TLS握手。

为了解决这个问题,于是就有了CA机构。网站通过CA机构签发证书(证书包含密钥),私钥由网站放在服务器上使用,公钥则在TLS握手阶段由网站传给客户端。

客户端拿到公钥证书后,“询问CA机构”,这个公钥证书与网站域名是否匹配,是否可信,是否在有效期内。

CA机构扮演的是“公安局”的身份。

那么,HTTPS是什么呢,HTTPS就是HTTP+TLS,但不是简单的相加。TLS即传输层安全协议,在TCP协议之上。

在发送HTTP数据包时,HTTP数据包先经过TLS层加密,变成TLS数据包,最后才变成TCP数据包。接收数据也先由TLS层将TLS数据包解密成HTTP数据包。这说明,TLS与TCP协议无关,TLS的握手自然也跟TCP协议三次握手无关,不要混到一起理解。

HTTPS、WSS协议交互,需要在连接建立时,发送请求(HTTP)或数据帧(WS)之前,完成TLS的握手。握手成功后,才能正常发送和接收数据。因此,安全检验是基于连接的,断开连接重新连接后也需要重新TLS握手。这也是TLS影响应用性能的原因。

TLS握手的过程中,服务端解密很消耗CPU,TLS握手也增加连接建立的耗时(TCP握手耗时+TLS握手耗时)。

如何降低TLS对性能的影响,常见的方法有:利用CDN走HTTP回源,在CDN侧将TLS卸载掉;或保持长连接,连接尽可能的复用;另外就是TLS的会话恢复。

这里简单介绍下TLS 1.2的握手流程。

从图中可以看出,整个握手流程共10个步骤,两次RTT。下面介绍每个步骤做的事情(对比图中的步骤有合并)。

第1步:客户端与服务端建立TCP连接后,TLS握手从客户端发送Client Hello开始,此消息包含客户端支持的加密套件和一个随机数。 

第2步:服务端接收客户端的Client Hello消息后,响应Server Hello消息,此消息携带服务端从客户端提供的支持的密钥套件列表中选择使用的加密套件,同时还会携带一个随机数。

第3步:服务端向客户端发送证书,证书包含公钥和签名。第3步是图中服务端向客户端发送Certificate、Server Key Exchange、Server Hello Done消息的合并,因为这三个消息是合并在一个TLS数据包发送的。 

第4步:客户端验证服务端发送的证书,验证通过后,假设选择的加密套件是RSA_WITH_AES_128_CBC_SHA256,那么客户端要生成一个预主密钥(pre-master secret),然后从服务器发送的证书中提取公钥,利用证书的公钥对预主密钥进行RSA加密,再发送给服务端。

此时客户端开始根据发送Client Hello时生成的随机数+服务端Server Hello响应的随机数+客户端生成的预主密钥,生成主密钥,此密钥用于给后续传输的数据进行加解密。

接着,客户端向服务端发送Change Cipher Spec消息,告诉服务端,它后续将切换到使用协商好的加密密钥对数据进行加密再传输。同时也是跟Client Key Exchange(此加密套件需要使用Client Key Exchange消息携带加密后的预主密钥)、Finished消息一起发送,Finished消息使用了该密钥进行加密。

第5步:服务端收到Client Key Exchange消息后,从消息中提取加密过的预主密钥,然后拿证书的私钥解密获取预主密钥。

服务端根据前面客户端发送Client Hello携带的随机数、服务端响应给客户端的随机数,加上这次接收的预主密钥,生成主密钥。

服务端响应Change Cipher Spec消息,告诉客户端,服务器执行相同操作,后续将切换到使用协商好的加密密钥对数据进行加密再传输。同时也是跟Finished消息一起发送,Finished消息使用密钥进行加密。

其中,第1、2、3步是一次往返,第4、5步是一次往返,所以TLS 1.2协议的握手需要两次往返,即2-RTT。

那么,加密套件是什么,用来做什么,以及为什么需要Server Key Exchange消息和Client Key Exchange消息?

加密套件其实就是几类算法的组合,包括签名算法(证书也必须是使用该签名算法的)、数据对称加密算法、协商对称加密密钥的交换算法。

例如加密套件:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA。TLS协议,使用ECDHE做密钥交换算法,RSA为签名算法,AES_128_CBC为数据对称加密算法。

选择何种加密套件,除了根据客户端或服务端优先级外,还会考虑证书使用的签名算法。

例如,当服务器配置RSA签名的证书时,加密套件只能选择签名算法为RSA的加密套件。

如果加密套件选择TLS_ECDHE_RSA_XXX,说明客户端和服务需要使用ECDHE做预主密钥交换算法,不再像RSA_WITH_AES_128_CBC_SHA256那样简单了。

使用ECDHE算法交换预主密钥,握手过程就需要通过Server Key Exchange、Client Key Exchange消息,交换各自本次会话临时生成的密钥对的公钥(不是使用证书的公钥,与证书无关,客户端与服务端都需要各自生成一对公私钥)。用于各自生成对称加密的预主密钥,客户端私钥+服务端公钥,根据椭圆算法算出预主密钥;服务端私钥+客户端公钥,根据椭圆算法算出预主密钥(实际算法实现很复杂)。

两端最终生成的这个预主密钥是相同的,所以最终生成的对称加密密钥肯定也是相同的,否则就不是对称加密了。

可见,TLS证书的公钥和服务器存储的私钥,只用于客户端验证服务端身份。真正用于对数据加密的对称加密密钥,是在握手过程中,根据密钥套件、Server Key Exchange、Client Key Exchange协商出来的。

下图是通过Wireshark抓取的一次TLS 1.2的握手数据包。

前面5个数据包即为TLS 1.2的握手数据包。第一个数据包是Client Hello消息,第二个数据包是Server Hello消息,第三个数据包是服务端响应的Ceritificate、Server Key Exchange、Server Hello Done消息,第四个数据包是客户端发送的Client Key Exchange、Change Cipher Spec和Finshed(Encrypted Handshake Message)消息,第五个数据包是服务端响应的Change Cipher Spec和Finshed(Encrypted Handshake Message)消息。

TLS 1.3握手过程相比以前版本做了不少优化,握手过程耗时降低到1- RTT,客户端发送ClientHello,服务端响应ServerHello之后,握手就完成了,如图所示。(这是通过Wireshark抓包的截图)

ClientHello已经为支持的每种加密套件,生成本该在Client Key Exchange消息传递的参数,不管服务端选择哪种加密套件,后续都节省了一次Client Key Exchange消息,而客户端发送的Change Cipher Spec消息改为在发送应用数据的数据包带上。也不需要Finished消息了。

ServerHello也将Server Key Exchange消息合并在一起发送了。所以总的,就减少了一次RTT。

这里不再展开讨论TLS 1.3的握手细节。想要了解详细的握手过程,以及握手数据包字段的意义,推荐阅读:

猜你喜欢

转载自juejin.im/post/7103050302575607844