阅读 1433

https握手流程详解

前言

本文所有的内容都是根据前言中的内容进行讲述。

人物:小蓝、小红、小黑。

事件:通信

加密算法

对称加密

对称加密就是通信的双方都持有同一个密钥,加密和解密都是使用这一个密钥进行的。举个例子:

小蓝和小红互相写情书,但是他们不希望他们之间的信件真实内容被其他人知道。那么他们要怎么保证他们的情书不被其他人知道呢?

因为信件是通过邮局进行邮寄,所以没有办法保证信件在运输途中不被小黑(小黑暗恋小红)阅读过。但是他们只要保证他们之间的信件别人看不懂就可以了,所以他们可以事先约定一个写信的规则,比如,每隔三个字取一个字或者只取每句话中间那个字等等的规则。

通过上面的这些规则,即使信件被小黑阅读了,但是只要小黑不知道这个规则,那么小黑就不知道信件要表达的内容是什么。从而达到了信件的真实内存不被小黑所知道。

优点

加密和解密的速度快,不会造成性能上太大的损失,尤其是在加密大量数据的情况下,更加明显。

弊端

对称加密最大的弊端就是商定加密规则的时候是不安全的。

因为小红和小蓝只能通过信件交流,所以他们的加密规则也是要通过信件进行传输的,但是这个信件是没有加密的,所以这个信件上的加密规则是可以被小黑获取并且解读出来的,只要小黑知道了这个加密规则,那么后面的所有信件的内容,小黑就都可以解读出来了。

为什么要限制小蓝和小红只能通过信件交流,他们不能先私底下商量好加密规则吗?如果在真实生活中确实是可以的,但是如果在我们的互联网环境下,我们的网站是不可能跟所有用户都私底下商量密钥的,我们只能通过一个请求生成一个对称密钥发送给用户,后续的通信双方都使用这个对称密钥对内容进行加密和解密,但是一开始生成并发送密钥的这个请求是明文的,只要是明文的,那么就是有风险的。

非对称加密

非对称加密存在两个密钥,一个称为私钥,一个称为公钥。既可以使用公钥加密,也可以使用私钥加密。但是使用公钥加密,那么就只有私钥才能解密;使用私有加密,就只能使用公钥解密。

继续使用上面的场景,小红和小蓝发现他们的信件总是有被拆开的痕迹,他们意识到他们的信件被第三者阅读过,他们当然不乐意,自己的小秘密怎么可以被第三者亵渎。小蓝发现将密钥明文传输非常不安全,所以它决定将一个保险箱先传递给小红,然后小红将写好的信件放进保险箱中,然后上锁,而打开保险箱的钥匙只有小蓝才有,并且这个钥匙从未被其他人看到过。

其中,保险箱就是公钥,钥匙就是私钥。经过保险箱(公钥)加密的内容,只有钥匙(私钥)才能解开。

优点

公钥是任何人都可以获取的。

经过公钥加密的内容,只有私钥才可以解开。

弊端

必须保证私钥不能泄露,否则将无加密可言。这个弊端还是可以接受的。

非对称加密相对于对称加密而言性能损耗较大,所以一般情况下都会结合非对称加密和对称加密一起使用,https的做法就是如此。

但是非对称加密还有一个最大的漏洞:中间人攻击,如图所示,

我们从中可以看到,最关键的部分就是小黑拦截了小蓝的保险箱,并将它替换为了自己的保险箱,但是小蓝和小红对此是一无所知的,因为小红不知道小蓝发送的保险箱是怎么样的,她只能确定她能收到一个保险箱。而小蓝也不知道自己发送的保险箱被拦截了,因为他收到的回信中确实是他自己的保险箱,也能使用自己的钥匙打开。

https

众所周知,http在网络中传输的报文都是明文的,也就相当于我们是在网络的世界中裸奔。我们只要通过嗅探器Wireshark、tcpdump、dsniff等工具对网络中的信息进行获取,就可以获取到网络上其他人发送的信息。

https是由http协议 + tls / ssl 协议组合而成的。

https = http + tls / ssl

这是我们使用http协议时可以接触得到的网络协议层:

这是我们使用https协议时可以接触得到的网络协议层:

我们可以看到https就是在TCP与HTTP之间加入了一个TLS/SSL层。

握手

以下是访问掘金主页时,发生的网络请求,我通过Wireshark抓包来解析Https的握手过程。我会直接省略tcp的三次握手过程,只关注tls/ssl的握手过程。

Client Hello

IP协议层:左边红色方框的是我的内网地址,它会在访问外网的时候通过路由器的NAT协议转换为外网的地址,右边红色方框就是掘金的外网地址。

TCP协议层:左边红色方框的是我发送这条请求时使用的端口(也叫源端口),我们接下来要使用这个端口(最好加上序号)来寻找服务器回复给我的信息。右边的红色方框就是我要访问的目的端口,这里可以看出Https使用的端口是443,而https的默认端口就是443,http协议的默认端口是80。

我们大致可以理解为,IP是为了找到一台主机,而TCP是为了找到主机上的一个进程。

