你想了解的 HTTPS 都在这里

HTTP 协议仅仅制定了互联网传输的标准,简化了直接使用 TCP 协议进行通信的难度。有关 HTTP 协议相关的讲解请看前面两节:

HTTP 协议详解

HTTP协议详解(二)

less is more 的概念本身很好,但是过于简单也会承担一些后果:

通信使用明文,内容可能会窃听

HTTP本身不具备加密的功能,所以也无法做到对通信整体(使用HTTP协议通信的请求和响应的内容)进行加密。即,HTTP报文使用明文(指未经过加密的报文)方式发送。

报文是否是正经用户发出的报文无法得知,可能被篡改

HTTP协议无法证明通信的报文完整性,因此,在请求或响应送出之后直到对方接收之前的这段时间内,即使请求或响应的内容遭到篡改,也没有办法获悉。
换句话说,没有任何办法确认,发出的请求/响应和接收到的请求/响应是前后相同的。

不验证发送方身份,可能遭遇伪装

HTTP协议中的请求和响应不会对通信方进行确认。在HTTP协议通信时,由于不存在确认通信方的处理步骤,任何人都可以发起请求。另外,服务器只要接收到请求,不管对方是谁都会返回一个响应(但也仅限于发送端的IP地址和端口号没有被Web服务器设定限制访问的前提下)

HTTP协议无法验证通信方身份,任何人都可以伪造虚假服务器欺骗用户,实现钓鱼欺诈,用户无法察觉。

因为有以上问题且随着时代发展,黑客的技术能力也越来越强,非加密的 HTTP 请求很容易引起相关的网络安全问题,对于安全性能的攻防控制需求也越来越强烈。

1. 什么是安全

既然 HTTP 不安全,那什么样的通信过程才是安全的呢?

通常认为,如果通信过程具备了四个特性,就可以认为是 安全 的,这四个特性是:机密性、完整性,身份认证和不可否认。

  • 机密性(Secrecy/Confidentiality):是指对数据的保密,只能由可信的人访问,对其他人是不可见的秘密,简单来说就是不能让不相关的人看到不该看的东西。

  • 完整性(Integrity,也叫一致性):是指数据在传输过程中没有被窜改,不多也不少,完完整整地保持着原状。

  • 身份认证(Authentication):是指确认对方的真实身份,也就是 证明你真的是你,保证消息只能发送给可信的人。

    如果通信时另一方是假冒的网站,那么数据再保密也没有用,黑客完全可以使用冒充的身份 套 出各种信息,加密和没加密一样。

  • 不可否认(Non-repudiation/Undeniable),也叫不可抵赖,意思是不能否认已经发生过的行为,不能说话不算数,耍赖皮

使用前三个特性,可以解决安全通信的大部分问题,但如果缺了不可否认,那通信的事务真实性就得不到保证,有可能出现 老赖

比如,小明借了小红一千元,没写借条,第二天矢口否认,小红也确实拿不出借钱的证据,只能认倒霉。另一种情况是小明借钱后还了小红,但没写收条,小红于是不承认小明还钱的事,说根本没还,要小明再掏出一千元。

所以,只有同时具备了机密性、完整性、身份认证、不可否认这四个特性,通信双方的利益才能有保障,才能算得上是真正的安全。

2. HTTPS 解决什么问题

HTTPS 为 HTTP 增加了刚才所说的四大安全特性。

HTTPS 其实是一个 非常简单 的协议,RFC 文档只有短短的 7 页,里面规定了新的协议名https,默认端口号 443,至于其他的什么请求 – 应答模式、报文结构、请求方法、URI、头字段、连接管理等等都完全沿用 HTTP,没有任何新的东西。

也就是说,除了协议名 http 和端口号 80 这两点不同,HTTPS 协议在语法、语义上和 HTTP 完全一样,优缺点也照单全收(当然要除去 明文不安全 )。

HTTPS 凭什么就能做到机密性、完整性这些安全特性呢?

秘密就在于 HTTPS 名字里的 S,它把 HTTP 下层的传输协议由 TCP/IP 换成了 SSL/TLS,由 HTTP over TCP/IP 变成了 HTTP over SSL/TLS,让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API,而是调用专门的安全接口。HTTPS 本身并没有什么惊世骇俗的本事,全是靠着后面的 SSL/TLS 撑腰。只要学会了 SSL/TLS,HTTPS 自然就手到擒来。

HTTPS 协议的主要功能基本都依赖于 TLS/SSL 协议,TLS/SSL 的功能实现主要依赖于三类基本算法:散列函数 、对称加密和非对称加密,利用非对称加密实现身份认证和密钥协商,对称加密算法采用协商的密钥对数据加密,基于散列函数验证信息的完整性。

