前言
最近在做 CSB 网关的过程中,开发了一个需求,支持 ”TLS 双向认证“。双向认证听起来比较高级,其实也不难,不过是单向认证的“加强版”。单向认证是客户端认证服务端,而双向认证则是在单向认证的基础上,增加了服务端认证客户端这一过程。
单说单向认证这个词,你可能不是很熟悉,但是提到 HTTPS,应该会恍然大悟吧,经典的 HTTPS 协议中,其实就是用到了 TLS 单向认证。正好借这个机会,我对 HTTPS 也做了一次回顾,整理成一篇文章,供参考。
什么是 HTTPS ?一句话,HTTPS = HTTP + SSL。HTTPS 并不是一个全新的协议,而是在 HTTP 的基础上,通过 SSL 增加了一层加密协议,从而大大增加了 HTTP 协议的安全性。
所以在正式了解 HTTPS 之前,先了解下 HTTP。
1. HTTP
HTTP 全称 超文本传输协议(HyperText Transfer Protocol),是一种广泛用于互联网中浏览器与服务器之间的应用层传输协议。简单来说,浏览器向服务器发送 HTTP 请求,服务器向浏览器返回 HTTP 响应,两者之间通过这种方式进行“交流”,来使得我们的浏览器可以正常从服务器端获取数据,并展示在用户的电脑屏幕上。
以访问 http://httpbin.org网址为例,一个典型的 HTTP 请求如下所示:
GET / HTTP/1.1
Accept: text/html,...
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Host: httpbin.org
Pragma: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
一个典型的 HTTP 响应如下所示
HTTP/1.1 200 OK
Date: Sat, 08 Apr 2023 16:28:43 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9593
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
body...
由于本文重点不在 HTTP,这里不详细介绍。
2. 为什么需要 HTTPS
上面简单讲了一下 HTTP。在正式讲 HTTPS 之前,首先我们要搞清楚 HTTP 的缺点是什么,为什么需要 HTTPS。
在安全方面,HTTP 的缺点主要在两个方面:
-
明文传输。数据在传输过程中没有任何加密,因此中间的任意一环都可以窃听到消息内容,甚至可以篡改。
-
客户端与服务端之间没有验证机制,即无法确认对方的真实身份。也就是说,你并不能确定与你通信的究竟是真正的对方,还是一个冒充对方的“中间人”。
HTTPS 的出现正是为了解决上面两个问题,其思想类似于近代战场上的电台通信。我们知道,战场上打仗时,部队之间会通过电台进行交流。如果通过明文进行交流,那么非常危险,敌军可以打开电台进行窃听,偷取你的军事情报,这样的事也屡见不鲜了...那么他们是如何解决这个问题的?两个部队之间提前约定一个加密的方案,在传数据之前,先把它进行加密再传输,另一端收到数据之后,按照事先约定的方案进行解密,然后读取就可以了。这样即使敌军开始窃听,也只能听到加密后的情报,如果无法对其破解的话,得不到任何有效信息。
没错,这就是 HTTPS 的思想,浏览器在发送 HTTP 请求之前,先通过某种方式对其进行加密,然后再进行传输。服务器端收到数据之后,对其解密,读取真实内容,生成 HTTP 响应,同样对响应进行加密,然后传回给浏览器,浏览器收到数据之后,对其进行解密,得到真正的 HTTP 响应。这样就可以保证数据在传输过程中的安全性,无论是路由器还是运营商,都没有办法“窃听”数据了。
这就引出了 HTTPS 的一大作用,它可以保证数据在互联网上传输的安全性,避免中间节点进行窃听和修改。
当然,其中还会存在一些问题,例如:
-
战场上军队之间是提前约定好加密方案的,但是咱们任意一个浏览器都可以随时访问网页,没有办法提前约定加密方案呀,那要怎么做?
-
战场上经常出现敌军对另一方部队之间的电台加密进行破解的事情,破解完成之后,还是能够窃听到数据,那 HTTPS 的这个加密方案到底安全吗,会被破解吗?
这些问题在后面解答。
3. HTTP + SSL = HTTPS !
上面提到了对 HTTP 进行加密的思想。在 HTTPS 的具体实现中,这个加密方案即是 SSL(Secure Sockets Layer)。
定义:SSL(Secure Sockets Layer)是一种安全协议,用于在互联网上保护数据的传输安全。它工作在传输层,主要功能是通过加密技术,保护不同计算机之间的数据传输过程,防止敏感数据被黑客窃取和篡改。SSL 协议可以用于保护网站的用户登录、信用卡支付、网上银行等敏感信息的传输,以及企业之间的机密数据的传输。SSL 协议目前已经被继承为 TLS(Transport Layer Security),是一种安全性更高的传输层协议。所以,下面我将统一以 TLS 为名称进行讲解。
首先,划重点,TLS 中有 Transport Layer,顾名思义,它一定是工作在传输层了。TLS 基于 TCP 协议,我们知道,TCP 协议里有三次握手,三次握手成功后连接才算建立,接下来才会真正开始传输数据。传统的 HTTP 协议中,三次握手成功之后,就会直接开始明文传输 HTTP 数据了。
那么 TLS 是什么时候开始发挥作用的呢?答案很简单,在三次握手之后,传输数据之前。也就是说,在 TCP 协议中加入 TLS 之后,三次握手成功之后就不会再立刻开始传输数据了,而是紧接着开始 TLS 的建立过程,也被称为 TLS 握手。
TLS 的目的是什么?上面提到,在战场上,两个部队之间会提前约定好加密的方案,例如面对面用纸互相写下加密方案,然后在一段时间之内的电台通信统一用这个加密方案,这样能一定程度上保证电台通信的安全性。但是 TLS 中我们并没有这样一个“面对面”的机会,咱们总不可能在访问网页之前,人肉跑到服务器的维护者那边去跟他约定加密方案吧。出于这个目的,TLS 握手便出现了。所以我们可以说,TLS 握手的目的是给通信双方约定一个安全的加密方案(可以理解为商量一个只有双方知道的加密密钥)。
知道了 TLS 握手的目的,接下来我们需要知道它具体是怎么做的。首先,我们肯定不能直接明文传输加密方案(密钥),不然这个密钥在传输过程中就直接被第三方获取了,那么加密将没有任何意义。也就是说,TLS 握手需要做到:通信双方可以约定一个共同的加密方案(密钥),并且这个约定的过程(即 TLS 握手过程),即使被任何第三方窃听到,也无法解析出这个加密方案(密钥)。
是不是听起来很神奇,到底是怎么做到?其实只要用到密码学中非常经典的两个概念:对称加密和非对称加密。
4. 对称加密,非对称加密
对称加密是 TLS 握手成功后,通信双方之间采用的数据加密方案。现在的主要问题是:通信双方如何安全的商量好这个对称密钥,防止密钥被其他人窃取?
这时就需要轮到非对称加密出场了。什么是非对称加密?与对称加密不同,非对称加密方案中,用户手握两把密钥,一把称为公钥,一把称为私钥,其中公/私钥都可以用来加密/解密数据,但是:用公钥加密后的数据,只有用私钥才能将其解开;用私钥加密后的数据,只有用公钥才能将其解开!
这里只介绍了非对称加密的特点,并没有介绍其原理,因为这属于密码学的范畴了,展开来讲又是一篇文章。简单说说其思想吧,目前流行的非对称加密算法 RSA 基于的原理其实就一句话:我们目前还没有很好的办法对一个很大的数做因式分解,例如你在心里默默想一个很大的质数 p 和 质数 q,算出其乘积 n,那么向外公开 n 的话,外部人员是很难找出 p 和 q 的(只能暴力尝试,而当这样的数够大够多的时候,以现在的计算机算力也需要几百上千年的时间才能破解了,这也是加密的安全所在)。
有了非对称加密,事情就变得有意思起来了。见下图,服务器端用非对称加密方案生成一对公/私钥,私钥掌握在自己手里,谁也不告诉;在 TLS 握手的过程中,服务器将自己的公钥交给浏览器端,浏览器端在心里默默想出一个对称加密的密钥后,将这个密钥用服务器端的公钥进行加密,然后再传回给浏览器端;浏览器端收到这个数据之后,用自己的私钥将其进行解密,就能够得到刚才浏览器心里默念的那个对称密钥了。
这样一来,这个问题就完美的解决了,两边可以心有灵犀的拿到这个对称密钥,而不用担心被任何第三方窃取到了。
这就是 TLS 握手的过程吗?不,当然没这么简单了,我们还没有考虑一个非常巧妙的攻击手段:中间人攻击。
5. 中间人攻击
什么是中间人攻击?看下面这张图
假设现在我们从电脑上访问百度,如果有一个中间人在我的路由器端,或者运营商端,或者任何一个中间节点上截取了我的请求,刚不是提到服务器端需要返回给我们公钥吗,中间人他自己也生成一套公/私钥,然后将自己的公钥返回给我,这样我就与中间人之间建立了一条我以为“安全”的连接了,此时我以为我连接的是百度服务器,其实我连接的是中间人...那么此时中间人可以做任何事情了,如果他人品比较好的话,他可以默默当一个代理,我要访问百度,他就去帮我访问百度,然后把结果返回给我,勤勤恳恳做一个“中间商”。当然,我们知道做这种攻击的人人品往往不会太好,所以他们可以做更坏的事情,例如伪造一个银行网页返回给我,让我填写账号和密码,这样的话...后果就不堪设想了。
那么如何防止中间人攻击呢?其中的核心就是:我们需要保证我们访问的就是目标服务器,例如,当我们访问百度时,我们需要确保在 TLS 握手时,给我们公钥的人就是百度,而不是任何其他人。
那么这个应该如何去保证呢?这就不得不提到接下来的几个概念了,数字证书,以及证书权威机构(Certificate Authority,简称 CA)。
6. 数字证书、CA
数字证书是由证书权威机构(CA)颁发的一个用于证明身份的证书,当然其中还包含了该用户的公钥等信息。例如还是以百度为例,假设百度需要给 www.baidu.com这个域名申请一个数字证书,他需要在生成公钥/私钥后,将自身的信息(包括域名、公司名称、公钥信息等)发给某个证书权威机构(CA),让 CA 给自己颁发一个数字证书。CA 需要验证百度的真实身份,并且他确实拥有 www.baidu.com这个域名,一切都验证通过后,CA 才会给百度颁发这么一个数字证书。那么之后,不管是谁用浏览器访问 www.baidu.com的时候,百度都会将刚才那个 CA 颁发的数字证书发送给用户,既可以用来自证身份,同时还顺便告诉了用户自己的公钥。
到这里又引出几个问题:
-
数字证书如何保证不能伪造呢,难道中间人不能伪造一个数字证书发送给用户吗?
-
即使数字证书不能被伪造,从概念上看他是公开的,难道中间人不能直接把这个证书颁发给用户吗?
下面会一一回答这几个问题。正式回答之前,先来看看数字证书里究竟有哪些内容。
既然上面提到百度,我们就以百度为例,我们使用浏览器访问百度,可以在地址栏左边看到一个小锁,点击后,就可以查看百度的数字证书
图中可以看到该证书的一些基本信息:
-
颁发对象:这个证书是颁发给百度的,并且只对域名 (www.)baidu.com 有效
-
颁发者:这个证书是由 GlobalSign 颁发的。(GlobalSign是一家全球知名的证书权威机构)
-
有效期:这个证书的有效期是从 2022 年 7 月到 2023 年 8 月。(一旦过期,证书将不被信任)
-
指纹:指纹是整张证书经过哈希计算后得到的特征值,主要与后面会提到的签名一起工作,起到防篡改的作用
当然,一张数字证书的内容远远不止于此,例如还包含了服务器的公钥,可以在“详细信息”中进行查看。
下面我们在证书详细信息中点击“导出”,将证书导出为 Base64 编码的单一证书,然后使用 openssl 对其进行解析和查看
$ openssl x509 -noout -text -in baidu.com.cer
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
44:17:ce:86:ef:82:ec:69:21:cc:6f:68
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=BE, O=GlobalSign nv-sa, CN=GlobalSign RSA OV SSL CA 2018
Validity
Not Before: Jul 5 05:16:02 2022 GMT
Not After : Aug 6 05:16:01 2023 GMT
Subject: C=CN, ST=beijing, L=beijing, OU=service operation department, O=Beijing Baidu Netcom Science Technology Co., Ltd, CN=baidu.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:aa:2f:cc:41:8d:25:ae:83:e9:f4:27:c4:00:b3:
39:6f:0e:98:2a:55:7d:07:e5:80:49:82:fa:d3:d3:
85:98:b5:df:7b:6f:bb:02:dd:ed:78:e4:0c:07:2b:
9e:1e:86:4b:f6:6a:86:58:d7:57:6f:21:59:11:d8:
6f:96:6e:d2:de:36:28:f6:b4:e3:ce:95:32:29:00:
c1:65:8e:69:b0:00:fe:52:37:f4:88:3f:8b:6d:0f:
bb:f0:ec:c5:c0:31:ef:ad:b5:0c:06:66:ad:be:dc:
43:13:c4:66:b0:5d:cf:56:53:e2:d1:96:82:1c:06:
bb:9b:5f:ed:60:8d:d2:ed:f3:d2:50:ee:bb:cd:b2:
36:97:c8:ce:7b:d2:4b:b7:5c:b4:88:ca:37:6e:8b:
ce:f9:96:fd:b4:f5:47:b5:20:77:bb:fc:a8:9d:81:
b2:6c:f8:c7:09:6a:dd:22:6e:83:3f:a7:53:df:f1:
da:2f:29:6b:22:c3:e9:1d:65:e8:c5:a0:ba:13:4e:
16:3f:03:93:f0:a5:59:8a:1a:80:e8:27:7d:49:23:
df:d1:f9:4b:97:b7:01:c4:19:f5:f1:c5:ff:91:33:
d0:a1:74:c6:ee:d4:cf:f6:38:0c:ed:bd:5e:aa:44:
fb:88:f7:7b:99:70:76:34:55:7e:55:d2:0f:9e:bf:
94:93
... (中间省略)
Signature Algorithm: sha256WithRSAEncryption
63:21:07:23:47:06:eb:b3:7c:77:6c:df:bc:55:12:b9:f1:5e:
6a:04:60:16:be:d0:0b:18:9c:94:0c:a8:82:08:25:0d:26:fb:
dd:cb:fc:8c:27:d9:0c:fa:4a:b6:31:b6:67:f0:26:2c:0d:96:
96:39:65:3f:d9:a1:ee:de:9c:10:4d:54:e1:c8:d6:a9:0e:77:
db:00:e2:37:e3:3f:b4:9c:31:4f:ac:74:d3:22:12:53:36:d0:
ef:18:07:2d:8e:d0:e6:91:b2:6c:4a:5e:39:53:14:58:4e:d1:
50:04:c9:83:7e:0d:7b:15:96:87:11:d7:5d:4a:17:ac:aa:9f:
84:e3:a8:24:9d:d6:17:77:26:8c:9f:7a:7b:18:da:39:2f:77:
f7:2b:c7:23:b8:97:6f:c3:d1:72:4c:7e:fc:c6:0d:cc:73:38:
19:81:fb:e7:c1:7a:e8:b9:1d:3a:05:dc:36:04:9b:f1:f0:e1:
a6:47:a0:30:4f:55:90:6c:da:cf:9e:b2:76:12:11:a1:5c:b6:
61:8d:15:a4:68:65:9a:57:2f:7a:6e:a3:1f:f5:b4:92:5a:3c:
df:71:0a:cd:57:d4:d0:15:36:7e:ba:d5:03:25:27:45:b4:60:
cd:2e:02:c1:0f:0a:e7:41:6f:58:69:20:9e:ad:47:52:1a:b5:
e6:e5:8d:1d
可以看到,除了上面 Chrome 里显示的一些基本信息外,证书里还包含了几个重要信息:
-
服务器端的公钥,即上面 RSA Public-Key 下面的那一长串数字,就是百度的公钥了。
-
签名,证明数字证书有效的关键信息。如果把数字证书类比成一张合同的话,我们知道合同需要老板签字才算有效,同样,数字证书是需要 CA 签名才算有效的,这里的一长串字符就是 CA 对该证书的“签名”了。
我们知道合同上的签名是靠笔迹鉴定来确认真伪的,很明显这一长串字符里没有笔迹,那它是如何保证该签名是 CA 颁发的,而不是被其他人伪造的呢?
上面我们提到,每一张数字证书有一个指纹,是将整张证书经过哈希运算后得到的特征值。CA 作为权威机构,其本身也是有一对公钥/私钥的,它在颁发数字证书的时候,会用自己的私钥对证书的指纹进行加密,生成的这段加密数据,就是该证书的签名了!那么我们浏览器是如何验证证书的真伪呢?我们只需要使用 CA 的公钥对签名进行解密,看看得到的值是不是跟证书的指纹是一样的,这样就 OK 了,只要是一样,说明这个证书一定是 CA 颁发的。
那么,又有问题来了:我们浏览器是从哪里拿到 CA 的公钥呢?总不能还是通过网络传输吧,这样就有“套娃”的中间人攻击风险了。所以,我们的浏览器或操作系统已经内置了世界权威的 CA 的数字证书(证书里就包含了其公钥)了,点击浏览器的 设置 -> 隐私设置和安全性 -> 安全 -> 管理设备证书,可以查看当前系统内置的所有 CA 证书。
上图是我的电脑中内置的 CA 证书。刚刚提到了百度的数字证书是由 GlobalSign 颁发的,这里也可以验证,GlobalSign 是被我们的操作系统所信任的 CA,并且我们已经将它的证书内置在操作系统中了。因此现在我们可以认定说,这个证书是值得信任的,与我们建立连接的就是百度,不是别人。
你可能会想,如果中间人将百度真实的数字证书返回给我呢?中间人是没有百度的私钥的,所以当我们提取出证书中的公钥,并对心里想的密钥进行加密后,中间人是解不开这个密钥的,所以中间人无论如何也无法与我们建立连接。
好了,数字证书的内容已经全部讲述完毕了,最后回过头来回答下前面提到的两个问题:
-
数字证书如何保证自身不会被伪造?
数字证书中有一段签名,该签名是 CA 使用其私钥对证书指纹进行加密后得到的值,我们浏览器使用 CA 的公钥对该签名进行解密后,与该证书的指纹进行对比,就可以知道证书是否被篡改或者伪造了。
当然,这里要多提一嘴,我们作为客户端,需要保证自己的电脑里保存的都是值得信任的 CA 根证书,因为信任某 CA 就代表信任了该 CA 颁发的所有数字证书,如果有人/软件想在你的电脑里安装来历不明的 CA 证书,那你就要保持警惕了...
-
如果中间人直接把真实的数字证书返回给我,它能够成功与我建立连接吗?
答案是不行的。这个问题其实比较简单,刚刚提到,服务器端除了公钥外,自身还保存有一份私钥的,而中间人是拿不到这个私钥的...那么如果中间人用服务器的证书返回给用户,用户采用服务器的公钥对自身默念出来的对称密钥进行加密后,返回给中间人的时候,中间人就一脸懵逼了,因为这个密钥它解不开呀,它没有私钥的,所以这个问题就完美解决了。
7. TLS 握手具体过程
最后,我们再完完整整讲一下 TLS 握手的具体流程。
上图来自 www.ssl.com,展示了整个握手流程,用大白话解释一下:
-
客户端向服务器发送 Client Hello 信息,告知自己想要建立一条 TLS 连接,并告知自己支持的加密算法。
-
服务器向客户端发送一个 Server Hello 的回应,并选择一个加密算法,同时给客户端发送自己的数字证书(包含服务器的公钥)。
-
客户端验证服务器发来的数字证书,验证通过后,在心里默默想出一个 pre-master 密钥(预主密钥),然后使用服务器的公钥,将预主密钥进行加密后,发送给服务器。
-
服务器用自己的私钥进行解密,得到预主密钥。
-
客户端和服务器都通过预主密钥,进行相同的计算后,得到后续通信时使用的对称加密密钥,称为 shared secret。
-
客户端和服务器端都分别用生成的 shared-secret 加密一段报文后,发送给对方,以验证对方能够成功收到信息并解密。
-
然后 TLS 就建立成功了,接下来双方都用这个 shared-secret 进行加密通信。
总结一下,HTTPS 的加密过程中其实既用到了非对称加密也用到了对称加密,其中握手过程使用的是非对称加密,主要目的是双方可以安全的协商一个统一的密钥,而真正的数据传输过程则使用的是对称加密,对称加密使用握手过程中商量的密钥。
那么为什么不全程使用非对称加密呢?因为对称加密效率更高,尤其是在大量数据的时候,对称加密比非对称加密整整快几个数量级,所以真正数据传输的过程选用了对称加密。
到这里,HTTPS 的原理就已经全部介绍完毕了。
8. 总结
总结一下,HTTPS 解决了两个问题:
-
数据传输过程中的安全问题,因为它对数据进行了加密,只有浏览器和服务器可以对其进行解密。
-
浏览器对服务器的信任问题,数字证书以及其中的数字签名,保证了我们访问的就是我们想要访问的服务器,不可能被钓鱼网站或者中间人所欺骗。
当然,保证以上安全的前提是我们的电脑本身没有被攻破,如果你的电脑被黑客攻击,装上了来历不明的根证书,那么 HTTPS 也不能保障你的安全了。