大家好,我是满天星,欢迎来到我的技术角落,本期我将带你一起来了解 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 的一大作用了,它可以保证数据在互联网上传输的安全性,避免中间节点进行窃听和修改。

当然,聪明的你还可能会想到一些问题,例如:

  1. 战场上军队之间是提前约定好加密方案的,但是咱们任意一个浏览器都可以随时访问网页,没有办法提前约定加密方案呀,那是怎么做到的呢?
  2. 战场上经常出现敌军对另一方部队之间的电台加密进行破解的事情,破解完成之后,还是能够窃听到数据,那 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 颁发的数字证书发送给用户,既可以用来自证身份,同时还顺便告诉了用户自己的公钥。

到这里你可能还会有几个疑问:

  1. 数字证书如何保证不能伪造呢,难道中间人不能伪造一个数字证书发送给用户吗?
  2. 即使数字证书不能被伪造,从概念上看他是公开的,难道中间人不能直接把这个证书颁发给用户吗?

下面会一一回答这几个问题。正式回答之前,先来看看数字证书里究竟有哪些内容。

既然上面提到百度,我们就以百度为例,我们使用浏览器访问百度,可以在地址栏左边看到一个小锁,点击后,就可以查看百度的数字证书





图中可以看到该证书的一些基本信息:

  • 颁发对象:这个证书是颁发给百度的,并且只对域名 (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,并且我们已经将它的证书内置在操作系统中了。因此现在我们可以认定说,这个证书是值得信任的,与我们建立连接的就是百度,不是别人。

你可能会想,如果中间人将百度真实的数字证书返回给我呢?中间人是没有百度的私钥的,所以当我们提取出证书中的公钥,并对心里想的密钥进行加密后,中间人是解不开这个密钥的,所以中间人无论如何也无法与我们建立连接。

好了,数字证书的内容已经全部讲述完毕了,最后回过头来复习下前面提到的两个问题:

  1. 数字证书如何保证自身不会被伪造?

数字证书中有一段签名,该签名是 CA 使用其私钥对证书指纹进行加密后得到的值,我们浏览器使用 CA 的公钥对该签名进行解密后,与该证书的指纹进行对比,就可以知道证书是否被篡改或者伪造了。

当然,这里要多提一嘴,我们作为客户端,需要保证自己的电脑里保存的都是值得信任的 CA 根证书,因为信任某 CA 就代表信任了该 CA 颁发的所有数字证书,如果有人/软件想在你的电脑里安装来历不明的 CA 证书,那你就要保持警惕了...

  1. 如果中间人直接把真实的数字证书返回给我,它能够成功与我建立连接吗?

答案是不行的。这个问题其实比较简单,刚刚提到,服务器端除了公钥外,自身还保存有一份私钥的,而中间人是拿不到这个私钥的,因为它被服务器雪藏起来,不会发送到互联网中的...那么如果中间人用服务器的证书返回给用户,用户采用服务器的公钥对自身默念出来的对称密钥进行加密后,返回给中间人的时候,中间人就一脸懵逼了,因为这个密钥它解不开呀,它没有私钥的,所以这个问题就完美解决了。

7. TLS 握手具体过程

最后,我们再完完整整讲一下 TLS 握手的具体流程。



上图来自 www.ssl.com,展示了整个握手流程,我用大白话解释一下:

  1. 客户端向服务器发送 Client Hello 信息,告知自己想要建立一条 TLS 连接,并告知自己支持的加密算法。
  2. 服务器向客户端发送一个 Server Hello 的回应,并选择一个加密算法,同时给客户端发送自己的数字证书(包含服务器的公钥)。
  3. 客户端验证服务器发来的数字证书,验证通过后,在心里默默想出一个 pre-master 密钥(预主密钥),然后使用服务器的公钥,将预主密钥进行加密后,发送给服务器。
  4. 服务器用自己的私钥进行解密,得到预主密钥。
  5. 客户端和服务器都通过预主密钥,进行相同的计算后,得到后续通信时使用的对称加密密钥,称为 shared secret。
  6. 客户端和服务器端都分别用生成的 shared-secret 加密一段报文后,发送给对方,以验证对方能够成功收到信息并解密。
  7. 然后 TLS 就建立成功了,接下来双方都用这个 shared-secret 进行加密通信。

总结一下,HTTPS 的加密过程中其实既用到了非对称加密也用到了对称加密,其中握手过程使用的是非对称加密,主要目的是双方可以安全的协商一个统一的密钥,而真正的数据传输过程则使用的是对称加密,正是使用刚才商量的这个密钥。

你可能会问,为什么不全程使用非对称加密呢?因为对称加密效率更高,尤其是在大量数据的时候,对称加密比非对称加密整整快几个数量级,所以真正数据传输的过程选用了对称加密。

到这里,HTTPS 的原理就已经全部介绍完毕了。大家如果还有什么疑问,欢迎在评论区留言讨论。

8. 总结

最后,我们总结一下,HTTPS 解决了两个问题:

  1. 数据传输过程中的安全问题,因为它对数据进行了加密,只有浏览器和服务器可以对其进行解密。
  2. 浏览器对服务器的信任问题,数字证书以及其中的数字签名,保证了我们访问的就是我们想要访问的服务器,不可能被钓鱼网站欺骗,也不可能被中间人攻击所欺骗。

当然,保证以上安全的前提是我们的电脑本身没有被攻破,如果你的电脑被黑客攻击,装上了来历不明的根证书,那么 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 服务器建设、到最后的浏览器访问。教程将从以下三个方面进行:

  1. 首先利用 openssl工具,模拟一个证书权威机构(CA),生成 CA 证书以及密钥。
  2. 然后利用 openssl工具,模拟 techcorner.cn网站的维护者,向上面的 CA 机构申请一个数字证书
  3. 使用 Nginx 搭建自己的 HTTPS 服务器,并使用上面生成的服务器证书。
  4. 使用浏览器访问自己搭建的 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 的全部内容。

如果本文对你有帮助的话,欢迎 点赞&收藏&转发,你的支持将是

万字长文,带你彻底搞懂 HTTPS(文末附实战)的更多相关文章

  1. 万字长文带你掌握Java数组与排序,代码实现原理都帮你搞明白!

    查找元素索引位置 基本查找 根据数组元素找出该元素第一次在数组中出现的索引 public class TestArray1 { public static void main(String[] arg ...

  2. [Redis] 万字长文带你总结Redis,助你面试升级打怪

    文章目录 Redis的介绍.优缺点.使用场景 Linux中的安装 常用命令 Redis各个数据类型及其使用场景 Redis字符串(String) Redis哈希(Hash) Redis列表(List) ...

  3. 彻底搞懂https原理

    我终于彻底理解了https原理!!!激动之下,写一篇博客,搞一波分享!!! 本篇博客比较精彩的地方: 思维方式:也是借鉴一位大佬的,写得很棒.https://blog.csdn.net/guolin_ ...

  4. 面试都在问的微服务、服务治理、RPC、下一代微服务框架... 一文带你彻底搞懂!

    文章每周持续更新,「三连」让更多人看到是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) 单体式应用程序 与微服务相对的另一个概念是传统的单体式应用程序( ...

  5. 面试都在问的「微服务」「RPC」「服务治理」「下一代微服务」一文带你彻底搞懂!

    ❝ 文章每周持续更新,各位的「三连」是对我最大的肯定.可以微信搜索公众号「 后端技术学堂 」第一时间阅读(一般比博客早更新一到两篇) ❞ 单体式应用程序 与微服务相对的另一个概念是传统的「单体式应用程 ...

  6. 万字长文带你入门Zookeeper!!!

    导读 文章首发于微信公众号[码猿技术专栏],原创不易,谢谢支持. Zookeeper 相信大家都听说过,最典型的使用就是作为服务注册中心.今天陈某带大家从零基础入门 Zookeeper,看了本文,你将 ...

  7. 一文带你快速搞懂动态字符串SDS,面试不再懵逼

    目录 redis源码分析系列文章 前言 API使用 embstr和raw的区别 SDSHdr的定义 SDS具体逻辑图 SDS的优势 更快速的获取字符串长度 数据安全,不会截断 SDS关键代码分析 获取 ...

  8. 五万字长文带你学会Spring

    Sping Spring概念介绍 spring是啥呢,你在斗地主的时候把别人打爆了那叫spring, 你成功的追到了你爱慕已久的女神,人生中的春天来了,那也叫sping 好了别看我老婆了,咱来讲讲啥是 ...

  9. 又长又细,万字长文带你解读Redisson分布式锁的源码

    前言 上一篇文章写了Redis分布式锁的原理和缺陷,觉得有些不过瘾,只是简单的介绍了下Redisson这个框架,具体的原理什么的还没说过呢.趁年前项目忙的差不多了,反正闲着也是闲着,不如把Rediss ...

  10. 8分钟带你深入浅出搞懂Nginx

    Nginx是一款轻量级的Web服务器.反向代理服务器,由于它的内存占用少,启动极快,高并发能力强,在互联网项目中广泛应用. 图基本上说明了当下流行的技术架构,其中Nginx有点入口网关的味道. 反向代 ...

随机推荐

  1. Vue3 animate.css + wowjs 官网实现滚动到对应元素位置增加动画特效

    本人在Vue3中使用的是 setup语法糖 也就是 <script setup>...</ script> 在项目中install一下两个插件: yarn add animat ...

  2. FCC 高级算法题 对称差分

    Symmetric Difference 创建一个函数,接受两个或多个数组,返回所给数组的 对等差分(symmetric difference) (△ or ⊕)数组. 给出两个集合 (如集合 A = ...

  3. 复制文件到U盘提示“一个意外错误使您无法复制该文件”处理办法

    运行cmd 运行 chkdsk H(U盘所在盘符):/f    即可

  4. 【基础知识】C++算法基础(头文件配置、获取输入、输出)

    基础的头文件配置.输入输出 <iostream> 和<iostream.h>的区别:加.h是C中的做法,C++里一般不加.h,但相应的,要加using namspace std ...

  5. JS实现一个函数getType获取所有的数据类型

    1 function getType(obj) { 2 if (obj === null) { 3 return String(obj) 4 } 5 const toType = (obj) => ...

  6. 1011.Django状态保持以及表单

    一.session保持状态 状态保持: 1. http协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态: 2. 客户端与服务器端的一次通信,就是一次会话实现状态保持的方式:在客户端或服 ...

  7. nohup /root/runoob.sh > runoob.log 2>&1 &

    nohup /root/runoob.sh > runoob.log 2>&1 &****

  8. 常用的typedef 定义

    今天开始学习VC++基础,系统编程栏目下都是WinAPI和MFC的内容,此为浏览博客园时学习的一篇文章,觉得很实用,拿来做笔记. 出处见最底部. 三行代码:     typedef char CHAR ...

  9. 使用 netstat 命令监视网络状态

    在linux 系统网络出现问题时可以使用netstat -s 来分析问题 使用 netstat 命令监视网络状态 netstat 命令生成包含网络状态和协议统计信息的显示内容.可以通过表格形式显示 T ...

  10. 63.C++类型转换

      类型转换(cast)是将一种数据类型转换成另一种数据类型.例如,如果将一个整型值赋给一个浮点类型的变量,编译器会暗地里将其转换成浮点类型.   转换是非常有用的,但是它也会带来一些问题,比如在转换 ...