huan89

前言

很高兴遇见你~

Https现在基本已经覆盖所有的http请求了,作为一个伟大的发明,保障了我们的通信安全。在Android中对于HTTPS其实感知不多,因为这些内容都有成熟的框架帮我们完成了,例如okHttp。我们发起一个http或https的请求几乎感受不到区别。

但最近在研究okHttp的源码的时候,发现很多的内容没看懂,最后发现是http相关的网络知识不扎实,再一次回过头来,把https学了一遍。正如前面所说,得益于框架,我们几乎不需要学习https背后到底发生了什么,但是发生了相关的bug也就无法修复(面试要问[狗头])。所以,作为一个android开发者,也还是很有必要学一下https。

HTTPS的目标就是解决网络通信的安全问题。本文首先阐述网络中存在的风险,然后再讨论其涉及的加密方法、证书验证,最后再同从请求的角度解析整个安全连接的流程。

网络存在的风险

在没有经过任何加密手段的HTTP通信中,面临着三大危险:消息监听、消息篡改、冒充身份

消息监听

我们发送的消息需要经过很多的中间路由器,我们无法确保网络中每一个节点都是安全的,所以我们发送的数据会被恶意的对象截取到。假如我们的消息没有经过任何加密,那么恶意用户就可以监听到我们通信的所有数据。如下图:

image.png

解决的方法是:对通信数据进行加密。如下图:

image.png

经过加密的数据,即时被黑客截取到,他也无法知道数据的内容。

消息篡改

第二个危险是消息篡改。我们发出的数据会经过危险的中间节点,黑客可以监听我们的数据,也可以对我们的数据进行修改。如下图:

image.png

解决篡改的方法是:利用MD5等hash算法手段来检验数据的完整性 。下面会详解。

冒充身份

HTTP并没验证身份的流程,我们无法保证我们接收到的数据是服务器响应的,服务器也无法鉴别请求的用户是否是恶意用户。如下图:

image.png

解决的方法是:使用证书来检验对方的身份

HTTP通信面临的这些问题,让我们的网络通信变得极其不安全,HTTPS就是在HTTP的基础上来解决安全问题。

加密算法

加密算法依旧是HTTPS安全通信中的重头戏。在理想的情况下,如若有一个加密算法使得仅有用户和服务可以加密解密,那么其实是不存在上面的安全问题的。但黑客本身,他也可以作为一个客户存在,普通客户可以加密解密,那么黑客也就可以做到。所以需要附加上动态因子来保证算法的安全。

这里解释一下什么是动态因子算法(这个名字我自己起的,仅仅为了帮助理解)

假如现在需要发送的数据是:123
算法是:数据+动态整数

现在通信双方商量的动态因子是:5,那么

  1. 发送方对数据进行加密:123+5=128
  2. 接收方对数据进行解密:128-5 =123

即使黑客知道具体的算法就是数据+动态整数,但是他不知道具体的动态整数是多少,也就无法解出原始的数据内容。这个动态整数称之为密钥

下面介绍HTTPS中用到的加密算法。

对称算法

对称算法比较简单:加密和解密数据使用相同的密钥 。如下图:

image.png

对称算法的优点就是效率很高,可以对长数据进行加解密。但对称算法也存在缺点。

第一是双方使用相同的密钥,无法辨别数据到底是由服务器加密还是客户端加密,也就是无法区分一个消息是由服务器发出还是由客户端发出。解决这个问题方法也很简单:双方加密使用不同的密钥

第二,通信双方难以确保拿到安全的密钥 。因为第一步总是需要通过网路通信来商量密钥,那可不可以使用固定的密钥?前面讲过,黑客也是一个客户,那么他也可以拿到密钥,这个算法就失去意义了。

解决这个问题的方法是:使用非对称算法

非对称算法

对称算法是加密解密使用相同的密钥,而非对称算法是加密与解密使用不同的密钥 。如下图:

image.png

  1. 非对称加密有两把密钥:公钥和私钥
  2. 公钥可公开给所有人,私钥必须自己保密,不给任何人拿到
  3. 客户端可以使用服务器的公钥加密数据,而这份密文,只有服务器的私钥才能解开
  4. 反过来,使用私钥加密的数据,也只有公钥可以解开

