『基础巩固』—清晰图解深度分析HTTPS原理
个人语录:
星光不问赶路人,时间不负有心人,以顶级好的态度写一遍博客
Https现在基本已经覆盖所有的http请求了,作为一个伟大的发明,保障了我们的通信安全。在Android中对于HTTPS其实感知不多,因为这些内容都有成熟的框架帮我们完成了,例如okHttp。我们发起一个http或https的请求几乎感受不到区别。
HTTPS的目标就是解决网络通信的安全问题。本文首先阐述网络中存在的风险,然后再讨论其涉及的加密方法、证书验证,TLS握手,浏览器缓存机制解析整个安全连接的流程。
文章目录
HTTPS的工作原理
在没有经过任何加密手段的HTTP通信中,面临着三大危险:消息监听,消息篡改,冒充身份
消息监听
我们发送的消息中间会有经过很多的路由,我们无法确定每一个结点都是安全的可能中间会被人监听我们的发送的消息如图:
解决方案:对通信数据进行加密
消息篡改
在整个网络过程中,我们发送的消息有可能会被监听人,进行篡改:
解决篡改的方法是:利用MD5等hash算法手段来检验数据的完整性 。
冒充身份
HTTP并没验证身份的流程,我们无法保证我们接收到的数据是服务器响应的,服务器也无法鉴别请求的用户是否是恶意用户。如下图:
解决的方法是:使用证书来检验对方的身份 。
HTTP通信面临的这些问题,让我们的网络通信变得极其不安全,HTTPS就是在HTTP的基础上来解决安全问题。
加密算法
HTTP的数据容易被人监听获取,所以我们HTTPS对数据进行了加密,HTTPS的加密有两种实现:
- 对称加密
- 非对称加密
对称加密
对称算法比较简单:加密和解密数据使用相同的密钥
非对称加密
对称算法是加密解密使用相同的密钥,而非对称算法是加密与解密使用不同的密钥 。如下图:
- 非对称加密有两把密钥:公钥和私钥
- 公钥可公开给所有人,私钥必须自己保密,不给任何人拿到
- 客户端可以使用服务器的公钥加密数据,而这份密文,只有服务器的私钥才能解开
- 反过来,使用私钥加密的数据,也只有公钥可以解开
非对称算法很好地解决了对称算法存在的问题:无法安全交换密钥 。服务器的公钥可以公开给所有的用户,当客户端首次访问服务器,服务器便把公钥返回即可。
但是对于非对称算法有一个很严重的缺点:性能极差 。所以我们可以将对称与非对称算法结合起来,解决上述问题。
对称+非对称
对称算法存在的问题是无法安全地互换密钥;因此第一步我们可以使用非对称算法来交换密钥,后续使用对称算法来进行通信。如下图:
- 当客户访问服务器时,服务器返回一个公钥;
- 客户端拿到公钥之后,对客户端密钥使用公钥进行加密之后发送给服务端;
- 服务端拿到客户端密钥之后,使用服务器的私钥解密出客户端的秘钥,作为以后的对称传输的秘钥
这样就完成了双方密钥的交换,后续可以使用密钥进行高效率通信。
到此我们的网络传输依旧不是安全的,因为,我们无法保证第一步服务器返回的公钥不会被黑客篡改。假如黑客把服务器返回的公钥转换成自己的公钥,后续他就可以对客户端的的所有消息使用自己的私钥解密。而问题的本质在于:我们无法辨别返回的数据是否是真的由服务器返回的 。这个问题的解决方法就是:使用数字证书来证明信息发送方的身份 。
数字证书
经过前面加密算法的讨论,对称+非对称算法已经可以解决大部分的网络安全问题。但第一步服务器返回的公钥仍旧有被黑客篡改的风险,因为我们无法确保通信对方的身份。数字证书的引入,就是为了解决这个问题。
数字证书是由公认的证书机构颁发给服务器的一个用于验证身份的数字认证。服务器的证书中,包含有服务器信息例如公钥等、证书签名、证书机构信息等。客户端拿到服务器的证书,进行证书验证后,就可以准确得到服务器的公钥,利用这个公钥,就可以实现上述的算法加密了。总之,数字证书的作用就是证明数据的来源,安全获取到服务器的公钥进行加密通信 。
证书验证
- 服务器向证书机构申请证书,同时提供自己的域名、地址、公钥等信息;
- 证书机构对服务器的信息使用hash算法得出一份128位的摘要,并对这份摘要使用自己的私钥进行非对称加密得到证书数字签名。
- 证书机构把服务器信息(明文)+数字签名+证书机构信息(包含证书机构公钥)发送给服务器
- 客户端请求服务器时,服务器把证书返回给客户端
客户端验证证书的重点就是:比较摘要 :
- 客户端拿到证书,得到服务器信息、数字签名、证书机构信息
- 客户端对服务器信息进行hash算法计算得出一份摘要S1
- 客户端使用证书机构的公钥对数字签名进行解密得到一份摘要S2
- 对比S1和S2即可辨别此证书是否来自服务器且没经过篡改
经过上面的证书验证流程,客户端就可以成功拿到服务器的公钥,进行下一步的加密流程。为什么通过比较摘要即可知道证书安全,下面进行讨论。
hash算法
我们会发现,证书并不是直接对服务器信息进行加密,而是利用hash算法得到服务器信息的摘要,再对摘要进行加密。那这里可能会有这些问题:
- 直接对信息进行加密不可以吗?为什么多此一举?
- 只对摘要进行加密,那么原文内容不是泄露了吗?
hash算法最常用的就是MD5,他可以把一段数据转化成一个128位的长度的摘要,不同的数据,会得到不同的摘要。
摘要的长度更短,使用非对称加密的效率更高。因此,证书中对摘要而不是直接对信息进行加密可以提高网络效率。而服务器信息本身并不是敏感信息,不怕被黑客截取监听,所以可以使用明文传输。
hash算法不仅为了提高效率,更重要的是可以辨别信息是否遭受了篡改。
假如在证书中我们直接对服务器信息进行私钥加密,黑客截取到我们的数据后,他虽然看不懂,但是他可以直接对密文进行篡改。最后接收方解密之后得到的就是一分错误的信息。
此时如果对密文进行hash得到一份摘要,同时对摘要进行加密。客户端拿到数据之后,对密文进行hash再加密,再与服务器发送过来的摘要进行比对即可知道数据是否发生了篡改。黑客不管是修改密文or摘要密文,最后都会导致最后两者的摘要不等。
hash算法的优化
MD5算法是有缺点的,他会发生碰撞。例如一年只有366天,但中国有13亿人口,肯定会有非常多的人生日相同。同理,摘要的长度只有128位,无法唯一表示所有的数据,存在一定的风险:两份不同的数据得到相同的摘要。让黑客变得有机可乘,所以需要引入一种优化方案:HMAC(消息认证码)。
HMAC与MD5的差别在于,他并不是直接对数据进行hash,他还需要一个随机数来共同作用hash,只要保证每次的随机数不同,黑客拿不到随机数,也就无法对hash算法进行破解;即使两次的数据一样,因为随机数不同,最终得出的摘要也不同;这更进一步保证了安全。
但是随机数需要通信双方进行协商拟定,所以在证书中无法使用HMAC。但是在HTTPS安全通信中,则可以加入随机数来实现HMAC,提高安全性。
HTTPS安全模型
我们知道,HTTP 是明文传输的协议,可能受到第三方的攻击,非常不安全。因此才诞生 “HTTPS”。
这个 “S” 表示 SSL/TLS 协议,用公式说明:HTTPS = HTTP + SSL(TLS)。
其中 SSL 即安全套接层(Secure Sockets Layer),处于 OSI 七层模型中的会话层。
- 1996 年,SSL3.0 问世,得到大规模应用(已于 2015 年弃用)。
- 1999 年,SSL3.1 被标准化,更新为 TLS1.0(传输层安全,Transport Layer Security)。所以有,TLS1.0 = SSL3.1。
- 现在主流的版本是 TLS1.2(2008 年发布)。
- 未来,可能是 TLS1.3 的时代(2018 年发布)。
TLS握手
要了解具体的握手流程,首先我们要了解何为握手?
握手 (Handshake)
:一般是通信的前置动作,即达成某种约定,比如 TCP 握手是要确定双端的接收、发送能力等;而 TLS 握手则是为了验证身份、交换信息从而生成秘钥,为后续加密通信做准备。
通过 Wireshark 抓包我们不难发现:不论客户端和服务端的连接走 HTTP 还是 TLS 协议,所有连接最初都要经过 TCP 三次握手,而 TLS 四次握手是在 TCP 建立连接之后进行的。
因此,HTTPS 首次通信需要 7 次握手!
TLS 主要的两种握手方式,分别为:RSA 握手
、DH 握手
。再以 DH 握手
为基础继续演进优化,推出更安全、性能更佳的 TLS1.3
版本握手方式。
RSA 握手
先看图
具体流程如下:
1.浏览器向服务器发送随机数 client_random,TLS 版本和供筛选的加密套件列表。
2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件
以及数字证书 (证书中附带公钥 Public key certificate)。
3.浏览器接收,先验证数字证书。若通过,接着使用加密套件的密钥协商算法 RSA
算法生成另一个随机数 pre_random,并且用证书里的公钥加密,传给服务器。
现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random。两者都用筛好的加密套件中的加密方法混合这三个随机数,生成最终的密钥。
最后,浏览器和服务器使用相同的密钥进行通信,即使用 对称加密
。
到这里,还有两点需要注意。
第一:握手中的任何消息均未使用秘钥加密,它们都是明文发送的。
第二:TLS 握手其实是一个 双向认证
的过程,客户端和服务器都需要进行摘要认证以及收尾 (发送 Finished 消息,意为后面 HTTP 开始正式加密报文通信了)。
DH 握手
还是看图:
具体流程如下(放出 diff 比较):
1.浏览器向服务器发送随机数 client_random,TLS 版本和供筛选的加密套件列表。
// RSA
-2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件
-以及数字证书 (证书中附带公钥)。
// DH
+2.服务器接收到,立即返回 server_random,确认好双方都支持的加密套件
+以及数字证书 (证书中附带公钥)。
+同时服务器利用私钥将 client_random,server_random,server_params 签名,
+生成服务器签名。然后将签名和 server_params 也发送给客户端。
+这里的 server_params 为 DH 算法所需参数。
// RSA
-3.浏览器接收,先验证数字证书。
-若通过,接着使用加密套件的密钥协商算法 RSA 算法
-生成另一个随机数 pre_random,并且用证书里的公钥加密,传给服务器。
// DH
+3.浏览器接收,先验证数字证书和 _签名_。
+若通过,将 client_params 传递给服务器。
+这里的 client_params 为 DH 算法所需参数。
-4.服务器用私钥解密这个被加密后的 pre_random,参考 “非对称加密”。
+4.现在客户端和服务器都有 client_params、server_params 两个参数,
+因 ECDHE 计算基于 “椭圆曲线离散对数”,通过这两个 DH 参数就能计算出 pre_random。
复制代码
现在浏览器和服务器都拥有三样相同的凭证:client_random、server_random 和 pre_random,后续步骤与 RSA 握手一致。
DH 握手前向安全性
TLS1.2 握手
有了前面一节的概念后,TLS1.2 握手理解起来就显得毫不费力了。因为主流的 TLS1.2 握手就是上节完整的 DH 握手流程。
TLS1.3 握手
互联网的世界飞速发展,随着时间的推移,人们早已不满足于现有的 TLS1.2。于是,更快、更安全的 “船新版本” TLS1.3 发布了。
TLS1.3 废除了原有的部分不安全的加密算法,其中甚至包括 RSA
算法。
RSA 算法的废除不仅因为已经有大能将其破解,同时还缺少 前向安全性
。何为前向安全?即能够保护过去进行的通讯不受密码或密钥在未来暴露的威胁。
现在我们来看看 TLS1.3 握手具体流程,先放图:
流程梳理:
// 原 DH 握手
-1.浏览器向服务器发送 client_random,TLS 版本和供筛选的加密套件列表。
// TLS1.3 优化
+1.浏览器向服务器发送 client_params,client_random,TLS 版本和供筛选的加密套件列表。
// 原 DH 握手
-2...
// TLS1.3 优化
+2.服务器返回:server_random、server_params、TLS 版本、确定的加密套件方法以及证书。
+浏览器接收,先验证数字证书和签名。
+现在双方都有 client_params、server_params,可以根据 ECDHE 计算出 pre_random 了。
复制代码
最后,集齐三个参数,生成最终秘钥。
如你所见,TLS1.3 客户端和服务器之间只需要一次往返就完成 (TLS1.2 需要两次往返来完成握手),即 1-RTT
握手。当然,如果利用 PSK
我们甚至能优化到 0-RTT
(这并不好,安全受到质疑~)。
HTTPS的缓存机制
当我们重复请求同一个url,打开控制台,会发现有很多资源是来自缓存。
我们来看下网络状态,大概可以分为下面几种状态。
- memory cache:内存缓存,存在浏览器的进程里面一般包括图片和js
- disk cache: 硬盘缓存,存在浏览器本地。
- 返回码304: 文件没有发生变化(服务端返回)
- 真正的网络请求:进行http网络请求,一般是缓存过期,或者没有缓存,或者不适用缓存等场景。
可以看出来,网络请求存在上述缓存状态,后面我们会解析这些缓存的逻辑和实现机制。
下面是一次返回码304: 文件没有发生变化(服务端返回)的请求头和响应体的分析
其中涉及到如下HTTP首部
- 通用头信息
字段 描述 Cache-Control 缓存控制,具体取值和作用参考下面表格 Pragma 另一种随报文传输的指示方式,并不专用于缓存,当该字段值为no-cache的时候,会客户端不要对该资源读缓存
- Cache-Control取值范围
字段 描述 private 仅客户端可以缓存,代理层不可以访问 public 客户端和代理服务器都可缓存 max-age=xxx 缓存的内容将在 xxx 秒后失效 no-cache 需要使用对比缓存来验证缓存数据 no-store 所有内容都不会缓存,强制缓存,对比缓存都不会触发
- 请求头:
字段 描述 If-Match 比较ETag是否一致 If-None-Match 比较ETag是否不一致 If-Modified-Since 比较资源最后更新的时间是否一致 If-Unmodified-Since 比较资源最后更新的时间是否不一致
- 实体头信息:
字段 描述 ETag 资源的唯一标识信息,资源发生变化会生成新的 Expires 有效期时间,服务端返回的到期时间 Last-Modified 最后一次修改的时间
浏览器缓存逻辑
缓存整体逻辑如下:
- 第一步,先判断是否存在缓存,当存在缓存的时候,判断缓存是否过期。这里需要用到的是缓存响应头里面的Expires和Cache-Control。
- 会优先读取Cache-Control的值,如果存在max-age,将用报文生成时间Date+max-age 和当前时间对比,参看是否过期。如果明确为no-cache,则会不使用缓存,直接请求网络。no-store网络请求结果不会存储在本地,不会产生缓存。
- 如果上述Cache-Control值。比较Expires(HTTP1.0的遗留物)中的过期时间与当前时间。判断是否过期。
- 第二步,如果过期,判断上一次缓存结果是否存在ETag和Last-Modified。如果没有直接进行HTTP请求,如果有则进入下一步。因为这两个值是用于服务器判断缓存是否可用(也就是服务端文件是否发生变化的)
- 第三步,请求头中携带If-None-Match(缓存实体ETag) 和 If-Modified-Since(缓存时间)。主要有以下判断逻辑:
- If-None-Match 携带的是缓存实体的Etag,到达服务端后,服务端首先会判断当前的ETag跟If-None-Match 是否匹配,如果匹配则返回304,浏览器则会读取硬盘缓存,并显示;如果不匹配,则将服务端的实体返回,并将新的ETag写入响应头。
- If-Modified-Since 值为缓存实体里面响应头的Last-Modified 字段,请求头携带这个值带到服务端,服务端会判断当前实体在这个时间后时候发生了变化,如果发生了变化,则将新的请求结果返回,否则返回304,浏览器会使用缓存。
- 如果上述缓存都没有命中,则需要将最新的实体结果通过网络返回浏览器,包括响应头信息。
看官,喜欢的话,点个赞再走叭