2 minute read

前言

人们最初设计互联网的时候,很少考虑到安全。这样造成一个问题:核心通信协议本质上是不安全的,只能依靠所有参与方的诚信。这显然有些理想主义了。

之后,随着安全需求的增加,我们提出了安全通信的共识,一般来说,具有下列特性:

  • 机密性
    仅有发送方和接收方能够理解传输数据的内容。
  • 信息完整性
    通信双方自然是想让通信的内容是完整的。
  • 身份鉴别
    确定通信双方是正确的。

基本上所有的安全措施,都是为了维护以上几点而设计的。

TCP 和 IP 是互联网的建立基础,它们本身就是非常容易受到攻击的;当然,不止它们,其他协议,比如 DNS 和 BGP 也是同样容易受到攻击。

可以把这篇分成三部分来看:

  1. TLS 依赖哪些密码学基础;
  2. 证书和身份认证是怎么工作的;
  3. TLS 1.2 和 TLS 1.3 的握手过程分别是什么样。

1. 加密算法

为了更加浅显易懂,我们虚构三个人物,通信双方 Alice 和 Bob,以及一个想要窃听的坏人 Eve。

对称加密

对称加密,又称之为私钥加密,是一种混淆算法。加密和解密使用相同的密钥

Alice 使用密钥给信息进行了加密,然后 Bob 在接收到信息的时候使用相同的密钥进行解密,Eve 虽然可以截获信息,但是没有密钥,无法解读。

比较典型的对称加密算法有 DES 和 AES 算法。

  • 优点: 计算量小、加密速度快、加密效率高。

  • 缺点:
    交易双方都使用同样密钥,安全性得不到保证; 每次使用对称加密算法时,都需要使用其他人不知道的惟一密钥,这会使得发收信息双方所拥有的钥匙数量呈几何级数增长,密钥管理成为负担; 如何传递密钥是一个问题。

非对称加密

非对称加密,又称双钥加密,加密和解密使用公钥和私钥。用公钥加密的内容必须用私钥才能解开。

Alice 将公钥发给 Bob,然后 Bob 用公钥将信息加密发给 Alice;Alice 在收到信息后,使用私钥进行解密。

最有名的非对称加密算法就是 RSA 算法.

  • 优点:
    密钥的安全性大大提升
  • 缺点:
    计算速度会慢许多; 无法保证公钥的合法性。

2. 消息完整性

为了保障消息完整性,推出了消息摘要算法。本身是一种哈希函数,函数的返回值,可以被称之为消息摘要或者指纹

这种函数是不可逆的,无法通过消息摘要反推出消息,所以又被称之为单向哈希函数

比较常见的消息摘要算法有 MD5SHA-1SHA-256 等。不过像 MD5SHA-1 这类算法,在现代安全场景下已经不再推荐使用。

3. 身份鉴别

为了进行身份鉴别,产生了一种消息认证码技术(Message Authentication Code,简称 MAC)。它可以被简单理解成一种带有密钥的哈希函数

它的前提是:通信双方已经共享了同一个密钥。运行机制可以简单描述如下:

  1. Alice 和 Bob 事先共享一个密钥;
  2. Alice 将要发送的消息和密钥一起计算出一个 MAC 值;
  3. Bob 收到消息和 MAC 值之后,也用同一个密钥重新计算;
  4. 如果两个 MAC 值一致,就说明消息没有被篡改,而且发送方也持有这个共享密钥。
  • 优点:
    保证消息完整性和真实性
  • 缺点: 不能防止 Alice 抵赖,否认发送消息。

4. 数字签名

数字签名相当于现实世界中的盖章、签名在计算机世界里的实现方式。它有两个特点:

  1. 数字签名可以证明消息确实由某个持有私钥的人发出。因为只有私钥持有者才能生成对应的签名。
  2. 数字签名可以防止报文被篡改。

在数字签名中,有 2 种行为:

  • 生成消息签名的行为
  • 验证消息签名的行为

生成消息签名的人是由消息发送者完成的,也称为“对消息签名”。生成签名就是根据消息内容计算数字签名的值。

验证数字签名的人,通常是消息接收方,或者任何持有公钥并需要验证签名的人。验证结果可以是成功,也可以是失败。

数字签名把“签名密钥”和“验证密钥”明确区分开来。验证密钥无法用来生成签名;签名密钥只能由签名人持有,而验证密钥则可以公开给所有需要验证签名的人。

5. 数字证书