非对称算法很好地解决了对称算法存在的问题:无法安全交换密钥 。服务器的公钥可以公开给所有的用户,当客户端首次访问服务器,服务器便把公钥返回即可。

但是对于非对称算法有一个很严重的缺点:性能极差 。所以我们可以将对称与非对称算法结合起来,解决上述问题。

对称+非对称

对称算法存在的问题是无法安全地互换密钥;因此第一步我们可以使用非对称算法来交换密钥,后续使用对称算法来进行通信。如下图:

image.png

  1. 当客户访问服务器时,服务器返回一个公钥;
  2. 客户端拿到公钥之后,对客户端密钥使用公钥进行加密之后发送给服务端;
  3. 服务端拿到客户端密钥之后对服务端密钥进行加密发送给客户端;

这样就完成了双方密钥的交换,后续可以使用密钥进行高效率通信。

到此我们的网络传输依旧不是安全的,因为,我们无法保证第一步服务器返回的公钥不会被黑客篡改。假如黑客把服务器返回的公钥转换成自己的公钥,后续他就可以对客户端的的所有消息使用自己的私钥解密。而问题的本质在于:我们无法辨别返回的数据是否是真的由服务器返回的 。这个问题的解决方法就是:使用数字证书来证明信息发送方的身份

数字证书

经过前面加密算法的讨论,对称+非对称算法已经可以解决大部分的网络安全问题。但第一步服务器返回的公钥仍旧有被黑客篡改的风险,因为我们无法确保通信对方的身份。数字证书的引入,就是为了解决这个问题。

证书概述

数字证书是由公认的证书机构颁发给服务器的一个用于验证身份的数字认证

数字证书可以用身份证来进行类比:

身份证是我们自身身份信息的一个认证,颁发的机构是我们全国人民认可的*局。
同理,服务器的数字证书也是服务器身份的一个认证,颁发的机构是互联网中普遍认可的证书机构。

服务器的证书中,包含有服务器信息例如公钥等、证书签名、证书机构信息等。客户端拿到服务器的证书,进行证书验证后,就可以准确得到服务器的公钥,利用这个公钥,就可以实现上述的算法加密了。

总之,数字证书的作用就是证明数据的来源,安全获取到服务器的公钥进行加密通信

证书验证

客户端如何验证服务器的证书呢?首先得看看证书是怎么做出来的:

image.png

  1. 服务器向证书机构申请证书,同时提供自己的域名、地址、公钥等信息;
  2. 证书机构对服务器的信息使用hash算法得出一份128位的摘要,并对这份摘要使用自己的私钥进行非对称加密得到证书数字签名
  3. 证书机构把服务器信息(明文)+数字签名+证书机构信息(包含证书机构公钥)发送给服务器
  4. 客户端请求服务器时,服务器把证书返回给客户端

客户端验证证书的重点就是:比较摘要

  1. 客户端拿到证书,得到服务器信息、数字签名、证书机构信息
  2. 客户端对服务器信息进行hash算法计算得出一份摘要S1
  3. 客户端使用证书机构的公钥对数字签名进行解密得到一份摘要S2
  4. 对比S1和S2即可辨别此证书是否来自服务器且没经过篡改

经过上面的证书验证流程,客户端就可以成功拿到服务器的公钥,进行下一步的加密流程。至于为什么通过比较摘要即可知道证书安全,下面进行讨论。

证书链

客户端验证证书的流程很简单:使用证书机构公钥解开证书的数字签名后进行比对即可。但这里有一个问题:如何保证证书机构的公钥可信 ?假如黑客使用自己的私钥加密,同时把证书机构的公钥修改成自己的公钥,那岂不是非常危险?

互联网中的主机对象非常多,但证书机构却不多。计算机产商,会在系统中安装一些根证书机构的信息,其中就包含了这些机构的公钥。这些公钥是在一定程度上是绝对安全的,是可以信任的。客户端可以使用这些公钥对数字签名进行解密。安全问题,终于得到了完美的解决。