TLS协议层:比较重要的是蓝色方框中的内容。

  • 它包含了一个客户端可用的版本号(版本是向下兼容的)
  • 一个随机数
  • 一个加密套件(客户端支持的所有的加密套件,在这里包含17个加密套件)

Server Hello

服务端接受到客户端发送的Client Hello后,会立即发送一个该类型的信息。

我们通过红色方框中的地址和端口号,可以确定该信息会回复上面那条Client Hello的。

我们依然看到蓝色方框中的内容:

  • 一个版本号(版本向下兼容)
  • 一个随机数
  • 一个从客户端发送的加密套件中选用的双方都支持的加密套件,该选用的套件很重要,选用不同的套件会对后续的交互产生不一样的动作。

此时客户端和服务端就已经协商好了版本号,密码套件,并且客户端和服务端都拥有了两个随机数。

Server Certificate

紧接着Server Hello信息,服务端会立刻发送一个Server Certificate信息,该信息包含了一个很重要的东西——CA证书,也就是我们平时说的数字证书。

可以看到红色方框中的就是CA证书。

该证书通常有两个目的:

  • 一个是身份验证
  • 一个是证书中包含服务器的公钥,该公钥结合密码套件的密钥协商算法协商出预备主密钥。

Server Key Exchange

该消息是有条件才发送的,还记得刚刚在Server Hello中说的,选择不同的密码套件会有不同的动作产生,该信息的发送就是属于不同的动作。

它发送的条件是,如果证书包含的信息不足以进行密钥交换,那么必须发送该信息。通常协商的加密套件属于下面的类型,就会发送:

  • DHE_DSS
  • DHE_RSA
  • ECDHE_ECDSA
  • ECDHE_RSA

我们可以看到客户端和服务端协商出来的密码套件是属于ECDHE_RSA类型的。所以这里我们可以看到该信息的发送。并且该信息是紧跟着Server Certificate信息的。

客户端每次连接服务器的时候,服务器都会发送动态DH信息(DH参数和DH公钥,DH公钥并不是证书上的那个公钥),这些信息不存在证书中,需要通过Server Key Exchange信息来进行信息传递,并且传递的DH信息需要使用服务器的私钥进行签名。

DH公钥就是Pubkey,签名就是Signatrue,DH参数就是由Curve TypeNamed CurvePubkey组成的。

该信息是用于密钥交换。因为https在真正的通信阶段,是通过对称加密来对内容进行加密的,那么对称加密的密钥交换的安全性就显得非常重要,如果安全的保证客户端和服务端都能获得这个对称加密的密钥就是该信息需要做的事情了。

具体的细节可以自行查看DH算法的流程,这里也提供一些资料:

Server Hello Done

服务器发送完上述信息之后,会立刻发送该信息,然后等到客户端的响应。

该信息的主要作用两点:

  • 服务端发送了足够的信息,接下来可以和客户端一起协商出预备主密钥
  • 客户端接收到该信息后,可以进行证书校验、协商密钥等步骤

Client Key Exchange

在接受到服务器的Server Hello Done信息之后,客户端应该立刻发送该信息。该信息的主要作用是协商出预备主密钥,一般有两种方式:

  • 客户端通过RSA/ECDSA算法加密预备主密钥,然后发送给服务器
  • 通过服务器发送的DH参数计算出客户端的DH公钥,并传递给服务器。

通过抓包信息可以看出,这里使用的是第二种方式。因为这里只将一个DH公钥发送给了服务端。

具体客户端是如何计算出该DH公钥的可以参考DH算法的实现流程,参考链接已经在上面给出。

最终,客户端和服务器会计算出相同的预备主密钥。而该预备主密钥就作为第三个随机数。

我们知道前面已经得到了两个随机数了,最后客户端和服务端会根据这三个随机数计算出对称加密所需要的密钥,因为该密钥是在各自的本地进行计算的,并没有通过网络传输,所以该密钥是安全的。

当然,我们只需要保证第三个随机数是安全的那么对称密钥才是安全的,而DH算法就是用于得到这第三个随机数,至于DH算法如何保证第三个随机数的安全性,请自行查阅。

Change Cipher Spec协议

该协议并不是握手协议中的一部分。客户端和服务端在计算出密钥之后,接下来通知对端,后续的信息都需要TLS记录层协议加密保护了。

也就是该信息用于告诉对方,我已经计算好需要使用的对称密钥了,我们接下来的通信都需要使用该密钥进行加密之后再发送。

需要注意的是,发送该信息的一方并不知道对方是否已经计算出密钥。一般由客户端先行发送该信息。

Finished

该信息是第一个由TLS记录层协议进行加密保护的信息,双方需要验证对方发送的Finished信息,保证协商的密钥是可用的,保证协商过程中,没有被篡改。

验证该verify Data的内容,包括三部分的内容:

  1. 主密钥
  2. 标签,客户端的标签是client finished,服务端的标签是server finished
  3. handshake_messages,包括了所有的握手协议信息。

保密性

其实在客户端与服务端协商处理的密码套件中,是同时包含了多个算法的.

拿我们文章中协商出的密码套件来说:

