HTTPS - 通俗易懂的阐述 HTTPS 协议,解决面试难题

804 阅读14分钟

本文收录于 Github Blog

HTTPS 工作原理,之前也看过一些,但是对整体的一个完整流程和部分细节,还是处于一个模糊状态,之前也有一些疑问:“证书是怎么验证的?”、“TLS 握手过程是怎么样的?”、“对称密钥如何计算?”、“计算预主密钥随机数用了几个?” 等等,基于这些疑问,也花了一些时间才逐步了解的,基于自己的理解,做了一个 HTTPS 的系列文章,希望能帮助到有此疑问的读者朋友。

本文为系列的第一篇,带着一些问题逐步了解对称加密、非对称加密、数字证书、密钥协商等这些概念分别是什么、能做什么,一层一层揭开其神秘面纱。

使用 HTTP 潜在的问题

在 HTTP 中数据之间的网络传输是明文的,很容易被中间人窃取、攻击,对数据进行伪造再发往服务器端,服务端接收到数据也无法判断数据的来源是否准确。 image 如果说为什么要使用 HTTPS?直白点就是 “HTTP 不安全”,无法准确的保证数据的机密性真实性完整性

什么是 HTTPS 协议

HTTPS 不是一种全新的协议,它是建立在 SSL/TLS 传输层安全协议之上的一种 HTTP 协议,相当于 HTTPS = HTTP + SSL/TLS,可保护用户计算机与网站服务器之间数据传输的完整性、机密性。

从 OSI 模型图上看主要是在应用层和传输层直接多了一个 SSL/TLS 协议。 image 这里最主要的部分 SSL/TLS 就是我们学习 HTTPS 的关键部分,SSL/TLS 做为一种安全的加密协议,其在不安全的基础设施之上为我们提供了安全的通信通道。

SSL/TLS 这个名字有时也会让人迷,现在我们所说的 SSL/TLS 一般特指 TLS 协议,不妨看下它的发展历史。

SSL/TLS 发展历史

SSL 是 secure socket layer 的简称,中文为安全套接字层。最早由网景(Netscape)公司开发,该协议的第一个版本从未发布过。自 1994 年 11 月开始发布第二个版本,SSL 2 在开发上基本上没有与 Netscape 公司以外的安全专家商讨,这个版本被认为存在严重缺陷,这个版本最终也以失败告终。在 SSL2 失败后,Netscape 专注于 SSL 3 进行了完全重新的协议设计,于 1995 年发布,SSL 3 版的协议被沿用至今,只不过后来被改了名字 TLS 1.0,也许很多人并不知道。

1996 年 5 月,TLS 工作组成立,开始将 SSL 从 Netscape 公司迁移至 IETF,由于 Netscape 与 Microsoft 在 Web 统治权的争执,整个迁移工作也经历了一个漫长的过程,在 1999 年 1 月 IETF 组织将 SSL 进行了标准化 TLS 1.0 问世,前身就是 SSL 3。

TLS 是 transport layer security 的简称,中文为传输层安全协议。在 2006 年 4 月 TSL 1.1 版本发布,修复了一些关键的安全问题,添加对 CBC 攻击的保护(隐式 IV 被替换为显示 IV,更改分组密码模式中的填充错误)。

在 2008 年 8 月 TLS 1.2 版本发布,主要包括:增加 SHA-2 密码散列函数、AEAD 加密算法、TLS 扩展定义和 AES 密码组合。

2018 年 8 月 TLS 1.3 版本发布,对安全的加强、性能的提升也做了很多改变,例如,在安全上将 MD5、SHA-1 这些不安全或过时的算法移除,仅保留了少数算法 ECDHA、SHA-2 等。性能上在 TLS 握手过程中由之前的 2-RTT 握手改进为 1-RTT 握手并初步支持 0-RTT。

选择合适加密算法

我们谈到 https 都知道它之所以安全是因为传输中对数据做了加密,首先了解下它选择了哪种加密方式来实现的。

对称加密

对称加密是一种共享密钥的算法,客户端与服务端共用一把密钥,对数据做加密传输,如果密钥只有通信双方持有,不保证泄漏,那就可以保证安全。 image 现实世界中显然不是这样的,例如浏览器同服务器交互,服务器把共享密钥传输给浏览器,这个密钥在传输过程中怎么保证不被截取、篡改?

非对称加密

进一步提升安全系数,出现了 “非对称加密” 又称为 “公钥加密”,该算法拥有两个不对称的密钥,它的特性是使用公钥加密只有对应的私钥可解密反之,私钥加密也只有对应的公钥才可解密。注意,私钥仅自己可见,对外暴露的是公钥image 非对称加密的安全性比对称加密要高,但是它需要更多的计算,不适用于数据量大的场景,通信速度没有了保证也不行的,TLS 加密算法并没有完全采用这种加密算法