系统中预装的证书机构是有限的,但世界上每时每刻申请数字证书却非常多,他们“忙不过来”,因此有了二级证书机构。二级证书机构由根证书机构签发,二级证书机构再去给服务器签发证书。那么此时如何进行证书验证呢?还是一样的道理:

  1. 利用根证书机构给二级证书机构签发的时候同样是一份数字证书,其中包含了二级证书机构信息、数字签名、根证书机构信息
  2. 服务器的数字证书中包含了二级证书机构的数字证书
  3. 客户端使用根证书机构的公钥对二级证书机构的数字签名进行解密得到摘要再进行比对,得到二级证书机构的公钥
  4. 使用二级证书机构的公钥对服务器证书进行验证

同理,三级、四级证书机构验证都类同。在浏览器中,我们可以查看网站的证书链:

image.png

可以看到这是一个包含了两级证书机构的证书链,最顶层的证书机构,即是根证书机构。

hash算法

我们会发现,证书并不是直接对服务器信息进行加密,而是利用hash算法得到服务器信息的摘要,再对摘要进行加密。那这里可能会有这些问题:

  1. 直接对信息进行加密不可以吗?为什么多此一举?
  2. 只对摘要进行加密,那么原文内容不是泄露了吗?

hash算法最常用的就是MD5,他可以把一段数据转化成一个128位的长度的摘要,不同的数据,会得到不同的摘要。

摘要的长度更短,使用非对称加密的效率更高。因此,证书中对摘要而不是直接对信息进行加密可以提高网络效率。而服务器信息本身并不是敏感信息,不怕被黑客截取监听,所以可以使用明文传输。

hash算法不仅为了提高效率,更重要的是可以辨别信息是否遭受了篡改

假如在证书中我们直接对服务器信息进行私钥加密,黑客截取到我们的数据后,他虽然看不懂,但是他可以直接对密文进行篡改。最后接收方解密之后得到的就是一分错误的信息。

如果信息是一个文本,我们可以很明显地辨别出来;但如果是一个数字编号,那么很难知道是否遭受了篡改。举个例子:

  1. 服务器发送货物编号123,对123进行加密之后得到098
  2. 黑客截取后无法解密,将098修改成048之后发送给客户端
  3. 客户端解密048之后得到129,数据遭受了篡改;虽然黑客不知道我们发送什么,但是可以让我们的业务发生错误

此时如果对密文进行hash得到一份摘要,同时对摘要进行加密。客户端拿到数据之后,对密文进行hash再加密,再与服务器发送过来的摘要进行比对即可知道数据是否发生了篡改。黑客不管是修改密文or摘要密文,最后都会导致最后两者的摘要不等。

hash算法的优化

MD5算法是有缺点的,他会发生碰撞。例如一年只有366天,但中国有13亿人口,肯定会有非常多的人生日相同。同理,摘要的长度只有128位,无法唯一表示所有的数据,存在一定的风险:两份不同的数据得到相同的摘要。让黑客变得有机可乘,所以需要引入一种优化方案:HMAC(消息认证码)

HMAC与MD5的差别在于,他并不是直接对数据进行hash,他还需要一个随机数来共同作用hash,只要保证每次的随机数不同,黑客拿不到随机数,也就无法对hash算法进行破解;即使两次的数据一样,因为随机数不同,最终得出的摘要也不同;这更进一步保证了安全。

但是随机数需要通信双方进行协商拟定,所以在证书中无法使用HMAC。但是在HTTPS安全通信中,则可以加入随机数来实现HMAC,提高安全性。

安全模型

这一小节主要讲一下HTTPS为我们建立的宏观安全模型。

需要特别注意的是,HTTPS并不是一个新的应用协议来取代HTTP,而是在HTTP的基础上,增加了网络安全的内容。HTTPS的全称:Hyper Text Transfer Protocol over SecureSocket Layer,建立在安全socket层次上的超文本传输协议,可以认为HTTPS = HTTP+SSL。HTTPS与HTTP、TCP的关系如下:

image.png

HTTPS在HTTP和TCP之间建立了一个安全连接层 。SSL/TLS层次和TCP很类似,双方建立TCP连接之后,需要再建立安全连接。与TCP连接一样,SSL连接本质上,是对双方安全信息的记录,并不是一个真正意义上的连接。HTTP通过安全连接,即可与目标主机进行安全的通信,不怕被监听、篡改、冒充身份。