数字证书可以理解成网络世界里的“身份证”,用来验证身份。一般来说常见的信任方式有三种:

  1. 手工指定证书 这种一般是用于银行等机构,之前使用 12306 买火车票的时候,也是使用的是自己的证书;
  2. 证书颁发机构颁发
    CA(Certificate Authority,证书颁发机构),可认为是大多数共同信任的第三方;
  3. 浏览器或操作系统自带
    浏览器和操作系统很多时候会内置一个证书颁发机构的名单,直接可以去信任。

一般来说,我们平时讨论得更多的是第二类。大致流程如下(真实细节会更复杂一些):

  1. Alice 先生成一对公钥和私钥,并向 CA 提交证书签发请求;
  2. CA 审核 Alice 的身份信息;
  3. CA 使用自己的私钥对 Alice 的证书内容进行签名,生成数字证书;
  4. Alice 在通信时把自己的证书发送给 Bob;
  5. Bob 使用自己信任的 CA 根证书链来验证这张证书是否可信;
  6. 验证通过后,Bob 才会信任证书里的公钥确实属于 Alice。

6. TLS

TLS 是一个密码学协议,用于保证通信双方之间的会话安全。从协议栈角度看,它位于 HTTP 之下、TCP 之上。

TLS是一个非常复杂且博大的协议,对于一般开发者来说只需要理解其工作原理即可,如果对它非常感兴趣,可以查阅RFC5246

它的头部如图所示。

7. TLS 握手

TLS 的握手是整个协议中最精密复杂的部分,在这个过程中,通信双方协商连接参数,并且完成身份验证。

由于使用的功能的不同,一般分为三种握手:

  1. 完整的握手,对服务器进行身份验证;
  2. 恢复之前的会话采用的简短握手;
  3. 对客户端和服务器都进行身份验证的握手。

完整的握手

主要分为以下四个步骤(TLS/1.2):

  1. 交换各自支持的功能,对需要的连接参数达成一致;
  2. 验证出示的证书,或使用其他方式进行身份验证;
  3. 对将用于保护会话的共享主密钥达成一致;
  4. 验证握手消息并未被第三方修改。

第一次握手

客户端向服务端发送 Client Hello 消息,其中携带客户端支持的协议版本、加密算法、压缩算法以及客户端生成的随机数;

第二次握手

服务端收到客户端支持的协议版本、加密算法等信息后;

  1. 向客户端发送 Server Hello 消息,并携带选择特定的协议版本、加密方法、会话 ID 以及服务端生成的随机数;
  2. 向客户端发送 Certificate 消息,即服务端的证书链,其中包含证书支持的域名、发行方和有效期等信息;
  3. 向客户端发送 Server Key Exchange 消息,传递公钥以及签名等信息;
  4. 向客户端发送 Server Hello Done 消息,通知服务端已经发送了全部的相关信息;
  5. (可选)向客户端发送可选的消息 CertificateRequest,验证客户端的证书;
第三次握手

客户端收到服务端的协议版本、加密方法、会话 ID 以及证书等信息后,验证服务端的证书;

  1. 向服务端发送 Client Key Exchange 消息,提供密钥交换所需的参数。具体内容会随着所选密钥交换算法的不同而不同:在早期 RSA 套件里,这里可能包含使用服务端公钥保护的 Pre Master Secret;而在 ECDHE 这类套件里,这里更多是密钥交换参数;
  2. 向服务端发送 Change Cipher Spec 消息,通知服务端后面的数据段会加密传输;
  3. 向服务端发送 Finished 消息,其中包含加密后的握手信息;
第四次握手

服务端收到 Change Cipher Spec 和 Finished 消息后;

  1. 向客户端发送 Change Cipher Spec 消息,通知客户端后面的数据段会加密传输;
  2. 向客户端发送 Finished 消息,验证客户端的 Finished 消息并完成 TLS 握手;

TLS 会话恢复

完整的 TLS 握手会带来额外的延迟和计算量,这个是非常大的性能损失。为了节约性能,TLS 提供了一个恢复功能,即在多个连接之间共享安全密钥。

在客户端,会保留之前会话的 Session ID 等信息,并在后续的 ClientHello 里带上它,提醒服务器“我这里可能有旧会话可以恢复”。如果服务器也还保留着对应会话状态,就可以走简短握手。如下图所示。

第一次握手

客户端向服务端发送 Client Hello 消息,其中携带客户端支持的协议版本、加密算法、压缩算法以及客户端生成的随机数;

