从非对称协商到对称加密,从证书链验证到重放攻击防御,彻底理解 HTTPS 的安全机制
HTTPS 的全称是 HTTP Secure,也常被称为 HTTP over SSL或 HTTP over TLS。它的核心定义非常简单:
HTTPS 不是一个新的传输协议,它只是在 HTTP 和 TCP 之间插入了一个 TLS 层:
数据流是:HTTP 报文 → 交给 TLS 加密 → 再交给 TCP 传输。反过来,接收时:TCP 收到数据 → TLS 解密 → 还原为 HTTP 报文。
SSL(Secure Socket Layer,安全套接层)是 Netscape 公司在 1990 年代发明的加密协议。后来标准化为 TLS(Transport Layer Security,传输层安全)。
HTTPS 的设计面临一个两难问题:
| 加密方式 | 优点 | 缺点 |
|---|---|---|
| 对称加密 (加密和解密用同一把密钥) |
速度快,适合大量数据 | 密钥怎么安全地交给对方? 网络传输密钥会被窃听,当面给不现实 |
| 非对称加密 (公钥加密,私钥解密) |
密钥分发安全,公钥可以公开传播 | 速度慢,不适合加密大量数据 |
HTTPS 的解法是取两者之长:
想象你要和远方的朋友安全通信:
保险箱(非对称加密)只用来传钥匙,真正的货物(HTTP 报文)用普通锁(对称加密)来保护。保险箱虽安全但笨重,普通锁虽简单但轻便。
HTTPS 通信分为两个阶段,不要混淆:
| 阶段 | 名称 | 职责 | 时长 |
|---|---|---|---|
| 第一阶段 | TLS 握手(Handshake) | 验证身份 + 协商对称密钥 | 连接建立时执行一次 |
| 第二阶段 | 对称加密通信 | 用协商好的密钥加密/解密 HTTP 报文 | 持续整个会话 |
发送是加密下行,接收是解密上行,路径完全对称
在发送第一条 HTTP 报文之前,客户端和服务器必须先完成 TLS 握手。以下描述的是 TLS 1.2 的 RSA 密钥交换模式,这也是目前最广泛部署的握手流程。整个过程可以浓缩为四句话:
客户端主动发起握手,发送一个 Client Hello消息。它虽然可以被看作"一个字节的数据",但实际上携带了丰富的协商信息:
服务器收到 Client Hello 后,做出选择并回应 Server Hello:
到这一步,客户端和服务端都拥有了相同的 TLS 版本、相同的加密套件、以及两个随机数(客户端随机数 + 服务器随机数)。
Server Hello 之后,服务器立即发送自己的证书(Certificate)。证书的核心作用是:
证书中的关键信息包括:
good.com)客户端收到证书后,需要做两重验证:
验证通过后,客户端用服务器公钥加密一个Pre-Master Secret(客户端生成的第三个随机数),发送给服务器。
| 步骤 | 谁→谁 | 内容 |
|---|---|---|
| 第 5 步 | 客户端 → 服务器 | 客户端生成 Pre-Master Secret(第三个随机数),用服务器公钥加密后发送 |
| 密钥生成 | 双方各自计算 | 用三个随机数(Client Random + Server Random + Pre-Master Secret)算出 Master Secret,再从 Master Secret 派生出四把密钥 |
| 第 6 步 | 客户端 → 服务器 | "我将使用加密通信" + Finished(用协商好的密钥加密的校验消息) |
| 第 7 步 | 服务器 → 客户端 | "我将使用加密通信" |
| 第 8 步 | 服务器 → 客户端 | Finished |
以上 8 步是TLS 1.2(RSA 密钥交换模式)的完整流程。TLS 1.3(2018 年发布,RFC 8446)对此做了大幅简化,核心变化如下:
| 对比维度 | TLS 1.2(本章描述) | TLS 1.3(2018) |
|---|---|---|
| 握手往返(RTT) | 2-RTT(两次来回,慢) | 1-RTT(一次来回,0-RTT 会话恢复更快) |
| 密钥交换方式 | RSA(Pre-Master Secret)或ECDHE | 仅 ECDHE(前向安全性强制要求) RSA 密钥交换被彻底废除 |
| 加密套件协商 | 客户端列出算法,服务器在 Server Hello 中选择 | 客户端在 Client Hello 中直接带上密钥参数(Key Share),服务器立即生成密钥 |
| Change Cipher Spec | 独立的步骤 5 和步骤 7 | 合并到 Finished 消息中,不再独立 |
| 证书加密 | 证书在握手中明文传输(可被嗅探) | 证书在握手后半段用临时密钥加密传输 |
| 完整性保护 | HMAC(加密和完整性分开,如 AES-CBC + HMAC) | AEAD(加密和完整性合一,如 AES-GCM、ChaCha20-Poly1305) |
| 废弃的旧算法 | 支持 RC4、3DES、CBC 模式等 | 全部移除,只保留安全的 AEAD 算法 |
TLS 1.3 简化后的握手流程:
服务器证书上的签名(Signature)是这样生成的:CA 用自己的私钥对服务器证书内容的哈希值做一次非对称加密,得到密文——这就是签名。验证时反过来:
验证时:
但客户端怎么拿到 CA 的公钥来验证签名呢?这引出了证书链:
验证链路:用根 CA 公钥 → 验证中间 CA 的签名 → 得到中间 CA 公钥 → 验证服务器证书签名 → 确认服务器公钥合法。
为什么不需要无限递归?因为根证书是操作系统/浏览器出厂时预置的,被认为是无条件可信的。这些根证书由全球公认的 CA 机构持有(如 DigiCert、Let's Encrypt、GlobalSign 等)。
即使证书的签名链验证通过,仍然可能有危险。考虑这个场景:
你本想去 good.com,但请求被 bad.com的攻击者拦截。攻击者拿出自己的合法证书(由正规 CA 签发,确实属于 bad.com)给你验证。你验证证书签名——完全通过!因为证书本身确实是合法的。
所以客户端验证的第二步是检查主机名:证书里包含了服务器的主机名和域名。客户端会比对证书上的域名和自己正在访问的域名是否一致。如果访问 good.com但证书上写的是 bad.com,浏览器就会报"证书不匹配"的警告。
| 随机数 | 来源 | 传输方式 | 是否可见 |
|---|---|---|---|
| ① Client Random | 客户端生成 | Client Hello 中明文发送 | 所有人可见 |
| ② Server Random | 服务器生成 | Server Hello 中明文发送 | 所有人可见 |
| ③ Pre-Master Secret | 客户端生成 | 用服务器公钥加密后发送 | 只有服务器能解密 |
三个随机数共同参与生成 Master Secret,再由 Master Secret 派生出最终的对称密钥。
核心问题:如果只用 Pre-Master Secret 一个随机数来生成密钥,会怎样?
假设只用一个随机数生成密钥,那么相同的随机数 ⇒ 相同的密钥。这就引出了重放攻击:
三个随机数的防御逻辑:
三个随机数生成了 Master Secret,而 Master Secret 又通过密钥派生函数(PRF)进一步生成四把密钥:
| 密钥 | 用途 |
|---|---|
| 客户端加密密钥 | 客户端用它加密数据 → 服务器用它解密 |
| 服务端加密密钥 | 服务器用它加密数据 → 客户端用它解密 |
| 客户端 MAC 密钥 | 客户端用它生成消息认证码(HMAC) |
| 服务端 MAC 密钥 | 服务器用它生成消息认证码(HMAC) |
如果客户端和服务器用同一把密钥加密,会存在一个很损的攻击方式——反射攻击(Reflection Attack):
双向不同密钥的防御逻辑:
MAC(Message Authentication Code,消息认证码)和 HMAC(Hash-based Message Authentication Code,基于哈希的消息认证码)是 TLS 完整性保护的核心机制。
| 作用 | 说明 | 对比数字签名 |
|---|---|---|
| 提取指纹 | 对消息内容生成一个"指纹"(哈希值),任何篡改都会导致指纹变化 | 相同,数字签名也能防篡改 |
| 验证拥有者身份 | 只有持有密钥的人才能生成正确的 HMAC。收到消息后验证 HMAC → 确认对方确实持有密钥 | 不同!数字签名可以公开验证(用公钥),HMAC 不能被公众验证(必须持有同一个密钥) |
TLS 的每条加密消息都附带一个 HMAC 校验值:
密文和 HMAC 标签一起发送。接收方先用加密密钥解密,再用 MAC 密钥验证 HMAC——双重保障:别人看不到(加密),别人改不了(HMAC)。
TLS 握手只在连接建立时做一次。之后的 HTTP 请求/响应都直接用对称密钥加密,TLS 层变成透明的加解密管道。如果开启了 TLS Session Resumption(会话恢复),后续连接甚至可以跳过完整握手,直接恢复之前的密钥。
| 保障 | 机制 | 防御什么 |
|---|---|---|
| 机密性 | 对称加密(AES / ChaCha20) | 窃听——别人看不到内容 |
| 完整性 | HMAC(消息认证码) | 篡改——别人改不了内容 |
| 身份认证 | 证书链 + 数字签名 | 冒充——确认服务器真的是它声称的那个 |
| 防重放 | 三个随机数(每次握手密钥不同) | 重放——旧的密文在新的会话中无效 |