这里的SSL与TLS指的都是安全协议。SSL全名Secure Sockets Layer,安全套接字层协议;TLS全名Transport Layer Security,安全传输层协议。TLS从SSL发展而来,SSL是早期的安全层协议;后期逐渐发现了其安全漏洞,发展出了TLS。现在使用的最多的是TLS1.2、TLS1.3版本,如我们查看掘金的证书:

image.png

可以看到使用了TLS1.2版本。安全协议版本需要通信双方进行协商,只有使用相同版本的协议,才能建立安全连接。

此外,建立安全连接是比较消耗性能的。如果每次请求都建立一次安全连接,那么网络的效率将会大打折扣。因此,在建立一次安全连接之后,服务器会存储客户端的安全相关信息,在一定时间内通信时无需再次建立安全连接,服务器会把先前的密钥等信息发送给客户端,直接使用此前已经记录的安全信息即可。

安全连接建立流程

和TCP连接类同,安全连接也需要一个建立的流程。但是经过了前面HTTPS加密算法以及证书体系的学习,理解HTTPS安全连接建立流程就非常简单了。基本就是把上面的流程走了一遍。先来看一张总体图:

image.png

  1. 客户端请求服务器建立安全连接,附加客户端支持的SSL与TLS版本、支持的加密算法版本、随机数

    加密算法与安全协议版本有很多,但服务不一定支持最新版本的协议预算法。所以客户端把所以支持的版本发送给服务器,让服务器去选择。

    随机数非常重要,前面讲hash算法的时候讲到,随机数是一个动态因子,让hash算法更加安全。同时,随机数也参与了对称密钥的生成。

  2. 服务器响应请求,附加选择的协议版本、加密算法版本、服务器随机数

    服务器从客户端支持的协议版本中,选择一套自己最喜欢的。

    为了辨别消息是由哪一方加密并发出的,需要准备两个对称密钥。因此服务器也需要产生一个随机数。

  3. 服务器向客户端发送证书

    服务器向客户端发送自己证书,其中就包含了服务器的公钥。

  4. 服务器发送hello done表示hello阶段结束

  5. 客户端验证证书,拿到服务器公钥;利用两个随机数,生成pre-master secret,并使用服务器的公钥加密发送给服务器。

    证书验证步骤参考上面的证书小节;

    pre-master secret是一个非常重要的东西,双方利用pre-master secret生成master-secret,利用前面的两个随机数生成两个对称加密密钥和两个HMAC密钥,两对密钥分别用于客户端加密和服务器加密。

  6. 客户端发送changeCipherSpec提示服务器此后使用pre-master secret产生的密钥加密通信

  7. 客户端发送FIN报文,表示结束

  8. 服务器也发送changeCipherSpec报文

  9. 服务器也发送FIN报文,表示结束

  10. 双方可以开始安全通信了

至此,对于HTTPS的加密流程,已经比较清晰了。

Android中运用

无论是HTTP还是HTTPS,事实上开源网络框架都已经为我们完成了这些粗活累活,例如okHttp。正常情况下,发起HTTP和HTTPS请求并没有任何异同。但有时候会出现一些特殊的问题,就需要我们自己动手解决:

  1. 系统过于老旧,没有安装根证书。缺乏根证书的公钥,那么无法验证服务器证书是否安全。
  2. 自签名证书。自己的app访问自己的服务器,有时候为了节约经费,可以自己给自己的服务器签名。
  3. 证书信息缺乏关键信息,如颁发证书的机构。

对于上面的问题,我们可以自己重写证书验证流程,或者在okHttp中添加信任的服务器公钥,可以解决上面的问题。

最后

HTTPS要解决的就是计算机网络中的安全问题,不同问题的解决方法要清楚:

  • 防止消息监听:加密
  • 防止消息篡改:hash算法
  • 验证身份:数字证书

HTTPS就是利用这些方法,为通信双方建立安全连接,从而来实现安全通信。

如果文章对你有帮助,还希望可以留下您的点赞鼓励一下作者~

全文到此,原创不易,觉得有帮助可以点赞收藏评论转发。
有任何想法欢迎评论区交流指正。
如需转载请评论区或私信告知。

另外欢迎光临笔者的个人博客:传送门

相关文章: