万字长文,带你彻底搞懂 HTTPS(文末附实战)
大家好,我是满天星,欢迎来到我的技术角落,本期我将带你一起来了解 HTTPS。
前言
其实网上写 HTTPS 的文章也不少了,但是不少文章都是从原理上泛泛而谈,只讲概念,没有讲原因,作为小白,看完还是会有一种似懂非懂的感觉。
本文尝试从 HTTP 开始,一步一步深入到 HTTPS,告诉你 HTTPS 到底是什么、为什么需要 HTTPS、以及 HTTPS 到底是怎么做的。相信你在阅读完本文后,一定会对 HTTPS 有深入的了解。
纸上得来终觉浅,除开原理部分外,本文还提供了 HTTP 和 HTTPS 的实战教程,帮助你从 0 开始搭建一个 HTTPS 加密的 Web 服务器,如果按照实战教程一步一步走下来,那么你将对 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
- GET 表示请求方法,常见的 HTTP 请求方法有 GET、POST、PUT、DELETE 等...
- GET 后面的
/
表示请求路径,这里我们访问的根路径,所以显示为/
。如果你访问httpbin.org/get
的话,这里显示的就是/get
了 - HTTP/1.1 表示使用的 HTTP 协议版本,现在常用的有 HTTP/1.1 和 HTTP/2,当然还有更先进的 HTTP/3,这里就不过多展开了
- 下面的 9 行全部都是 HTTP header,每一个 header 包含 name 和 value,之间用冒号分隔开。
一个典型的 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/1.1 指的协议版本,响应和请求的协议版本是一致的
- 200 OK 代表返回的响应码,表示这个响应是符合预期的。另外还有非常常见的返回码 404 NOT FOUND ,大家应该或多或少听说过,它表示服务器告诉你你访问的这个资源不存在
- 后面 7 行全部是 HTTP header,同样每一个 header 包含 name 和 value,之间用冒号分隔开。
- 最后是 HTTP Body,也就是响应体,即服务器返回给你的内容主体,浏览器正是根据响应体来渲染页面的
由于本文重点不在 HTTP,这里不再详细介绍各个部分的作用。
如果你对 HTTP 还不太了解的话,我在本文附录部分准备了一份 HTTP 实战,建议你跟着实操一遍,了解下 HTTP 的基本结构。当然,这部分内容比较基础,如果你已经对 HTTP 比较熟悉的话,这部分可以跳过了。
2. 为什么需要 HTTPS
上面简单讲了一下 HTTP。在正式讲 HTTPS 之前,首先我们要搞清楚 HTTP 的缺点是什么,为什么需要 HTTPS。
我们知道,HTTP (除了最新的 HTTP/3 外),传输层是基于 TCP 协议的。TCP 建立连接时,有三次握手。三次握手完毕之后,TCP 连接就顺利建立了,接下来两端将会传输数据。
对于普通的 HTTP 协议,在建立完 TCP 连接之后,就直接开始传输数据了,这时候数据是明文传输的,这也是 HTTP 最不安全的地方。
明文传输是什么概念呢?我们知道,浏览器和服务器之间,是存在很长一条路线的,你在家里通过浏览器访问网页的时候,数据会从你的电脑,传到你家里的路由器,再到光猫,到运营商,到互联网....直到最后才到服务器。在明文传输下,理论上来讲,浏览器和服务器之间的任一节点,包括你家里的路由器、包括你购买宽带/流量的运营商,都可以“窃听”你们的数据,甚至还可以修改数据。
听起来不够直观?打个比方,近代时期,战场上打仗时,部队之间会通过电台进行交流。如果通过明文进行交流,那么非常危险,敌军可以打开电台进行窃听,偷取你的军事情报,这样的事也屡见不鲜了...那么他们是如何解决这个问题的呢?两个部队之间提前约定一个加密的方案,在传数据之前,先把它进行加密再传输,另一端收到数据之后,按照事先约定的方案进行解密,然后读取就可以了。这样即使敌军开始窃听,也只能听到加密后的情报,如果无法对其破解的话,得不到任何有效信息。
没错,这就是 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,顾名思义,它一定是工作在传输层了。上面提到过,HTTP 是应用层协议,传输层和应用层的概念,想必大家应该知道吧,计算机网络的事实标准中,自顶向下可以分为五层:应用层、传输层、网络层、链路层、物理层...这是《计算机网络》的基础,这里不过多展开,不熟悉的同学,要回去重修一下课程了。
我们知道,TCP 协议里有三次握手,三次握手成功后连接才算建立,接下来才会真正开始传输数据。传统的 HTTP 协议中,三次握手成功之后,就会直接开始明文传输 HTTP 数据了。
那么 TLS 是什么时候开始发挥作用的呢?答案很简单,在三次握手之后,传输数据之前。
也就是说,在 TCP 协议中加入 TLS 之后,三次握手成功之后就不会再立刻开始传输数据了,而是紧接着开始 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 也不能保障你的安全了。
附录一:HTTP 实战
这里的实操部分针对不太熟悉 HTTP 的同学,熟悉的同学请直接跳过...
在浏览器(建议 Chrome)中访问httpbin.org
,并使用开发者工具进行观察
点击 Request Headers 后面的 View source,可以看到原始的 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
其中:
- GET 表示请求方法,常见的 HTTP 请求方法有 GET、POST、PUT、DELETE 等...
- GET 后面的
/
表示请求路径,这里我们访问的根路径,所以显示为/
。如果你访问httpbin.org/get
的话,这里显示的就是/get
了 - HTTP/1.1 表示使用的 HTTP 协议版本,现在常用的有 HTTP/1.1 和 HTTP/2,当然还有更先进的 HTTP/3,这里就不过多展开了
- 下面的 9 行全部都是 HTTP header,每一个 header 包含 name 和 value,之间用冒号分隔开。
当然,点击 Response Headers 后面的 view source,你就可以看到服务器返回给你的 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/1.1 指的协议版本,响应和请求的版本肯定是一样的了
- 200 OK 代表返回的响应码,表示这个响应是 OK 的。另外还有非常常见的返回码 404 NOT FOUND ,表示服务器告诉你你访问的这个资源不存在...
- 后面 7 行全部是 HTTP header,同样每一个 header 包含 name 和 value,之间用冒号分隔开。
- 最后是 HTTP Body,也就是响应体,在 Response Header 里是看不到了,不过点击上面 Tab 页的 Response 后,就可以看到完整的响应体了。
由于本篇文章的重点不在 HTTP,这里就不再过多展开,非常建议大家平时使用浏览器访问网页的时候,有事没事多打开下上面的开发者工具,看看 HTTP 到底是怎么工作的,说不定,还能找到一份工作呢...
(访问百度时,开发者工具中的小彩蛋)
当然,作为程序员,还是希望有更“极客”的方式来了解 HTTP 协议,这时候就要用到 curl
命令了
终端里输入 curl -v httpbin.org
,可以看到整个 HTTP 交互的流程
附录二:数字证书实战
这部分实战教程将带你体验 HTTPS 的全流程,从证书生成、到 HTTPS 服务器建设、到最后的浏览器访问。教程将从以下三个方面进行:
- 首先利用
openssl
工具,模拟一个证书权威机构(CA),生成 CA 证书以及密钥。 - 然后利用
openssl
工具,模拟techcorner.cn
网站的维护者,向上面的 CA 机构申请一个数字证书 - 使用 Nginx 搭建自己的 HTTPS 服务器,并使用上面生成的服务器证书。
- 使用浏览器访问自己搭建的 HTTPS 服务器,体会全加密流程。
值得一提的是,网上也有不少利用 openssl
生成证书的教程,但是它们质量参差不齐,绝大多数都无法生成一个浏览器识别的、能够真正用在生产环境中的证书。而本教程是我查阅官方文档、并且亲自试验过的教程,绝对有效。
实战前,你需要准备:
- Mac / Linux 服务器
- 安装 openssl 工具、以及 Nginx 。
1. 生成 CA 证书
我们知道服务器拿到的数字证书都是从 CA 那边颁发的,所以首先我们需要模拟出一个 CA 机构,即生成 CA 证书以及密钥。
首先生成 CA 私钥(公钥可以从私钥中提取而来)
# 生成 CA 私钥
openssl genrsa -out ca.key 2048
然后生成证书签发申请文件
# 生成证书签发申请文件(.csr)
openssl req -new -key ca.key -out ca.csr -subj '/C=CN/ST=Zhejiang/L=Hangzhou/O=Tech_Corner/CN=ROOTCA'
-subj
后面的参数代表了生成证书的基本信息,包括国家、省份、城市、公司名称等。CN=ROOTCA,代表是准备申请一个根证书。
然后根据上面生成的私钥和证书申请文件,自签一个根证书
# 自签一个根证书
openssl x509 -req -days 365 -sha256 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.cer
总结一下,以上命令,一共生成了三个与 CA 有关的文件:
ca.key
:CA 私钥ca.csr
:CA 为自己签发证书的请求。(该文件的作用是生成 CA 证书,生成之后不会再用到)ca.cer
:CA 为自己签发的证书
可以用 openssl 命令,查看下申请的证书信息
$ openssl x509 -in ca.cer -text -noout
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
92:63:8f:e3:23:97:e1:c6
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=ROOTCA
Validity
Not Before: Apr 11 15:39:56 2023 GMT
Not After : Apr 10 15:39:56 2024 GMT
Subject: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=ROOTCA
... 以下省略
2. 生成服务器证书
接下来,我们模拟网站 techcorner.cn
的维护者,想要为自己的网站申请一个数字证书,所以我们需要向上面模拟的 CA 来申请证书。
同样,我们首先生成一个自己的私钥 。
openssl genrsa -out server.key 2048
然后,生成一个证书签发申请文件
openssl req -new -key server.key -out server.csr -subj '/C=CN/ST=Zhejiang/L=Hangzhou/O=Tech_Corner/CN=techcorner.cn'
注意,这里 -subj
中有一个很重要的参数是 CN=techcorner.cn
,它代表我们生成的证书,只签发给这个域名。换句话说,其他的域名如果使用这个证书,会被浏览器判断为无效。
以上证书签发申请文件(server.csr) 发送给 CA 后,CA 会对其做一些验证,例如验证申请者本身是不是符合资质,是不是确实拥有这个域名,等等。都确认完毕后,CA 才会决定给用户颁发这个证书。(CA 必须维护自己的权威性,随便颁发证书的 CA 将被世界所抛弃,已经有这样的先例了)
CA 为其颁发证书的命令,可模拟如下
openssl x509 -req -extfile <(printf "subjectAltName=DNS:techcorner.cn,DNS:www.techcorner.cn") -days 365 -in server.csr -CA ca.cer -CAkey ca.key -CAcreateserial -out server.cer
其中比较有意思的是 -extfile
后面填写的 subjectAltName=...
这一串,这其实是 X509 V3 证书里面的字段,对于普通的 TLS 加密,他可能不是必须的,但是对于 HTTPS,尤其是浏览器来说,它会额外对这个字段进行校验,确保其中存在当前访问的域名,否则即使 CN
满足,浏览器同样不会信任这个证书!(网上很多教程都没有注意到这个地方,导致最后生成的证书不可用)
得到的服务器证书为 server.cer
。同样可以使用 openssl 查看证书
$ openssl x509 -in server.cer -text -noout
Certificate:
Data:
Version: 1 (0x0)
Serial Number:
80:56:02:4d:21:ff:5e:60
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=ROOTCA
Validity
Not Before: Apr 11 15:52:10 2023 GMT
Not After : Apr 10 15:52:10 2024 GMT
Subject: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=techcorner.cn
...以下省略
上面可以看到该证书的基本信息(Subject),以及颁发者信息(Issuer)等。
3. 启动 Nginx,并使用上面生成的服务器证书
编辑 nginx.conf
,添加 HTTPS 端口的监听
server {
listen 443 ssl;
server_name techcorner.cn;
ssl_certificate /path/to/server.cer;
ssl_certificate_key /path/to/server.key;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
root html;
index index.html index.htm;
}
}
ssl_certificate
的值修改为上一步生成的 server.cer 的绝对地址ssl_certificate_key
的值修改为上一步生成的 server.key 的绝对地址
然后启动 Nginx。
启动完毕后后,通过浏览器访问 https://localhost:443
访问 Nginx
可以看到浏览器给出了不安全的警告,这是符合预期的,因为我们使用的证书是自己使用 openssl
签发的,CA 机构也是自己模拟的,并不在电脑的信任列表中。
如果想要正常访问,可以在系统的根证书列表中,将我们自己模拟的 CA 证书添加进去(Mac 系统中,将根证书 ca.cer
拖入钥匙串访问的 系统 中即可,并对其进行 “始终信任”)。
这个 CA 证书是我们自己生成的,只要我们自己不泄漏其私钥,那么将是安全的
使用浏览器重新访问 https://localhost:443
,得到如下结果
可以发现仍然是 不安全 的状态,不过这时错误代码已经变了,刚才是 ERR_CERT_AUTHORITY_INVALID
,意思是颁发证书的 CA 机构无效。
而现在错误变成了 ``ERR_CERT_COMMON_NAME_INVALID,这是什么意思呢?它其实是说证书的
CN和我们访问的地址不匹配,怎么理解呢?还记得我们创建该证书的时候,里面包含了
CN=techcorner.cn,意思是这个证书只能被
techcorner.cn这个域名使用,现在我们访问的是
localhost`,显然他们是不匹配的,因此证书无效。
虽然我们没有购买该域名的使用权限,但是可以通过在本地修改 hosts 的方式,强行将 techcorner.cn
解析为本机地址 127.0.0.1
。
具体方式为,修改本机的 /etc/hosts
文件,在文件末尾增加一行 127.0.0.1 techcorner.cn
添加完毕后,再次使用浏览器访问 https://techcorner.cn
可以发现,Chrome 提示连接是安全的,证书是有效的
Perfect,恭喜你,到这里,实战部分就结束了,你已经成功体验了 HTTPS 的全部内容。
如果本文对你有帮助的话,欢迎 点赞&收藏&转发,你的支持将是我不断更新的动力。