15

3. 什么是 SSL/TLS

现在我们就来看看 SSL/TLS,它到底是个什么来历。

SSL 即安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层),由网景公司于 1994 年发明,有 v2 和 v3 两个版本,而 v1 因为有严重的缺陷从未公开过。

14

SSL 发展到 v3 时已经证明了它自身是一个非常好的安全通信协议,于是互联网工程组 IETF 在 1999 年把它改名为 TLS(传输层安全,Transport Layer Security),正式标准化,版本号从 1.0 重新算起,所以 TLS1.0 实际上就是 SSLv3.1。

到今天 TLS 已经发展出了三个版本,分别是 2006 年的 1.1、2008 年的 1.2 和去年(2018)的 1.3,每个新版本都紧跟密码学的发展和互联网的现状,持续强化安全和性能,已经成为了信息安全领域中的权威标准。

目前应用的最广泛的 TLS 是 1.2,而之前的协议(TLS1.1/1.0、SSLv3/v2)都已经被认为是不安全的,各大浏览器即将在 2020 年左右停止支持,所以接下来的讲解都针对的是 TLS1.2。

TLS 由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。

浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为密码套件(cipher suite,也叫加密套件)。

TLS 的密码套件命名非常规范,格式很固定。基本的形式是密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法,比如刚才的密码套件的意思就是:

握手时使用 ECDHE 算法进行密钥交换,用 RSA 签名和身份认证,握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM,摘要算法 SHA384 用于消息认证和产生随机数。

4. OpenSSL

说到 TLS,就不能不谈到 OpenSSL,它是一个著名的开源密码学程序库和工具包,几乎支持所有公开的加密算法和协议,已经成为了事实上的标准,许多应用软件都会使用它作为底层库来实现 TLS 功能,包括常用的 Web 服务器 Apache、Nginx 等。

OpenSSL 是从另一个开源库 SSLeay 发展出来的,曾经考虑命名为 OpenTLS,但当时(1998 年)TLS 还未正式确立,而 SSL 早已广为人知,所以最终使用了 OpenSSL 的名字。OpenSSL 目前有三个主要的分支,1.0.2 和 1.1.0 都将在今年(2019)年底不再维护,最新的长期支持版本是 1.1.1。

由于 OpenSSL 是开源的,所以它还有一些代码分支,比如 Google 的 BoringSSL、OpenBSD 的 LibreSSL,这些分支在 OpenSSL 的基础上删除了一些老旧代码,也增加了一些新特性,虽然背后有 大金主,但离取代 OpenSSL 还差得很远。

5. 机密性实现-加密

实现机密性最常用的手段是 加密(encrypt),就是把消息用某种方式转换成谁也看不懂的乱码,只有掌握特殊钥匙的人才能再转换出原始文本。

这里的钥匙就叫做 密钥(key),加密前的消息叫明文(plain text/clear text),加密后的乱码叫 密文(cipher text),使用密钥还原明文的过程叫 解密(decrypt)。

所有的加密算法都是公开的,任何人都可以去分析研究,而算法使用的 密钥 则必须保密。这个关键的 密钥 又是什么呢?由于 HTTPS、TLS 都运行在计算机上,所以 密钥 就是一长串的数字,但约定俗成的度量单位是 (bit),而不是 字节(byte)。比如,说密钥长度是 128,就是 16 字节的二进制串,密钥长度 1024,就是 128 字节的二进制串。

按照密钥的使用方式,加密可以分为两大类:对称加密 和 非对称加密

对称加密

对称加密 很好理解,就是指加密和解密时使用的密钥都是同一个,是 对称 的。只要保证了密钥的安全,那整个通信过程就可以说具有了机密性。

举个例子,你想要登录某网站,只要事先和它约定好使用一个对称密码,通信过程中传输的全是用密钥加密后的密文,只有你和网站才能解密。黑客即使能够窃听,看到的也只是乱码,因为没有密钥无法解出明文,所以就实现了机密性。

3

TLS 里有非常多的对称加密算法可供选择,比如 RC4、DES、3DES、AES、ChaCha20 等,但前三种算法都被认为是不安全的,通常都禁止使用,目前常用的有 AES-128、AES-192、AES-256 和 ChaCha20。

DES 的全称是 Data Encryption Standard(数据加密标准) ,它是用于数字数据加密的对称密钥算法。尽管其 56 位的短密钥长度使它对于现代应用程序来说太不安全了,但它在加密技术的发展中具有很大的影响力。

AES 的意思是 高级加密标准(Advanced Encryption Standard),AES-128, AES-192 和 AES-256 都是属于 AES 。密钥长度可以是 128、192 或 256。它是 DES 算法的替代者,安全强度很高,性能也很好,而且有的硬件还会做特殊优化,所以非常流行,是应用最广泛的对称加密算法。

ChaCha20 是 Google 设计的另一种加密算法,密钥长度固定为 256 位,纯软件运行性能要超过 AES,曾经在移动客户端上比较流行,但 ARMv8 之后也加入了 AES 硬件优化,所以现在不再具有明显的优势。

分组模式

对称算法还有一个 分组模式 的概念,它可以让算法用固定长度的密钥加密任意长度的明文,把小秘密(即密钥)转化为大秘密(即密文)。

最早有 ECB、CBC、CFB、OFB 等几种分组模式,但都陆续被发现有安全漏洞,所以现在基本都不怎么用了。最新的分组模式被称为 AEAD(Authenticated Encryption with Associated Data),在加密的同时增加了认证的功能,常用的是 GCM、CCM 和 Poly1305。

把上面这些组合起来,就可以得到 TLS 密码套件中定义的对称加密算法。

比如,AES128-GCM,意思是密钥长度为 128 位的 AES 算法,使用的分组模式是 GCM;ChaCha20-Poly1305 的意思是 ChaCha20 算法,使用的分组模式是 Poly1305。

非对称加密

对称加密看上去好像完美地实现了机密性,但其中有一个很大的问题:如何把密钥安全地传递给对方,术语叫 密钥交换。因为在对称加密算法中只要持有密钥就可以解密。如果你和网站约定的密钥在传递途中被黑客窃取,那他就可以在之后随意解密收发的数据,通信过程也就没有机密性可言了。

这个问题该怎么解决呢?一般来说除非是双方私下约定好了密钥,如果是每次通信都会发生变化的密钥是不能在通信过程中带给对端,这样你就陷入了要给密钥加一次密的尴尬境地。所以,就出现了非对称加密(也叫公钥加密算法)。

它有两个密钥,一个叫 公钥(public key),一个叫 私钥(private key)。两个密钥是不同的,不对称,公钥可以公开给任何人使用,而私钥必须严格保密。

公钥和私钥有个特别的 单向 性,虽然都可以用来加密解密,但公钥加密后只能用私钥解密,反过来,私钥加密后也只能用公钥解密。

非对称加密可以解决 密钥交换 的问题。网站秘密保管私钥,在网上任意分发公钥,你想要登录网站只要用公钥加密就行了,密文只能由私钥持有者才能解密。而黑客因为没有私钥,所以就无法破解密文。

4

非对称加密算法的设计要比对称算法难得多,在 TLS 里只有很少的几种,比如 DH、DSA、RSA、ECC 等。

RSA 可能是其中最著名的一个,几乎可以说是非对称加密的代名词,它的安全性基于 整数分解 的数学难题,使用两个超大素数的乘积作为生成密钥的材料,想要从公钥推算出私钥是非常困难的。

10 年前 RSA 密钥的推荐长度是 1024,但随着计算机运算能力的提高,现在 1024 已经不安全,普遍认为至少要 2048 位。

ECC(Elliptic Curve Cryptography)是非对称加密里的 后起之秀,它基于 椭圆曲线离散对数 的数学难题,使用特定的曲线方程和基点生成公钥和私钥,子算法 ECDHE 用于密钥交换,ECDSA 用于数字签名。

ECDHE 即使用椭圆曲线(ECC)的 DH 算法,优点是能用较小的素数(256 位)实现 RSA 相同的安全等级。缺点是算法实现复杂,用于密钥交换的历史不长,没有经过长时间的安全攻击测试。

目前比较常用的两个曲线是 P-256(secp256r1,在 OpenSSL 称为 prime256v1)和 x25519。P-256 是 NIST(美国国家标准技术研究所)和 NSA(美国国家安全局)推荐使用的曲线,而 x25519 被认为是最安全、最快速的曲线。

比起 RSA,ECC 在安全强度和性能上都有明显的优势。160 位的 ECC 相当于 1024 位的 RSA,而 224 位的 ECC 则相当于 2048 位的 RSA。因为密钥短,所以相应的计算量、消耗的内存和带宽也就少,加密解密的性能就上去了,对于现在的移动互联网非常有吸引力。

混合加密

看到这里你是不是认为可以抛弃对称加密,只用非对称加密来实现机密性呢?

很遗憾,虽然非对称加密没有 密钥交换 的问题,但因为它们都是基于复杂的数学难题,运算速度很慢,即使是 ECC 也要比 AES 差上好几个数量级。如果仅用非对称加密,虽然保证了安全,但通信速度有如乌龟、蜗牛,实用性就变成了零。

是不是能够把对称加密和非对称加密结合起来呢,两者互相取长补短,即能高效地加密解密,又能安全地密钥交换。

这就是现在 TLS 里使用的 混合加密 方式,其实说穿了也很简单:

在通信刚开始的时候使用非对称算法,比如 RSA、ECDHE,首先解决密钥交换的问题。

然后用随机数产生对称算法使用的 会话密钥(session key),再用公钥加密。因为会话密钥很短,通常只有 16 字节或 32 字节,所以慢一点也无所谓。

对方拿到密文后用私钥解密,取出会话密钥。这样,双方就实现了对称密钥的安全交换,后续就不再使用非对称加密,全都使用对称加密。

5

这样混合加密就解决了对称加密算法的密钥交换问题,而且安全和性能兼顾,完美地实现了机密性。

6. 完整性

摘要算法

实现完整性的手段主要是 摘要算法(Digest Algorithm),也就是常说的散列函数、哈希函数(Hash Function)。

你可以把摘要算法近似地理解成一种特殊的压缩算法,它能够把任意长度的数据压缩成固定长度、而且独一无二的摘要字符串,就好像是给这段数据生成了一个数字指纹。

换一个角度,也可以把摘要算法理解成特殊的单向加密算法,它只有算法,没有密钥,加密后的数据无法解密,不能从摘要逆推出原文。

摘要算法实际上是把数据从一个 大空间 映射到了 小空间,所以就存在 冲突(collision,也叫碰撞)的可能性,就如同现实中的指纹一样,可能会有两份不同的原文对应相同的摘要。好的摘要算法必须能够 抵抗冲突,让这种可能性尽量地小。

因为摘要算法对输入具有 单向性雪崩效应,输入的微小不同会导致输出的剧烈变化,所以也被 TLS 用来生成伪随机数(PRF,pseudo random function)。

你一定在日常工作中听过、或者用过 MD5(Message-Digest 5)、SHA-1(Secure Hash Algorithm 1),它们就是最常用的两个摘要算法,能够生成 16 字节和 20 字节长度的数字摘要。但这两个算法的安全强度比较低,不够安全,在 TLS 里已经被禁止使用了。

目前 TLS 推荐使用的是 SHA-1 的后继者:SHA-2。

SHA-2 实际上是一系列摘要算法的统称,总共有 6 种,常用的有 SHA224、SHA256、SHA384,分别能够生成 28 字节、32 字节、48 字节的摘要。

摘要算法保证了 数字摘要 和原文是完全等价的。所以,我们只要在原文后附上它的摘要,就能够保证数据的完整性。

比如,你发了条消息:有内鬼,终止交易,然后再加上一个 SHA-2 的摘要。网站收到后也计算一下消息的摘要,把这两份 指纹 做个对比,如果一致,就说明消息是完整可信的,没有被修改。

如果黑客在中间哪怕改动了一个标点符号,摘要也会完全不同,网站计算比对就会发现消息被窜改,是不可信的。

不过摘要算法不具有机密性,如果明文传输,那么黑客可以修改消息后把摘要也一起改了,网站还是鉴别不出完整性。

所以,真正的完整性必须要建立在机密性之上,在混合加密系统里用会话密钥加密消息和摘要,这样黑客无法得知明文,也就没有办法动手脚了。这有个术语,叫哈希消息认证码(HMAC)。

6

数字签名

加密算法结合摘要算法,我们的通信过程可以说是比较安全了。但这里还有漏洞,就是通信的两个端点(endpoint)。

就像一开始所说的,黑客可以伪装成网站来窃取信息。而反过来,他也可以伪装成你,向网站发送支付、转账等消息,网站没有办法确认你的身份,钱可能就这么被偷走了。

现实生活中,解决身份认证的手段是签名和印章,只要在纸上写下签名或者盖个章,就能够证明这份文件确实是由本人而不是其他人发出的。

回想一下上面的介绍在 TLS 里有什么东西和现实中的签名、印章很像,只能由本人持有,而其他任何人都不会有呢?只要用这个东西,就能够在数字世界里证明你的身份。

没错,这个东西就是非对称加密里的 私钥,使用私钥再加上摘要算法,就能够实现 数字签名,同时实现 身份认证不可否认

数字签名的原理其实很简单,就是把公钥私钥的用法反过来,之前是公钥加密、私钥解密,现在是私钥加密、公钥解密。

但又因为非对称加密效率太低,所以私钥只加密原文的摘要,这样运算量就小的多,而且得到的数字签名也很小,方便保管和传输。

签名和公钥一样完全公开,任何人都可以获取。但这个签名只有用私钥对应的公钥才能解开,拿到摘要后,再比对原文验证完整性,就可以像签署文件一样证明消息确实是你发的。

7

刚才的这两个行为也有专用术语,叫做 签名验签

只要你和网站互相交换公钥,就可以用 签名验签 来确认消息的真实性,因为私钥保密,黑客不能伪造签名,就能够保证通信双方的身份。

比如,你用自己的私钥签名一个消息 马冬梅你别跑。网站收到后用你的公钥验签,确认身份没问题,于是也用它的私钥签名消息 十年翻身同学会,拆散一对是一对。你收到后再用它的公钥验一下,也没问题,这样你和网站就都知道对方不是假冒的,后面就可以用混合加密进行安全通信了。

有关加解密相关的算法,可以看我另一篇博文:

加解密算法浅析

可以帮助大家对加解密有一个统一的认识。

7. 数字证书和 CA–身份认证

到现在,综合使用对称加密、非对称加密和摘要算法,我们已经实现了安全的四大特性,是不是已经完美了呢?

不是的,这里还有一个 公钥的信任 问题。因为谁都可以发布公钥,我们还缺少防止黑客伪造公钥的手段,也就是说,怎么来判断这个公钥就是你的公钥呢?

我们可以用类似密钥交换的方法来解决公钥认证问题,用别的私钥来给公钥签名,显然,这又会陷入俄罗斯套娃

但这次实在是没招了,要终结这个死循环,就必须引入外力,找一个公认的可信第三方,让它作为信任的起点,递归的终点,构建起公钥的信任链。

这个第三方就是我们常说的 CA(Certificate Authority,证书认证机构)。它就像网络世界里的公安局、教育部、公证中心,具有极高的可信度,由它来给各个公钥签名,用自身的信誉来保证公钥无法伪造,是可信的。

CA 对公钥的签名认证也是有格式的,不是简单地把公钥绑定在持有者身份上就完事了,还要包含序列号、用途、颁发者、有效时间等等,把这些打成一个包再签名,完整地证明公钥关联的各种信息,形成 数字证书(Certificate)。

知名的 CA 全世界就那么几家,比如 DigiCert、VeriSign、Entrust、Let’s Encrypt 等,它们签发的证书分 DV、OV、EV 三种,区别在于可信程度。

DV 是最低的,只是域名级别的可信,背后是谁不知道。EV 是最高的,经过了法律和审计的严格核查,可以证明网站拥有者的身份(在浏览器地址栏会显示出公司的名字,例如 Apple、GitHub 的网站)。

不过,CA 怎么证明自己呢?

这还是信任链的问题。小一点的 CA 可以让大 CA 签名认证,但链条的最后,也就是Root CA,就只能自己证明自己了,这个就叫 自签名证书(Self-Signed Certificate)或者 根证书(Root Certificate)。你必须相信,否则整个证书信任链就走不下去了。

8

有了这个证书体系,操作系统和浏览器都内置了各大 CA 的根证书,上网的时候只要服务器发过来它的证书,就可以验证证书里的签名,顺着证书链(Certificate Chain)一层层地验证,直到找到根证书,就能够确定证书是可信的,从而里面的公钥也是可信的。

证书体系的弱点

证书体系(PKI,Public Key Infrastructure)虽然是目前整个网络世界的安全基础设施,但绝对的安全是不存在的,它也有弱点,还是关键的 信任 二字。

如果 CA 失误或者被欺骗,签发了错误的证书,虽然证书是真的,可它代表的网站却是假的。

还有一种更危险的情况,CA 被黑客攻陷,或者 CA 有恶意,因为它(即根证书)是信任的源头,整个信任链里的所有证书也就都不可信了。

这两种事情并不是耸人听闻,都曾经实际出现过。所以需要再给证书体系打上一些补丁。

针对第一种,开发出了 CRL(证书吊销列表,Certificate revocation list)和 OCSP(在线证书状态协议,Online Certificate Status Protocol),及时废止有问题的证书。

对于第二种因为涉及的证书太多,就只能操作系统或者浏览器从根上下狠手了,撤销对 CA 的信任,列入黑名单,这样它颁发的所有证书就都会被认为是不安全的。

我们来看一下Github的数字证书长什么样子:

16