密码套件 密钥协商算法 身份验证算法 加密算法 Hash 算法 协议
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDHE RSA AES-128-GSM SHA256 TLS

https是如何保证保密性的呢,我们知道对称密码是通过ECDHE算法协商出一个随机数,然后通过随机数1,随机数2,随机数3通过一个计算方法一起计算出一个对称密钥,因为该密钥是在本地计算的,不经过网络传输,所以该对称密钥是安全的。

然后每次的通信都可以使用该对称密钥进行加密和解密了。

但是DH算法如何保证发送的DH公钥是客户端发送的呢?这里也遇到了跟CA证书一样的问题,如何保证CA证书是有效的呢?在CA证书中是通过使用CA的私钥对用户的CRS信息+CA的信息+用户提供的公钥进行一个数字签名,只要浏览器可以使用公钥进行解密,那么就等于该证书是有效的。

同样,DH算法也是如此,发送DH的信息时,服务端会对DH参数和DH公钥使用私钥进行数字签名,只要客户端可以使用公钥进行解密,那么就说明该信息是有效的。客户端发送的信息也是如此。

这里提供一个参考文章:DH算法如何防御中间人攻击

认证

浏览器是如何对CA证书进行验证的?

首先我们先要知道CA证书是如何产生的,首先是申请证书的服务方S需要向第三方CA机构提供CSR(Certificate Signing Request 证书签名请求),第三方机构收到该请求之后,会对该请求中的信息进行核对,线上线下都会进行验证,比如,提供的域名是否属于服务方,地址是否正确等等,只有第三方机构认为你提供的信息都是准确的,它就会根据(你提供的信息+CA机构的信息+公钥)生成一个信息,然后会对该信息进行一个信息摘要,然后使用CA私钥对该信息摘要进行一个数字签名。然后再将生成的信息、数字签名一起生成一个数字证书发布给服务方。

浏览器校验证书的流程:首先在本地电脑寻找一个发布该证书的CA机构的根证书,根证书都是预先安装在操作系统上的,如果有则会对证书上的内容进行验证,比如域名是否一致,证书是否过期等,然后使用CA机构的公钥对该数字签名进行解密,如果解密成功,则说明该证书是有效的。

完整性

客户端如何保证CA证书是完整的,而没有被别人篡改过呢?比如第三方截取到证书之后,对证书的内容进行了修改。

我们知道在生成证书的时候,会对信息生成信息摘要,客户端只要拿到CA证书之后,对证书中的信息进行一次信息摘要的生成,然后与使用数字签名解密出来的内容进行对比,就可以知道证书中的内容是否被篡改过。

问题

中间人有可能篡改证书吗

假如中间人获取到了CA证书,并修改了证书中的域名,但是此时它没有CA机构的私钥,所以它无法得到一个新的签名,然后客户端收到该篡改的证书后,通过私钥解密出来一个原证书信息的摘要,并且客户端对收到的证书内容生成信息摘要,发现两个信息不一致,就会终止通信,防止信息泄露。

中间人有可能替换证书吗

假如中间人也有一个CA机构颁发的合法的证书,中间人拦截到服务端发送的证书,并将自己的证书发送给客户端,此时客户端就会使用中间人证书的公钥进行通信。

其实该情况是不会发生的,因为在验证的过程中会验证域名,如果访问的域名与证书中的域名不一致就会提示不安全的链接。但是如果中间人可以使用你的域名去申请到证书,那就另当别论了,但是这也不会发生,因为证书申请是需要进行信息核实的,并不是可以随意申请的。

数字签名为什么是对hash值进行签名

其实这只是一个性能问题,因为hash值相对于CA证书的信息来说是比较短的值,对该值进行加密和解密都会比较快。通常在生成证书的时候也不会在乎这些时间,但是在浏览器进行解密时,如果时间过长,用户可就等不了那么长的时间了。

怎么证明CA机构的公钥是可信的

其实在CA机构中的公钥都是内置于操作系统的,内置于操作系统中的公钥一定是可信的。如果连他们都不信,那么我们就无法完成CA证书的验证了。

并且CA证书并不一定就是由CA机构颁发的,也可以由CA机构授权的代理商进行发布,也就是说CA机构对它授权的代理商也是信任的。所以我们一般拿到的都是一个三级证书,一级证书就是CA机构的根证书,二级证书就是CA机构授权的代理商的证书,三级证书就是我们自己的证书,这三个证书构成了一个证书链。

我们看到掘金中的证书,就是一个三级的证书链。

HTTPS必须在每次请求中都要先在SSL/TLS层进行握手传输密钥吗

如果每一次https的请求都需要进行TLS的握手,TLS的握手那么复杂,势必会对通信带来较大的延时,这对注重用户体验的网站来说,是不可接受的。那么有什么办法可以避免这种情况吗?

其实是通过一个Session Identifier(会话标识符),该Session ID是 TLS 握手中生成的 Session ID。服务端可以将 Session ID 协商后的信息存起来,浏览器也可以保存 Session ID,并在后续的 Client Hello 握手中带上它,如果服务端能找到与之匹配的信息,就可以完成一次快速握手。