带着数据去旅行
一次完整的HTTP请求
在讲HTTPS之前,我们有必要了解一下HTTP协议。先看一个完整的HTTP请求:
上面是我用Wireshark工具所抓到的一次完整HTTP请求的样子。
首先可以看到的是三个TCP协议的数据包,也就是我们常说的TCP三次握手,在这之后,两台电脑之间的连接就建立起来了。继续向下就可以看到HTTP请求了,Request和Response一来一回,很好辨认。大家还可以注意到在每个HTTP数据包之后还跟了一个TCP的包,这是TCP协议的确认,表示告诉发送者:"你刚才的报文我已经收到了"。页面底部显示了一个原始的HTTP GET请求报文的样子,为什么请前面会出现乱码呢?其实前面的乱码就是TCP/IP协议。不同于HTTP协议可以用字符编码,TCP/IP的传递需要极致压缩数据量,每一个数据位就表示了一种特定的意思,需要使用TCP/IP的规则去解析(就像我们在软件上面页面所看到的解析结果),而不能使用字符编码来阅读。什么是分层网络模型
互联网发展早期,需要解决的一个重要问题就是电脑之间如何传输数据。人们需要找到一种大家都认同的规范,每个想要相互连接的计算机都需要按照规范来办事。这期间有一个协议脱颖而出,他就是现在应用非常广泛的TCP/IP协议,它规定了网络上的计算机要如何找到另一台计算机,并且如何与之建立连接。TCP/IP的发明者还非常有远见地提出了“网络分层”的概念,将复杂的网络通信划分成了多个层次,每个层次只需要关心自己的问题,将大问题划分为了很多个小问题从而解决了网络通信的难题。就像我们写代码需要分层一样(Service,Controller,Dao层等等),每一层只需要关注自己的功能。 值得注意的是,TCP/IP协议只有四层,而我们现在经常说起的OSI七层网络模型,是由国际标准组织(ISO)后来才提出来的。在此之前,虽然TCP/IP协议非常优秀,但是奈何市面上还是各玩各的,所以ISO想要制定一个大一统的协议,在很多细节上进行了细化,成就了我们现在所看到的七层网络模型。我们简单对比一下OSI七层模型和TCP/IP的四层模型:
虽然OSI七层模型有更细分的设计,但是他也无法撼动TCP/IP已经实行多年的统治地位,所以OSI模型现在更多是一种建议和标准。 在TCP/IP模型中,我们的HTTP协议,TCP协议,IP协议分别位于应用层,传输层和互联网层。当我们发起一次HTTP请求,浏览器会按照HTTP协议的要求,拼装出HTTP的数据,再调用下层TCP协议提供的API将HTTP数据封装到TCP协议中,再往下针对IP协议再一次封装,最后到达物理网络层,数据被发送出去。 目标主机收到数据以后,反过来将包装好的数据一层一层剥开,最终得到我们要传递的信息,然后再一次将要返回的数据经过层层协议的打包返回到源主机。
带着保险箱去旅行
从上面的基础知识我们不难发现,HTTP协议是非常简单且高效。
HTTP直接运行在TCP协议之上,依靠TCP来实现自己的稳定性。HTTP使用明文传输,数据可读性和可扩展性都非常好。但是它的简单高效也成为了一把双刃剑,原始的HTTP请求就像是在网上裸奔,所有信息都是明文传递,毫无安全可言。随着科技的发展,人们对安全、隐私的要求越来越高,特别是在现在万物互联的时代,敏感信息如果不经过保护直接在网上传播,将造成极大的隐患。
------在这样的历史浪潮中,HTTPs应运而生。
HTTPs与HTTP的区别就在这个s上,它代表的是"TLS",既安全传输层协议。TLS也是运行在TCP之上的应用层协议,但是与一般传输数据的协议不同,TLS所关心的,只有安全。我们可以认为TLS就是一个运行在TCP上的保险箱,现在我们将需要保护的数据先装入这个保险箱,再经过TCP发送出去,就能实现我们对于安全的需求。我们将HTTP over TCP,变成了HTTP over TLS over TCP。
那么这个保险箱是如何工作的呢?这就是我们今天的重点。
何谓安全
我们考虑一下两个人写信的场景,怎样才能做到安全的远程交流呢?
我不想我们写信的内容被人看懂得想个办法保证内容不被人修改你得在信里面证明你的身份一旦证明了这是你写的,以后你可不许抵赖如果能够做到这四点,那么我们的信应该是比较安全的,而这四点正好对应了我们信息安全领域中的四个要点,既:
机密性完整性身份认证不可否认下面我们就逐一来分析一下HTTPs是如何做到这四点的:
1. 机密性
我们知道两台计算机之间要想传输数据,这些数据必然会经过层层路由,这些节点上的每台计算机理论上都能看到我们的数据,所以安全的首要目标就是对我们想要传输的数据进行加密。 根据密码学的知识,摆在我们面前的有两种选择---对称加密和非对称加密。 对称加密使用一把密钥,加密解密都用它,在双方都知道密钥并且妥善保管的情况下理论上非常安全。但是对于我们复杂的网络环境,一台服务器需要服务众多的客户,难道要为每个客户都配置一个对称加密的密钥吗?这显然不现实。
那么非对称加密呢?一把私钥对应一把公钥,私钥加密的内容只有公钥可以解开,公钥加密的内容也只有私钥可以解开,那么我们在服务器上配置一把私钥,再把公钥发给所有客户,是不是就行了呢?
答案是否定的,原因有两点:
服务器是面向大众客户的,公钥必然会公开,谁都可以获取,那么路由的中间节点一样可以拿着这把公钥解析服务器返回的报文。非对称加密的性能无法适应互联网高并发的环境,所有请求都用非对称加密会导致连接异常缓慢,无法使用。基于上述两点,HTTPs做了一个非常聪明的选择,就是混合加密。现在我们在TCP三次握手的基础上再引入TLS的四次握手,而这次会晤的重要目标就是协商出密钥。为了便于理解我们先看一个简单的模型:
客户端通过可靠的渠道获取到服务器的公钥。客户端生成一个随机数作为对称加密的密钥,也就是本次会话的主密钥(实际上生成主密钥需要Server-Client都参与,通过相同的算法算出来,但是在老版本的RSA交换密钥中最终的安全性还是客户端保证的,所以这里我们简单认为是客户端生成了一个随机数即可,后面会详细讲到)客户端用Server的公钥将主密钥加密传输给Server。后续交换内容均用主密钥进行加密解密。 混合加密.png2. 完整性
机密性我们可以满足之后,来看看第二个需要考虑的重点,完整性。 大家应该也有注意到,我们在HTTPs握手模型中的前几次数据交换也都是明文传输的,因为双方还没有协商出主密钥的时候是无法进行加密传输的。而在这个过程中又有许多关键的信息,这些信息我们需要有一定的手段保证他们不会被人修改。 这时候就需要引入数字签名。 在数字签名中首先要提到的就是摘要算法(也就是我们常说的Hash函数,HTTPs中常见的有SHA-2族等),它的作用是将任意长度的数据映射成一串固定长度的字符串。摘要算法有几个特点:
单向性,无法通过计算出来的字符串反推出原文雪崩效应,即原文中一点点微小的改变也会导致计算出的字符串完全不一样,无法通过规律逆向推导。抵抗冲突,既然是将无限打的数据集映射到有限大的空间中(字符串空间),势必会出现映射冲突的可能,既不同的数据映射出的字符串是相同的。抵抗冲突是摘要算法的一个重要指标,一个好的摘要算法他的冲突概率应该是非常非常小的。这三个特点使得我们可以很好的验证原文数据的完整性,我们只需要使用SHA-2计算出报文的摘要,将它附在报文后面。接收方收到报文后,再通过同样的算法计算报文的摘要,如果和发送过来的摘要相同,那么就可以验证数据的完整性。3. 身份认证
有了完整性验证还是不够,我们网络中的人可能太坏了,如果他连摘要也一起修改了呢?这时候还需要为摘要上一把小小的锁,不能让人轻易修改,这就是数字签名。 现在我们将摘要信息用RSA非对称加密中的私钥进行加密,在接收方使用公钥解密验签,即便中间人想要修改摘要,但是他无法得知原始的私钥,所以数字签名是无法伪造的。 客户端接收到请求之后用服务器的公钥将签名解密,再验证完整性,这样既可以保证消息没有被篡改过,又可以保证消息确实是目标服务器发送的。
4. 不可否认
由于存在伪装(任何一方都可能被中间人冒名顶替),所以其中一方可以假装没有收到对方消息或对方收到的消息不是自己发的。 在使用数字签名验签的过程中我们也实现了不可否认的特性,大家可以想一想,既然中间人无法获取私钥,也就无法伪造签名。反过来讲,一旦我们验签成功,说明数字签名合法,那么上述信息就确确实实是对应的人发送的,无法抵赖。
5. 公钥的信任问题
有了上述几点的分析,我们为HTTP设计的安全方案似乎已经可以了。但是还有一个致命的问题,就怎么把公钥安全地交到用户手上? 之前的分析都基于一个假设,无论是非对称交换密钥,还是签名的验签,都需要用到服务器的公钥,并且我们都认为这把公钥一定是和服务器的私钥对应的。但是问题就出在这里,服务器如何安全的把公钥传递给用户?如果在公钥的传递过程中就被人替换,那后续的加密等步骤不就没有意义了吗。 这个时候有同学会说,简单啊,我们把这个公钥加密一下传输给客户不久好了吗? 。。。。。。 等等,那加密公钥用的密钥又怎么传输?这已经成了一个鸡生蛋的问题,网络协议的制定者们也意识到了这一点,光靠算法等技术手段已经没法了,一定要人为介入打破这个循环。 咣的一声,CA(Certification Authority)闪亮登场。 CA介入之后,它以自己的信誉为各服务器的公钥尽心担保。这时候我们服务器在第一步发送给客户的不再是简单的公钥,而是一连串的信息,包括服务器的域名,服务器持有者等等,当然最重要的还是服务器的公钥。 这些信息并不是简单的发送,他们经过了CA的认证,CA使用自己的私钥对他们进行了签名,生成了数字证书。 然后CA通知微软苹果安卓。。。等终端操作系统提供商:“你们把我(CA)的公钥先放在你们的操作系统里”。这就是我们常说的根证书。 这时候CA的担保工作算是完成了。客户端在收到服务器的数字证书之后,使用内置在设备中的根证书进行验签,取得可信任的服务器公钥,从而解决了公钥的信任问题。
HTTPS握手过程
有了上述四点的分析,我们详细看一下HTTPS的握手过程。前面我们有提到,HTTPs协议是在HTTP和TCP之间添加了一层TLS协议,它也属于应用层协议,它的作用就是在TCP建立起连接之后对传输通道进行加密,将HTTP放在TLS之上,可以保证在不影响HTTP本身协议的情况下获得加密传输的特性,所以HTTPS的握手过程也就是TLS的握手过程。
TLS交互过程.jpg
Encrypted Application Data.png
抓包数据显示了一个完整的TLS握手过程(RSA密钥交换),从图中我们可以看到,直到第五个包,才开始正真传输数据Application Data,并且从第二张图可以看出实际的Application Data(也就是HTTP协议内容)已经经过了加密,抓包所能看到的也只是乱码。 下面我们就详细分析一下在正式传送数据之前的四个步骤是怎么做的:
HTTPS握手过程.png
第一步,Client Hello。
首先由客户端发起了一个打招呼的包:ClientHello,通过抓包我们可以看到TLS层的内容:
有两个点我们需要关注的就是Client传输了一个随机数和21个Cipher Suites,我们详细解释一下这两个点:
Client Random:客户端随机数。在之前的原理中我们有讲到HTTPs握手的主要目的就是得出对称加密的主密钥。这个密钥单独由某一方生成都不太合适,应为并不是每个主机都能产生完全的随机数,如果只是由某一方生成,很可能会产生比较弱的随机数,容易被猜测,导致加密密钥被破解。所以我们在过程中会涉及到三个随机数,并且后续可以看到Server端也会参与到随机数的生成,从而使“随机数”更接近真实的随机,保证安全。Cipher Suites:在之前的知识铺垫中我们知道HTTPS的加密过程需要用到的密码技术主要有:对称加密非对称加密摘要算法数字签名每一种算法分类中有很多的选择,例如对称加密就有AES_256_GCM, AES_128_CBC等,非对称加密也有RAS和ECDSA,他们的安全强度和性能都各有侧重。那在后续的连接中,针对算法的多种选择,本次会话使用哪种组合呢?这些算法的组合就是我们说的Cipher Suites------密码套件,看一个具体的Cipher Suite: Cipher Suite 客户端提供了许多这样的组合,表示客户端支持这些组合,供服务端进行选择,每种组合的安全和性能各有差异,服务器结合自身情况尽心选择。第二步,Server Hello
服务器收到了客户端的招呼请求之后,做出了Server Hello的回应,这里面有三个很重要的内容:
Server端随机数从Client Hello发送的Cipher Suites中选择一个自己支持的Cipher Suite服务器自己的CA证书这一步服务器提供了必要的信息给客户端,证书用于验证自己的身份,以便确保公钥安全的交到用户手中,并且提供自己生成的随机数,和选定的Cipher Suite,让客户端继续进行后续步骤。第三步,Client Key Exchange
前两个打招呼的报文实际上都是明文传输的,因为在建立连接之前双方都没有密钥,无法使用加密传输的。而前文有提到的两个随机数(Client Random和Server Random)都有被截获的风险,如果中间人获取到我们的随机数,再根据同样的算法进行计算,不是就可以得到我们最终生成的对称密钥了吗?显然我们不可能让坏人轻易得逞,原因就在于第三步的Client Key Exchange中的第三个随机数,我们通常称之为预主密钥(Pre Master)。 客户端在收到了服务器返回的消息之后,首先会用存放在本地的根证书对服务器证书进行验证,验证通过便会认为服务器证书中的公钥是可信的,取出来备用。 接下来客户端会生成第三个随机数,为了把这第三个随机数安全的交给服务器,我们需要对他进行加密,这时的情况跟第一次Client Hello时有所不同,因为我们已经有了可以信任的服务器公钥。 客户端根据服务器返回的Cipher Suite使用确定的算法和公钥对第三个随机数进行加密传输。这时候只有服务器的私钥可以解密获取这第三个随机数,从而保证了主密钥的安全。
在这一步里,客户端还做了一些小动作,由于客户端现在已经抢先服务端计算出了主密钥,而当这条Client Key Exchange信息到达服务端时,服务端应该也能正确的计算出主密钥,所以客户端先用主密钥对之前握手的信息做了摘要和签名以供服务端计算出主密钥之后当场进行验证,这是双方第一次使用对称加密的主密钥进行加密。
第四步,Server Change Cipher Spec
走到这一步,服务端会先用自己的私钥解密,获取到客户端传来的第三个随机数(预主密钥),接着通过相同的算法计算出主密钥。服务端对上一步客户端做的小动作进行验签,这是服务端第一次使用对称加密主密钥,如果验签成功说明我们双方的主密钥计算成功,是安全的。 接着服务端做了最后的回应:
Change Cipher Spec,告诉客户端,我准备好了,后续咱们就用主密钥加密沟通,你懂的。对我们握手的信息用主密钥进行签名,你验证一下。至此,整个HTTPs的握手完成。
后记
关于HTTPs的内容确实有很多要讲。比如本文所讲的主要时原始的RSA密钥交换,而目前应用最多的密钥交换算法是ECDHE,它在交换随机数与预主密钥时使用了不同的策略,但是流程原理与RSA密钥交换是共通的。 HTTPs的每一步都有它的用意,少了任何一步都会导致信息安全出现漏洞,原PPT的后面还有一部分反向案例分析,由于篇幅问题就下次再更新吧。