HTTPS 证书和中间人攻击的原理

香格里拉古镇里面的牛肉火锅

有同学在知识星球和公众号粉丝群里面提到,希望我讲一讲 HTTPS 证书、为什么使用 Charles、Fiddler、MitmProxy 抓 HTTPS 的请求要安装证书、 requests 发送请求的时候,verify 参数除了 False/True 还能填写什么参数。今天我们就这几个问题来做一个简单的介绍。

首先我们定义几个术语:

  • 公钥:一串字符串,在非对称加密里面用来加密数据,随意公开。
  • 私钥:一串字符串,在非对称加密里面用来解密数据,不能泄露。根据私钥可以反推出公钥。
  • 普通密钥:一串字符串。在对称加密里面,加密和解密都用它。

首先关于HTTPS 链接的创建流程,网上已经有太多文章来介绍了。网上给出的流程,大概是这样的:

  1. 客户端发送请求到服务器。
  2. 服务器把自己的公钥下发给客户端。
  3. 客户端使用这个公钥加密一个普通的密钥,并发送给服务器。
  4. 服务器使用私钥解密出这个普通的密钥。
  5. 接下来所有的请求都由这个普通的密钥通过对称加密来实现。

并且,这些文章一般还会说道,对称加密速度快,但是加密解密使用的是同一个密钥。当你要传输这个密钥的时候可能会泄露;非对称加密,公钥可以随意公开,公钥加密,私钥解密。安全性高但是速度慢。所以 HTTPS 使用非对称加密用来传输普通密钥。这个普通密钥再来传输正常的数据。

这个流程看起来没有什么问题,也很合理。但是,它漏掉了一个很重要的东西:如何识别信息有没有被篡改或者监听?

我们说 HTTPS 协议正常情况下是不怕窃听的,也就是说,我即使在你家路由器上面安装一个监控程序,也无法监听到你的数据。但是上面这个流程,无法推导出这个结论:

如果我在你家的路由器上面安装了一个中间人监控的程序。那么,你的客户端第一次往服务器发送请求的时候,我就知道你要请求哪个网站了,这个时候,我首先假装服务器,让你把请求信息都发给我。然后我再假装客户端,把你的请求信息转发给服务器。服务器的公钥下发下来以后,监控程序保留这个公钥。监控程序自己也有一套公钥、私钥。他把自己的公钥发送给你。你以为这个公钥是服务器的,但实际上它是监控程序的。你用这个公钥加密普通密钥,监控程序能就使用自己的私钥来解密,拿到真正的对称加密的密钥。然后它再把普通密钥用服务器下发的公钥加密,传给服务器。接下来,服务器解密以后,用这个普通密钥加密数据,和它以为的客户端正常通信。

在这个过程中,客户端和服务器,完全不知道自己都在跟一个中间人进行通信。那么数据就这样轻易被监听了。

这样一来,HTTPS 的安全性意义在哪里?难道你要给监听的人说:你等一下,等我跟服务器交换完密钥以后,你再来监听?

使用 HTTPS,应该能保证,只要客户端和服务器是正常的,那么监听程序在中间的任何环节出现,我都不害怕。

HTTPS 之所以能这样保证,是因为它使用的是符合X.509标准的证书,而不仅仅是公钥和私钥。

国际电信联盟设计了一套专门针对证书格式的标准X.509,其核心提供了一种描述证书的格式。

X.509数字证书不仅包括用户名和密码,而且还包含了与用户有关的其他信息,通过使用证书,CA可以为证书接收者提供一种方法,使他们不仅信任证书主体的公钥,而且还信任有关证书主体的其他信息。

证书本质上就是一个文本文件。但是这个文件里面记录了很多其他信息,包括这个证书是谁颁发的,过期时间等等。

我们知道,要生成一个 SSL 证书,在 Linux 里面就是一条命令而已,非常简单。但是,国际电信联盟提供了一批值得信任的证书颁发机构,只有使用这些机构颁发的证书,浏览器才认为是安全的,才会出现绿色的锁。否则,如果你使用的不是认证机构颁发的证书,或者干脆你是自己一条命令生成的证书,那么当你访问网站的时候,就会变成下面这样:

这是因为,浏览器不知道你现在这个网站的证书,是真正服务器就用的自签证书,还是被中间人替换了。所以会给你发报警。如果你确认服务器就是这个自签证书,那么你就可以点高级-继续访问,如下图所示:

访问成功以后,浏览器地址栏也会提示你请求不安全:

如果你用 requests 请求这个网站,也会报错,如下图所示:

我们知道,requests 可以设置参数verify=False来强行访问使用了非认证机构颁发的证书的网站:

这里的verify=False,其实就相当于我们在浏览器上面点击了高级-继续访问

除此之外,requests 的verify参数,还可以填写成一个文件地址:

这里的这个test.cer文件,就是我在使用openssl生成网站自签证书的时候,一并自动生成的。它同时包含了公钥和私钥。它长下面这样:

我们再来看看 Charles 的根证书:

他们的格式是一样的。所以,当我们要使用 Charles/Fiddler/MitmProxy 抓HTTPS 的时候,需要信任根证书,实际上就相当于使用requests的时候,把verify=设置为根证书的地址。

为什么 Charles 的根证书被信任了以后就可以抓包了?为什么requests 指定了根证书以后,访问使用自签证书的 https 网站就不报错了?这是因为,我们现在有办法可以检测数据是否被篡改过。

现在话又要说回私钥和公钥了。我们都知道,公钥可以对数据进行加密,私钥对数据进行解密。但是实际上,我们还可以用私钥对数据进行“加密”,公钥进行“解密”!注意这里的加密解密,我打了引号,是因为准确的说,应该叫做用私钥对数据明文的摘要加密得到数字签名,用公钥可以验证这个数字签名是不是自己对应的那个私钥生成的。

服务器发给客户端的数据,除了客户端索要的数据外,还包括一份经过签名的摘要数据。客户端收到数据以后,用公钥就可以从签名里面解析出客户端需要的数据对应的摘要。客户端再把自己收到的数据使用摘要算法计算出一个摘要,两边一对比,就知道数据有没有被篡改。

自签证书不能伪装成可信机构签发的证书,就在于证书里面有一段数字签名,可信任机构颁发的证书,这个签名都是唯一的,自签证书如果修改了机构信息,那么新的摘要信息就跟那么这个数字签名解密后的摘要信息不匹配了。于是浏览器就会给你发出警报。

但当你信任了一个根证书以后,浏览器就不会发送警报了。所以如果你安装了来路不明的证书,那么你的客户端和服务器的通信就可能会被监听。

如果你看明白这篇文章,那么你应该会知道,如果你想使用 Charles 等等抓包工具,那么,根证书应该是安装到你的客户端。而不是安装到电脑上。例如你想抓手机的数据包,那么你应该把根证书安装到手机上,而不是安装到运行 Charles 的电脑上。