证书上的信息可以得知:类型是 EV,拥有最高权威认证;过期时间;证书所属组织;证书签发机构。

8. TLS 协议的组成

当你在浏览器地址栏里键入 https 开头的 URI,再按下回车,会发生什么呢?

浏览器首先要从 URI 里提取出协议名和域名。因为协议名是https,所以浏览器就知道了端口号是默认的 443,它再用 DNS 解析域名,得到目标的 IP 地址,然后就可以使用三次握手与网站建立 TCP 连接了。

在 HTTP 协议里,建立连接后,浏览器会立即发送请求报文。但现在是 HTTPS 协议,它需要再用另外一个握手过程,在 TCP 上建立安全连接,之后才是收发 HTTP 报文。

这个握手过程与 TCP 有些类似,是 HTTPS 和 TLS 协议里最重要、最核心的部分。

在讲 TLS 握手之前,先简单介绍一下 TLS 协议的组成。

TLS 包含几个子协议,你也可以理解为它是由几个不同职责的模块组成,比较常用的有:记录协议、警报协议、握手协议、变更密码规范协议等。

  • 记录协议(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。
  • 警报协议(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。
  • 握手协议(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。
  • 变更密码规范协议(Change Cipher Spec Protocol),它非常简单,就是一个通知,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。

下面的这张图简要地描述了 TLS 的握手过程,其中每一个都是一个记录,多个记录组合成一个 TCP 包发送。所以,最多经过两次消息往返(4 个消息)就可以完成握手,然后就可以在安全的通信环境里发送 HTTP 报文,实现 HTTPS 协议。

在 TCP 完成三次握手建立连接之后, HTTPS 开始加密认证握手流程。 TLS 的握手过程如下:

以上过程我们可以使用 wireShark 抓包工具看到。

在 TCP 建立连接之后,浏览器会首先发一个 client_hello 消息,里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random),用于后续生成会话密钥:

17

可以看到客户端发送给服务端他所支持的密码套件有 16 套之多,另外客户端使用的 TLS 版本是1.2。服务器收到 Client Hello 后,会返回一个 Server Hello 消息。把版本号对一下,也给出一个随机数(Server Random),然后从客户端的列表里选一个作为本次通信使用的密码套件,在这里它选择了Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)

18

然后服务器为了证明自己的身份,就把证书也发给了客户端(Server Certificate)。

19

接下来是一个关键的操作,因为服务器选择了 ECDHE 算法,所以它会在证书后发送 Server Key Exchange 消息,里面是 椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。

Handshake Protocol: Server Key Exchange
    EC Diffie-Hellman Server Params
        Curve Type: named_curve (0x03)
        Named Curve: x25519 (0x001d)
        Pubkey: 3b39deaf00217894e...
        Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
        Signature: 37141adac38ea4...

这相当于说:刚才我选的密码套件有点复杂,所以再给你个算法的参数,和刚才的随机数一样有用别丢了。为了防止别人冒充,我又盖了个章。

之后是 Server Hello Done 消息,服务器说:我的信息就是这些,打招呼完毕。

这样第一个消息往返就结束了(两个 TCP 包),结果是客户端和服务器通过明文共享了三个信息:Client Random、Server Random 和 Server Params

客户端这时也拿到了服务器的证书,那这个证书是不是真实有效的呢?下面客户端就开始鉴定证书的真伪。校验过程如下:

  1. 首先读取证书所有者有效期等信息进行校验,查找内置的收信人证书发布机构 CA 与证书 CA 相对比,校验是否是合法机构颁发;这一步会做如下操作:

    1. 证书链的可信性 (trusted certificate path)校验,发行服务器证书的 CA 是否可靠;
    2. 证书是否吊销 (revocation),有两类方式离线 CRL 与在线 OCSP,不同的客户端行为会不同;
    3. 有效期 (expiry date),证书是否在有效时间范围;
    4. 域名 (domain),核查证书域名是否与当前的访问域名匹配,匹配规则后续分析。
  2. 第一步检验通过之后产生随机数 Pre-master, 并用证书公钥加密,发送给服务器,作为以后对称加密的密钥。

客户端向服务器发送 Client Key Exchange。最后客户端与服务器互发 Change Cipher Spec,Encrypted Handshake Message

20

接下来服务器接收到客户端发送的 Pre-master,解密出被加密的 Pre-master,然后通知客户端:

握手阶段结束,随后所有的通信将使用对称加密的方式进行。

9. 双向认证

上面已经讲完了 TLS 握手,从上面的流程不难看出只是客户端认证了服务器的身份,而服务器是没有认证客户端身份的,我们简称 单向认证。通常单向认证通过后已经建立了安全通信,用账号、密码等简单的手段就能够确认用户的真实身份。但为了防止账号、密码被盗,有的时候(比如网上银行)还会使用 U 盾给用户颁发客户端证书,实现双向认证,这样会更加安全。

双向认证的流程也没有太多变化,只是在 Server Hello Done 之后,Client Key Exchange 之前,客户端要发送 Client Certificate 消息,服务器收到后也把证书链走一遍,验证客户端的身份。

不过 TLS1.2 已经是 10 年前(2008 年)的协议了,虽然历经考验但毕竟 岁月不饶人,在安全、性能等方面已经跟不上如今的互联网。经过四年近 30 个草案的反复打磨,TLS1.3 在2018 年推出,再次确立了信息安全领域的新标准。

10. 最大化兼容性

由于 1.1、1.2 等协议已经出现了很多年,很多应用软件、中间代理(官方称为MiddleBox)只认老的记录协议格式,更新改造很困难,甚至是不可行(设备僵化)。

在早期的试验中发现,一旦变更了记录头字段里的版本号,也就是由 0x303(TLS1.2)改为 0x304(TLS1.3)的话,大量的代理服务器、网关都无法正确处理,最终导致 TLS 握手失败。

为了保证这些被广泛部署的老设备能够继续使用,避免新协议带来的冲击,TLS1.3 不得不做出妥协,保持现有的记录格式不变,通过伪装来实现兼容,使得 TLS1.3 看上去像是 TLS1.2。

那么,该怎么区分 1.2 和 1.3 呢?

这要用到一个新的 扩展协议(Extension Protocol),它有点 补充条款 的意思,通过在记录末尾添加一系列的 扩展字段 来增加新的功能,老版本的 TLS 不认识它可以直接忽略,这就实现了后向兼容。

在记录头的 Version 字段被兼容性固定的情况下,只要是 TLS1.3 协议,握手的 Hello 消息后面就必须有 supported_versions 扩展,它标记了 TLS 的版本号,使用它就能区分新旧协议。

你可以看一下Client Hello消息后面的扩展,只是因为服务器不支持 1.3,所以就后向兼容降级成了 1.2。

21

TLS1.3 利用扩展实现了许多重要的功能,比如supported_groupskey_sharesignature_algorithmsserver_name等,这些等后面用到的时候再说。

11. 强化安全

TLS1.2 在十来年的应用中获得了许多宝贵的经验,陆续发现了很多的漏洞和加密算法的弱点,所以 TLS1.3 就在协议里修补了这些不安全因素。

比如:

  • 伪随机数函数由 PRF 升级为 HKDF(HMAC-based Extract-and-Expand Key Derivation Function);
  • 明确禁止在记录协议里使用压缩;
  • 废除了 RC4、DES 对称加密算法;
  • 废除了 ECB、CBC 等传统分组模式;
  • 废除了 MD5、SHA1、SHA-224 摘要算法;
  • 废除了 RSA、DH 密钥交换算法和许多命名曲线。

经过这一番减肥瘦身之后,TLS1.3 里只保留了 AES、ChaCha20 对称加密算法,分组模式只能用 AEAD 的 GCM、CCM 和 Poly1305,摘要算法只能用 SHA256、SHA384,密钥交换算法只有 ECDHE 和 DHE,椭圆曲线也被到只剩 P-256 和 x25519 等 5 种。

算法精简后带来了一个意料之中的好处:原来众多的算法、参数组合导致密码套件非常复杂,难以选择,而现在的 TLS1.3 里只有 5 个套件,无论是客户端还是服务器都不会再犯选择困难症了。

密码套件名称 代码
Cipher Suite: TLS_AES_128_GCM_SHA256 0x1301
Cipher Suite: TLS_AES_256_GCM_SHA384 0x1302
Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 0x1303
TLS-AES128-CCM-SHA256 0x1304
TLS-AES128-CCM-8-SHA256 0x1305

这里还要特别说一下废除 RSA 和 DH 密钥交换算法的原因。

在 RSA 密钥交换中,共享密钥由客户端生成,然后客户端利用服务器的公钥(从证书中提取)将共享密钥加密并将其发送到服务器。

DH 算法由 DiffieHellman 于1976年发明,即所谓的 Diffie-Hellman 密钥交换。在 Diffie-Hellman 中,客户端和服务器都从创建 DH 参数对开始。然后,他们将其 DH 参数的公共部分发送给另一方。当双方都收到对方方的公共参数时,它们将它与自己的私钥组合在一起,最终计算出同一个值:前主密钥。然后,服务器使用数字签名来确保交换未被篡改。如果客户端和服务器都为每次密钥交换选择一个新的 DH 参数,则该密钥交换称为 “Ephemeral”(DHE)。

DH 是一个功能强大的工具,但并非所有DH参数都可以 “安全” 使用。DH 的安全性取决于称为数学中离散对数问题的难度。如果可以解决一组参数的离散对数问题,就可以提取私钥并破坏协议的安全性。一般来说,使用的数字越大,解决离散对数问题就越困难。因此,如果您选择较小的DH参数,就有可能遭受攻击。

上两种模式都会使客户端和服务器具有共享密钥,但 RSA 模式有一个严重的缺点,这是因为它不具有 前向安全(Forward Secrecy)。

假设有这么一个很有耐心的黑客,一直在长期收集混合加密系统收发的所有报文。如果加密系统使用服务器证书里的 RSA 做密钥交换,一旦私钥泄露或被破解(使用社会工程学或者巨型计算机),那么黑客就能够使用私钥解密出之前所有报文的 Pre-Master,再算出会话密钥,破解所有密文。

而 ECDHE 算法在每次握手时都会生成一对临时的公钥和私钥,每次通信的密钥对都是不同的,也就是 一次一密,即使黑客花大力气破解了这一次的会话密钥,也只是这次通信被攻击,之前的历史消息不会受到影响,仍然是安全的。

所以现在主流的服务器和浏览器在握手阶段都已经不再使用 RSA,改用 ECDHE,而 TLS1.3 在协议里明确废除 RSA 和 DH 则在标准层面保证了前向安全。

RSA 密钥交换在一段时间内一直存在问题,其原因不仅仅是因为它支持前向保密。而是因为想要正确的实现 RSA 密钥交换也是不容易的。1998年,Daniel Bleichenbacher 在 SSL 中使用 RSA 加密时发现了一个漏洞并创建了所谓的 “百万消息攻击”, 它允许攻击者通过发送数百万条消息或一些特定的消息给服务器,根据服务器响应的不同错误码计算加密密钥, 进而解密消息。多年来,这种攻击得到了改进,在某些情况下只需要数千次就可破解,这使得在笔记本电脑上都可以破解。最近发现,许多大型网站(包括facebook.com)在2017年也受到 Bleichenbacher 变种漏洞的影响,即ROBOT攻击

为了降低非前向加密连接和 Bleichenbacher 漏洞所带来的风险,RSA 加密已从 TLS 1.3 中删除,将 Diffie-Hellman Ephemeral 作为唯一的密钥交换机制。

12. 提升性能

HTTPS 建立连接时除了要做 TCP 握手,还要做 TLS 握手,在 1.2 中会多花两个消息往返(2-RTT),可能导致几十毫秒甚至上百毫秒的延迟,在移动网络中延迟还会更严重。

现在因为密码套件大幅度简化,也就没有必要再像以前那样走复杂的协商流程了。TLS1.3 压缩了以前的 Hello 协商过程,删除了 Key Exchange 消息,把握手时间减少到了 1-RTT,效率提高了一倍。

那么它是怎么做的呢?其实具体的做法还是利用了扩展。客户端在 Client Hello 消息里直接用 supported_groups 带上支持的曲线,比如 P-256、x25519,用 key_share带上曲线对应的客户端公钥参数,用signature_algorithms带上签名算法。

服务器收到后在这些扩展里选定一个曲线和参数,再用key_share 扩展返回服务器这边的公钥参数,就实现了双方的密钥交换,后面的流程就和 1.2 基本一样了。

除了标准的 1-RTT 握手,TLS1.3 还引入了0-RTT 握手,用 pre_shared_keyearly_data 扩展,在 TCP 连接后立即就建立安全连接发送加密消息,不过这需要有一些前提条件,限于篇幅这里暂且不表。

13. HTTPS使用成本

HTTPS 截止到目前为止国内的大中型企业基本都支持并已经使用。一般来讲,使用 HTTPS 前大家可能会非常关注如下问题:

  1. 证书费用以及更新维护。费用主要是证书的申请,而且现在也有了免费的证书机构,比如著名的 mozilla 发起的免费证书项目:let’s encrypt 就支持免费证书安装和自动更新。
  2. HTTPS 降低用户访问速度。HTTPS 对速度会有一定程度的降低,但是只要经过合理优化和部署,HTTPS 对速度的影响完全可以接受。在很多场景下,HTTPS 速度完全不逊于 HTTP,如果使用 SPDY,HTTPS 的速度甚至还要比 HTTP 快。