第二次握手

服务端收到客户端支持的协议版本、加密算法等信息后;

  1. 向客户端发送 Server Hello 消息,并携带选择特定的协议版本、加密方法、会话 ID 以及服务端生成的随机数;
  2. 向客户端发送 Certificate 消息,即服务端的证书链,其中包含证书支持的域名、发行方和有效期等信息;
  3. 向客户端发送 Server Hello Done 消息,通知服务端已经发送了全部的相关信息;
    第三次握手

    客户端收到服务端的协议版本、加密方法、会话 ID 以及证书等信息后,验证服务端的证书;

  4. 向服务端发送 Client Key Exchange 消息,包含使用服务端公钥加密后的随机字符串,即预主密钥(Pre Master Secret);
  5. 向服务端发送 Change Cipher Spec 消息,通知服务端后面的数据段会加密传输;
  6. 向服务端发送 Finished 消息,其中包含加密后的握手信息;

8. TLS 1.3

TLS 1.2 的完整握手为例,通常需要大约 2 RTT 的额外时延,这是一个不小的开销。也正因为如此,后续版本的 TLS 在设计上越来越强调性能优化。

关于 TLS 1.3 的资料,可以阅读此文

为了提升 TLS 的性能和安全性,TLS 1.3 登场了。它主要有以下几个方向的改进:

  1. 相比于 TLS 1.2 的 2 RTT,TLS 可以做到 1 RTT,甚至 0 RTT;
  2. 引入了新的密钥协商机制 — PSK
  3. ServerHello 之后的所有握手消息采取了加密操作,可见明文大大减少;
  4. DSA 证书不再允许在 TLS 1.3 中使用。

为了做这些提升,TLS 1.3 在握手流程和密钥派生上相对 TLS 1.2 有了非常明显的变化。握手过程如下图所示:

第一次握手

客户端发送 ClientHello 消息,该消息主要包括客户端支持的协议版本,以及 KeyShare 等密钥交换参数;

第二次握手

服务端回复 ServerHello,包含选定的加密套件;然后发送证书给客户端;再使用证书对应的私钥对握手消息签名;同时结合客户端提供的参数完成 ECDHE 密钥交换,并把自己的 KeyShare 发送给客户端;

第三次握手

客户端接收到 KeyShare 之后,使用证书公钥验证服务端签名,获取服务器端的临时公钥,生成会话所需的共享密钥; 之后双方就可以基于这个共享密钥对后续消息进行加密传输。

我们可以发现,虽然图上看起来还有第三步,但从时延角度看,TLS 1.3 已经把完整握手压缩到了 1 RTT,如果配合 0-RTT 场景,甚至还能进一步减少等待时间。

PSK

PSK(pre-shared key)是一种新的密钥交换暨身份恢复机制,主要目标就是减少时延。在实现 0-RTT 的过程中,PSK 起到了关键作用。

在一次 TLS 握手之后,服务器可以发送一个 NST(new_session_ticket)报文给客户端,在报文中记录 PSK 的值、名字和有效期等信息,双方下一次建立连接的时候,可以使用该 PSK 值作为初始密钥材料。

因为 PSK 实际上来自前一次安全通信,所以只要双方都能证明自己持有同一个 PSK,就可以省去一部分完整握手里的额外开销。

如果双方持有还在有效期内的 PSK,就可以基于它来进行后续密钥协商。这里会用到一个 PSK_entry 结构,里面包含 PSK 的标识信息,以及与之对应的校验材料。

HMAC(Hash-based Message Authentication Code),哈希运算消息认证码,一种基于Hash函数和密钥进行消息认证的方法。

具体的实施过程如下:

  1. 客户端在 ClientHello 的相关扩展里传递自己可用的 PSK 信息;
  2. 服务端收到之后,选择其中一个自己也认可的 PSK,并验证双方是否真的持有同一份密钥材料;
  3. 验证通过后,双方基于这个 PSK 派生出后续要使用的初始密钥。

如果使用了 PSK,客户端就有机会发送 early_data。当然,这部分能力也伴随着额外的重放风险,所以服务端通常会更谨慎地控制它的使用场景。

引用

《HTTPS 权威指南》

SSL/TLS协议运行机制的概述

图解SSL/TLS协议

为什么 HTTPS 需要 7 次握手以及 9 倍时延

The Transport Layer Security (TLS) Protocol Version 1.3:draft-ietf-tls-tls13-latest