自上而下的理解网络(3)——HTTPS篇
本系列的前一篇博文中,我们介绍了一个很常用的应用层协议HTTP,在日常生活工作中,HTTP协议的应用可谓无处不在,我们浏览网页,我们使用手机App都离不开HTTP。通过前面的介绍,我们也理解了HTTP本质上只是客户端和服务端约定好的一套通信规则,使得客户端和服务端能够相互理解的进行交流。在互联网普及的前期阶段,网页主要用来展示公开的静态内容,HTTP协议并没有明显的缺陷。随着互联网的不断发展,互联网应用越来越个性化也原来越复杂,某些安全性问题就变得非常重要。HTTP向HTTPS演化就成了互联网应用的必经之路。
一.HTTP协议的安全性问题
在解决问题之前,我们首先需要先发现问题。HTTP协议从根本上有如下3种安全问题:
1. 数据没有加密
回忆下我们前面编写的建议的HTTP服务端与客户端程序,数据其实是明文传输的,HTTP协议本身没有涉及到数据加密的相关定义。因此我们的网络通信报文一旦为截获,用户的隐私就完全暴露了。而在互联网环境中,要截获数据实在是非常容易的一件事,运营商可以做到,代理服务器可以做到,数据传输中的各个路由节点设备也可以做到,甚至客户端的恶意程序也可以做到。想象一下,如果你使用的是一个网上银行的互联网应用,你的银行账号密码在互联网中被窥视的一清二楚,是不是一件非常恐怖的事情。当然,如果我们的客户端应用是配套的,我们也可以在HTTP协议之上约定一种加密算法,传输数据时服务端和客户端按照同样的规则加密解密来保证数据完全(实际上很多移动端应用的接口服务是这样做的)。但是这依然有很多问题,首先浏览器是第三方的,私有的加密协议无法用于浏览器应用,其次客户端的加密算法如果被破解,破解者就可以用同样的方式破解所有拦截到的数据,依然会产生安全性问题。
2. 无法验证对方的身份
HTTP是基于TCP/IP协议的应用层协议,TCP协议要保证的是双方正确完整的传输数据,但是并不能验证对方的身份。例如我们在访问huishao.cc这个网站时,任何一个中间节点接收到此HTTP请求后都可以不转发而直接返回错误的数据,而客户端收到数据后,还认为是huishao.cc返回的,客户端根本无法判断返回数据的对方是谁,也无法知道返回的数据是否是真的。对应于银行业务,你的余额信息可能被任何中间人篡改,危害极大。同样,服务端在接收到客户端的请求时,也无法判断此客户端是否是正常合法的,这就可能造成任何伪装者只要截取了客户端的请求,就可以伪装成此客户端继续和服务端做更深入的通信。
3. 数据可能被篡改
这一个问题实际上是数据完整性和一致性的问题。无论是客户端还是服务器,在数据发出后都会经过很多中间节点,任何一个节点都可能改动数据中的某些部分,而接受方是完全没有手段对数据是否已经被改动了做校验的,对于网页应用来说,一些恶意者可以拦截到数据后可以加入各种烦人的广告。更严重一些,拦截者可能改动某些重要数据使得客户端错误的理解了服务端的意图,而将客户端引入危害性更大的攻击网站或下载攻击代码。
二. 针对安全性问题的解决方案思考
综上所述,HTTP协议在安全上有3种致命的缺陷,实际应用中,越来越需要一种更加安全,且完全兼容HTTP协议的新型通信方式。我们先来看如何解决这些安全问题。
1. 使用加密来保护明文内容
对于明文传输数据的问题,我们想到的最简单的方式是对数据进行加密,如使用对称密钥加解密,流程如下:
这种对称密钥的加密方式有两个明显的问题,首先客户端与服务端使用的密钥相同,原理上用户A可以拿到密钥,攻击者B也有办法拿到此密钥,这样就失去了加密的意义。其次,即是服务端为每个客户端都分配不同的一套私钥对,维护起来是很困难的,而且客户端环境负责,在传输私钥的过程中攻击者也可能将私钥拦截,这样加密依然失去了意义。
既然对称加密有这样的问题,那么我们是否可以使用非对称加密呢,非对称加密流程如下:
在非对称加密中,密钥对分为公钥和私钥,顾名思义,公钥本身是可以公开的,所有客户端都可以得到,私钥是存在在服务器上的。相比对称加密,服务端的安全性要比客户端高很多,私钥相对是安全的。但是使用这种非对称加密的方式对数据进行保护也是有局限性的,如上图所示,在客户端发送数据给服务端时,攻击者没有服务端的私钥,此时无法解密数据。但是当服务端使用私钥加密信息后返回给客户端时,就可能被攻击者截获并使用公钥进行解密。
既然非对称加密和对称加密都无法完美的保护数据的安全,那么能否将其结合一下呢?在HTTPS的实现中,也确实是这么做的。流程如下:
非对称加密的主要问题是使用私钥加密的数据,任何拥有公钥的人都可以进行解密,我们只需要保证服务端回数据的时候不使用私钥进行加密即可。如上图所示,首先客户端使用公钥将要使用的加密算法,所使用的对称密钥加密后发送给服务端,中间节点没有服务端的私钥,无法获知客户端发送的密钥是什么。之后如果服务端同意此次协商,则使用客户端的对称密钥来加密回执数据,之后的数据通信都使用此密钥进行加密,攻击者无法解密。
好了,现在的加密方式看上去很好用,但是实际实施起来好像并不太容易,公钥如何正确的给到客户端,这又是一个棘手的问题。我们下面再看。
2. 如何验证对方的身份
公钥之所以难以正确的给到客户端是因为在网络通信中,通信双方都无法验证对方的身份。例如服务端把公钥放到一个公开的网站上供客户端下载,可是客户端无法判断此网站是否真的是服务方官方的网站,此网站本身也可能已经被劫持,客户端将下载到攻击者提供的公钥。之后客户端所有的通信都将直接和攻击者进行,并且客户端还一无所知(钓鱼网站的攻击方式)。如果服务端在客户端发起会话时先将公钥发送给客户端,这同样不安全,中间节点可以直接截取服务端的公钥,将其替换成攻击者自己的公钥发给客户端。之后客户端发送的对称密钥将使用攻击者的公钥加密,攻击者可以拿到后续网络通信所使用的对称密钥,这时就更危险了,客户端和服务端都不知道自己发送的数据都被攻击者先获取到并解密成功,并且攻击者还在继续伪装成客户端/服务端与双方通信。
那么,有没有一种方式可以安全的获取服务端的公钥呢?这就需要我们解决双方身份验证的问题。而要解决身份认证的问题,本质是想办法将公钥与身份信息进行绑定,并且使其不可篡改。我们最直接的想法是对公钥也进行非对称加密,但是这样用来解密公钥的公钥如何保证正确给到客户端就又成了问题,就陷入了反反复复无穷已的逻辑循环。因此,要打破这个循环,我们就必须公认一些安全的公钥,对其进行信任。再举个例子,你要到银行取钱,必须要证明你是你,你可以说我妈可以证明我是我,但是又必须证明你妈是你妈,这样永远无法证明出你的身份真实。但是现实生活中我们不会遇到这样的困境,因为你可以使用身份证证明你是你,身份证是国家机关颁发的,所有社会机构都对其认可。将其类比到我们的网络通信场景中,各个端都需要一个证件来进行自我证明,这个证件就是证书。
证书的工作流程如下:
如上图中所示,在服务端向证书颁发机构申请证书时,需要将自己的加密公钥提交,此时证书颁发机构需要对申请者的身份进行保证,即申请者的是否是某个域名的真实运营者,验证无误后(这直接影响到证书机构的信誉,即生存底线)其使用自己的密钥对服务端的公钥进行加密,后生成证书给到服务端。证书本身是一个比较复杂的技术,其可以通过继承关系来进行有效性验证,这里我们不做过多介绍,我们只需要知道,用来解密证书的公钥一开始就已经集成进了客户端设备,当然客户端设备也可以根据需要来自己添加所信任的证书,这些客户端的证书中包含了域名信息以及对应的公钥。之后,每当客户端要与服务端发起通信时,服务端先把证书发给客户端,如果这中间有人拦截篡改,但其没有证书颁发机构加密时的私钥,篡改后的证书文件无法通过客户端的内置公钥解密,客户端会意识到遭到了攻击,中断请求即可。当客户端收到证书后,从本地找到受信任的与之配对的证书文件,用本地证书文件中的公钥来进行解密,解密成功后即可拿到正确服务端的公钥。
如此,通过证书我们解决了身份认证与防信息篡改的问题。下面,我们可以来理解HTTPS协议了。
三. HTTPS协议究竟是什么
现在,我们已经有了解决HTTP安全的理论方法,说HTTPS是一种协议其实是一种误称,HTTPS的全称是Hyper Text Transfer Protocol over SecureSocket Layer,即安全套接字通道之上的HTTP协议。其本质是SSL(SecureSocket Layer)或TLS(Transport Layer Security)协议。TLS协议是SSL协议的一种升级版,本篇博客中我们以TLS为例来进行介绍,当使用其加密通信的数据格式是HTTP协议格式的时候,这种通信我们就称为HTTPS,同样,其他网络层应用协议也可以工作在TLS协议之上,例如使用TLS来使文件传输更加安全的FPTS。HTTP,TLS和HTTPS的关系如下:
下面我们来介绍TLS协议的工作方式,理解了TLS协议,HTTPS只是其传输的业务数据内容的格式是HTTP协议的而已。
1. TLS的工作流程
根据我们前面的分析,客户端和服务端要安全的进行通信,需要3个部分:
- 客户端向服务端索要身份认证证书并验证和获取服务端公钥。
- 使用服务端公钥加密对话信息进行通信算法和密钥的协商。
- 使用协商出的算法和密钥进行数据通信。
其中前两步是TLS的握手过程,最后一步才是通信过程。我们先来看握手过程。
完成握手过程客户端和服务端间共需要4次通信。过程如下:
在握手过程中,首先由客户端发起ClientHello消息,此消息中会包含客户端支持的TLS协议版本号,一个客户端生成的随机数(后面生成密钥用),客户端支持的加密算法以及支持的压缩方法等。后面我们会具体介绍。
接收到客户端的ClientHello消息后,服务端会进行回应,此回应消息为SeverHello消息。SeverHello消息主要包含的内容有协商所使用的加密通信协议版本,一个服务端生成的随时数(后面生成密钥用),确认使用的加密方法以及服务端的证书。
客户端接收到服务端的SeverHello消息后,首先会验证服务端的证书,如果证书不是可信机构颁发的或者证书中的域名与实际访问的域名不一致,或者证书过期,客户端都需要中断此次通信(不同的浏览器可能实现不同,有些可能仅仅是警告用户不安全)。如果证书验证通过,则客户端会提取出服务端的公钥。发送回应信息给服务端,回应信息将包含一个使用服务端公钥加密的随机数,编码改变的通知,表示之后和服务端的通信将使用双方协定好的加密方法和密钥进行通信。以及客户端握手结束的通知,这时客户端会将前面所有发送内容的Hash值发给服务端,供服务端来校验中间是否有篡改。当然,如果服务端也需要验证客户端你的身份,在这一步的时候客户端也会将证书发给服务端。
最后,服务端会再次发回应消息给客户端。回应消息包括编码改变通知,表示随后发送的信息将用双方协定的加密方法和密钥进行加密。服务端也会发送服务端握手结束通知,之后客户端和服务端的数据通信将进入加密通信,原始数据的格式有HTTP是完全一致的。
2. 关于TLS的协议内容
TLS的握手过程的实现主要是通过如下4种类型的子协议实现的:
- TLS Handshaking protocols握手协议
- Alert Protocol警告协议
- Application Data Procotol应用层数据协议
- Change Cipher Spec Protocol密码切换协议
每个TLS协议有消息都有消息头和消息体组成,消息头是固定的,长度为5个字节40位。
消息头的第1个字节描述的是消息体的类型,枚举如下:
| 类型 | 值 |
| 密码切换协议 | 0x14 |
| 警告协议 | 0x15 |
| 握手协议 | 0x16 |
| 应用程序数据协议 | 0x17 |
第2和第3个字节用来描述当前使用的TLS的版本号。
第3和第5个字节用来描述当前消息的长度,长度会包含头信息。
在头信息结束后,即是具体的消息体内容,根据不同的协议类型消息体内容不同。
握手类型的协议:
一般,一次成功的TLS握手需要客户端和服务端交互两次。握手的主要作用是协商出客户端和服务端任何的加密方法和密钥。握手类的消息由3部分组成,分别为子类型,子消息长度和具体的子消息体。
子消息类型占1个字节,枚举如下:
| 类型 | 值 |
| HELLO_REQUEST | 0x00 |
| CLIENT_HELLO | 0x01 |
| SERVER_HELLO | 0x02 |
| CERTIFICATE | 0x0B |
| SERVER\_KEY\_EXCHANGE | 0x0C |
| SERVER_DONE | 0x0E |
| SERVER_REQUEST | 0x0D |
| CERTIFICATE_VERIFY | 0x0F |
| CHIENT\_KEY\_EXCHANGE | 0x10 |
| FINISHED | 0x14 |
在具体执行的时候,多条子消息可能封装进同一条TLS消息中,交给TCP层处理发送。
警告类型的协议:
在握手过程中可能会产生异常,例如客户端的加密算法服务端无法支持时,可以直接回执警告类型的子协议,警告类型的消息格式是固定的,由两个字节组成,第1个字节描述警告的等级,第2个字节描述警告的原因。
警告类型的枚举有:
| 等级 | 值 |
| warning | 1 |
| fatal | 2 |
警告原因的枚举有:
| 原因 | 值 |
| close_notify | 0 |
| unexpected_message | 10 |
| bad\_record\_mac | 20 |
| decryption\_failed\_RESERVED | 21 |
| record_overflow | 22 |
| decompression_failure | 30 |
| no\_certificate\_RESERVED | 41 |
| bad_certificate | 42 |
| unsupported_certificate | 43 |
| certificate_revoked | 44 |
| certificate_expired | 45 |
| certificate_unknown | 46 |
| illegal_parameter | 47 |
| unknown_ca | 48 |
| access_denied | 49 |
| decode_error | 50 |
| decrypt_error | 51 |
| export\_restriction\_RESERVED | 60 |
| protocol_version | 70 |
| insufficient_security | 71 |
| internal_error | 80 |
| user_canceled | 90 |
| no_renegotiation | 100 |
| unsupported_extension | 110 |
密码切换协议:
密码确认协议就是我们前面所说的客户端和服务端的确认回应信息,这个消息一旦发出,表示后续的通信都将可以在加密层进行了。
应用层数据协议:
这一协议的作用就是对上层的应用层协议数据进行一层加密保护后在进行传输。
3. Client Hello子消息详解
当客户端要和服务端简历TLS通信时,最先发出的就是客户端的Clent Hello消息。此消息包含如下信息:
client_version:客户端支持的最高的TLS协议版本号。
random:客户端生成的随机数。
sessionID:会话的id,可用于恢复会话。
cipher_suites:客户端发送所支持的密码套件列表。
compression_methods:客户端支持的压缩方法。
extensions:扩展内容,TLS协议支持方便的进行扩展,可以有多个扩展数据。
我们可以使用Wireshark来监听TLS的协议数据,Client Hello消息结构如下所示:
4. Server Hello子消息详解
Server Hello消息的主要作用是根据客户端支持的密码套件来选择一个双方都满意的密码套件,Server Hello包含如下信息:
server_version:服务端最终选择使用的TLS协议版本号。
random:服务端生成的随机数。
session\_id:如果能够恢复会话,则和客户端传递的session\_id一致。如果不能,则创建一个新的session_id返回。
ciper_suite:根据客户端支持的密码套件,服务端选择一个双方都支持的来使用。
compression_method:根据客户端提供的压缩方法,服务端选择一个双方都支持的来使用。
extensions:根据客户端传递的扩展列表,服务端来进行处理。
一个示例的Server Hello消息如下图所示:
可以看到,Server Hello消息的主要作用是对客户端提供的密码套件列表,压缩方法列表等进行选择,最终决定双方通信要使用的。
5. Server Certificate,Server Key Exchange,Server Hello Done子消息
当服务端发送过Server Hello消息后,一般会立刻发送Server Certificate消息。这个消息是选发的,但是一般服务端都会发送,用来给客户端验证服务端的身份。并且将服务端的公钥安全的给到客户端。证书消息中的信息是一系列的证书链。关于证书的详细内容我们这里不过多描述。
Server Key Exchange子消息也是有条件才会发送的,某些密码套件会需要发送此消息。逻辑上,服务端发送Server Hello消息后,紧随着就会发送Server Hello Done消息,这条消息是一条空消息,表示服务端已经将要发送的东西都发送完了,客户端可以进行证书校验,密钥协商逻辑了。
这3条消息通常会保证到一个TCP报文中发送,如下图:
后面,还有一些客户端协商出密钥的消息,握手完成的消息等。这些子消息完成后,表明整个握手过程结束,后续客户端和服务端开始将数据进行加密通信。
完成了完整的握手过程后,后续的业务数据通信都会进行加密,如下图所示:
四.结尾
现在,我们对网络通信在应用层的逻辑有了宏观上了理解。应用数据的逻辑理清楚了,更重要的是将这些数据安全稳定的传输给对方。这就需要传输层协议登场了。
专注技术,热爱生活,交流技术,也做朋友。——珲少 QQ:316045346