混合加密

所谓 “取长补短”,TLS 在加密算法上结合了非对称加密和对称加密,我们这里称之为 “混合加密” 算法,使用非对称加密进行身份验证和共享密钥的协商,只用一次即可,后续的通信中使用对称密钥进行数据的传输。

除此之外,客户端和服务端交换公钥的过程,依然存在被窃听,经典的例子还是中间人攻击,因为公钥在传输的过程是可见的,中间人可以对客户端扮演服务端的角色或者对服务端扮演客户端的角色,依然可以对数据进行篡改,但是服务端无法判别来源是否可靠,问题仍然存在。

举一个例子:

  • 服务器使用非对称加密算法生成一对公私钥,我们称为公钥 A、私钥 A,解决密钥交换问题。
  • 这里还存在一个中间人,它也生成了一对公私钥,我们称为公钥 B、私钥 B。
  • 浏览器向服务器发起请求,服务器返回自己的公钥 A,传输中被中间人截取(问题来了),将服务器的公钥 A 替换为中间人的公钥 B 发往浏览器。
  • 浏览器获取到公钥 B,并不知道这个是中间人的,它生成一个随机数再用公钥 B 加密,得到对称加密所需要的 “会话密钥”
  • 浏览器将生成的 “会话密钥” 发送给服务器,中间人截取之后使用自己测私钥 B 解密,得到 “会话密钥”,再用服务器公钥 A 加密发送到服务器。
  • 服务器在收到信息后,用自己的私钥 A 解密,得到 “会话密钥”,但服务器也不知道此时已被中间人截取了。

这也不行那该怎么办?在这里使用 “混合加密” 从安全、性能上得到了一个平衡,使用非对称加密交换对称加密密钥,已实现了我们需要的机密性

现在我们要解决下一个疑问:如何保证浏览器拿到的公钥是可信的?

数字证书解决信任问题

例如,现实世界里,我们去银行办事,到柜台前你说我是张三,要办理业务,银行工作人员首先需要你出示证件,得证明你是真的张三,能证明自己的就是 “身份证” 了,由权威机构(现实世界里的公安局)颁发的大家都认可的证件。

网络世界的 “公安局”

那么网络世界里的公安局,就是我们常说的 CA,Certificate Authority,证书认证机构,我们也需要为网站申请数字证书

证书是一个包含版本、序列号、签名算法、颁发者、有效期、公钥等的数字证书文件。我们的网站在使用 HTTPS 之前都会预先向 CA 机构申请一份数字证书,安装到自己的服务器上,之后浏览器发起请求,服务器就可以把这个数字证书返回到浏览器,这个过程中怎么保证数字证书不被修改呢?

公安局在颁发我们的身份证时有一定的防伪技术,同样 CA 在签发证书时也会对证书进行数字签名,保证证书的完整性

image

摘要算法

摘要算法是一种单向的加密算法,也称为 “散列算法”,在加密数据时不需要提供密钥,加密之后的数据也不能进行逆向推算。

它能实现对一个大文件加密之后映射为一个小文件,好比一篇文章提取一段摘要,但如果原文发生改变,哪怕是增加或删除一个标点符号再次加密后的结果也会发生完全不同的变化,目前一些常用的摘要算法(MD5、SHA-1)被认为存在安全性问题,在 TLS 1.3 版本已经移除了,现在推荐的是 SHA-2,例如 SHA256。

CA 机构对明文数据会做一个摘要算法,生成一段不可逆向解密的 Hash value,这段 Hash value 不能明文传输,避免中间人在修改证书后把摘要算法也修改了。

数字签名

数字签名,这个名字在现实世界也是如此,例如我给你一个证明,要证明是我给你的,最有效的办法就是签名、按手印,这个是没办法伪造的。

CA 也有一对自己的公私钥,结合上面摘要算法生成的 hash value,使用 CA 私钥加上这段 hash value 来生成数字签名,这个只有对应的公钥才可解密。

数字证书

CA 将数字签名和我们申请的信息(服务器名称、公钥、主机名、权威机构的名称、信息等)整合到一块,生成数字证书,颁发给服务器。

下面是对 www.nodejs.red 这个域名截取的一张图。 image

有了数字证书,客户端和服务端在交互时就可使用非对称密钥来协商用于数据加密的对称加密密钥了。

协商对称加密密钥

证书验证

我们在浏览器打开一个 HTTPS 协议的网址发起请求,在建立 TCP 链接之后,会发起 TLS 的握手协议,之后服务器会返回一系列消息,其中就包括证书消息。

证书的验证存在一个证书信任链问题,我们向 CA 申请的证书,通常是由中间证书机构颁发的。例如,www.nodejs.red 这个域名你会看到它的证书签发者是 “R3”,它是 Let's Encrypt 在 2020 年 11 月 20 日推出的一个免费证书,通过 R3 我们可以找到它的签发者是 “ISRG Root X1”,而 “ISRG Root X1” 没有了上级的签发者,现在会认为它是根证书。

下图展示的是 www.nodejs.red 这个域名网站的证书链关系。 image 在我们的操作系统中会预先安装一些权威机构的证书,浏览器信任的是根证书,如果根证书在本地,就用根证书 “ISRG Root X1” 公钥去验证 “ISRG Root X1” 这个中间证书机构是否可信,如果校验通过,再用 “ISRG Root X1” 去验证最终的实体证书 “www.nodejs.red” 是否可信任,如果通过就认为证书 “www.nodejs.red” 是可信的。

证书验证基本上都是这种模式,最终要找到本地安装的根证书,在反向的逐级验证,确认网站的签发者是可信的。如下图所示。 image

如果服务器返回的证书验证通过,浏览器就可获取到数字证书的明文、签名信息,做以下操作:

  • 用 CA 机构的公钥(CA 机构的公钥是不需要传输的,操作系统提供的根证书里会存在)去解密签名,得到摘要算法计算出的 hash value,我们暂定名称为 hashCode1。
  • 用证书里指定的摘要算法对明文数据做加密,得到 hashCode2。
  • 如果明文数据未被篡改,hashCode2 应该等于 hashCode1。
  • 现在证书是可信的,就可拿到服务器的公钥。

image 如果证书信息被篡改,没有证书私钥是不能改签名的,客户端收到证书之后对原文信息做个签名一比对就知道是否被篡改。

另一个问题,假设:“我们的证书被黑客用合法证书调包呢?”,证书的域名等信息是不能被篡改的,就算黑客调包换成了自己的合法证书,因为域名信息不一样,浏览器请求的时候一对比也可发现问题。

没有绝对的安全,如果黑客把自己的根证书安装在了你的计算机上,那么它就可以签发任意域名的虚假证书了,因此,遇到一些不可信的文件还是不要乱安装的好,保证根证书的安全

计算加密密钥

上面浏览器向服务器发起请求,服务器返回证书,这个过程双方会交换两个参数,分别是客户端的随机数、服务端的随机数,用于生成主密钥,但是主密钥的生成还依赖一个预主密钥。

不同的密钥交换算法,生成预主密钥的方法也不同。一种密钥交换算法是 RSA,它的密钥交换过程很简单,由客户端生成预主密钥,为 46 字节的随机数,使用服务器的公钥加密,经过密钥交换消息发送到服务端,服务端再用私钥就可解密出这个预主密钥

基于 RSA 的密钥交换算法被认为存在严重的漏洞威胁,任何能够接触到私钥的人(例如,由于政治、贿赂、强行进入等)都可恢复预主密钥,进而构建相同的主密钥,最终密钥泄漏就可解密之前记录的所有流量了。这种密钥交换算法正在被支持前向保密的其它算法替代,例如,ECDHE 算法在密钥交换时,每个链接使用的主密钥相互独立,如果出现问题也只是影响到当前会话,不能用于追溯解密任何其它的流量。

ECDHE 是临时椭圆曲线密钥交换算法,客户端和服务器会分别交换两个信息 Server Params、Client Params,在每次的链接中,都会生成一对新的临时公私钥。基于 ECDHE 算法客户端和服务端可分别计算出预主密钥(premaster secret)

这时客户端和服务端就分别拥有 Client Random、Server Random、Premaster Secret 三个随机数。

主密钥在 TLS v1.2 是通过一个伪随机函数 master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random) 计算出来的。

但主密钥并不是最终的会话密钥,最终的会话密钥使用 PRF 伪随机函数传入主密钥、客户端随机数、服务端随机数生成。

key_block = PRF(master_secret, "key expansion", server_random + client_random)

这个最终的会话密钥包括:对称加密密钥(symmetric key)、消息认证码密钥(mac key)、初始化项量(iv key,只在必要时生成)

上面这些都是在 TLS 的握手协议中完成的,当握手完成之后,客户端/服务端建立一个安全通信隧道,就可以发送应用程序数据了。

HTTPS 完整过程图示

协商对称加密密钥,这里面主要就是 TLS 的握手协议,这个过程很复杂,还有很多内容本篇最后没有详细的讲解,下图为笔者画的一个握手交互图,在下一篇文章中会通过 Wireshark 工具来抓取网络数据包做分析,做一个实战讲解,更深刻的理解 HTTPS 的原理

image

Reference