把 HTTPS 理解成「HTTP 外面套一层加密」,会漏掉握手真正在解决的两个问题:你怎么知道对面是不是它声称的那个服务器,以及双方怎么在一条所有人都能看到的线路上商量出一把只有彼此知道的密钥。加密只是这两件事做完之后的结果。
一、TLS 1.3 架构:1-RTT 与状态机重构
TLS 1.3 通过激进地废弃过时的密码学原语(如 RSA 密钥传输、SHA-1)并强制启用前向安全(Forward Secrecy),将握手延迟从 2-RTT 大幅降低至 1-RTT。在 OpenSSL 的 C 语言实现中,这一切由 statem_clnt.c 和 statem_srvr.c 中的状态机驱动。客户端发送包含预计算 key_share 扩展的 ClientHello。服务器立刻状态转移至 TLS_ST_SW_SRVR_HELLO,计算共享密钥,并在单个 flight 中回传 ServerHello、EncryptedExtensions、Certificate、CertificateVerify 和 Finished 消息。

二、高阶状态机与 HKDF 可视化
下面的时序图展开了 1-RTT 的详细流程,重点标出了基于 HMAC 的密钥派生函数(HKDF)提取(Extract)和展开(Expand)握手与应用流量密钥的具体阶段。
sequenceDiagram
autonumber
participant Client as 客户端
participant Server as 服务端
Client->>Server: ClientHello + Key Share (X25519) + ALPN
Note right of Server: statem_srvr.c: tls_process_client_hello()
HKDF-Extract(0, ECDHE Shared Secret)
HKDF-Expand(Handshake Secret)
Server->>Client: ServerHello + Key Share
Server->>Client: {EncryptedExtensions + Certificate + CertVerify + Finished}
Note left of Client: statem_clnt.c: tls_process_server_hello()
派生密钥,验证签名,校验 Finished MAC
Client->>Server: {Finished} + [Application Data (HTTP Request)]
Note right of Server: HKDF-Expand(Master Secret) -> 应用层流量密钥
Server->>Client: [Application Data (HTTP Response)]
三、数学严密性:ECDLP 与 AES-GCM 多项式运算
现代 TLS 1.3 生产部署严重依赖 Curve25519 进行密钥交换,以及 AES-GCM 进行认证加密(AEAD)。
椭圆曲线离散对数问题 (ECDLP)
密钥交换使用了 Curve25519 蒙哥马利曲线,该曲线定义在素数域 \(\mathbb{F}_p\) 上的方程为:
\[ v^2 = u^3 + 486662u^2 + u \pmod{2^{255} – 19} \]
其安全性依赖于 ECDLP 的难解性:已知基点 \( G \) 和公钥 \( P = dG \),在计算上无法反推私有标量 \( d \)。共享密钥的计算公式为 \( S = d_{client} P_{server} = d_{client} d_{server} G = d_{server} P_{client} \)。OpenSSL 在底层使用了高度优化的、恒定时间(constant-time)的蒙哥马利阶梯算法(Montgomery ladders)来实现,从而杜绝了时间侧信道攻击。
伽罗瓦/计数器模式 (GCM) 的数学原理
密钥协商完毕后,AES-GCM 负责加密应用层记录。GCM 的认证标签(Authentication tag)是基于伽罗瓦域 \( \text{GF}(2^{128}) \) 上的通用哈希函数 GHASH 计算出来的。该域由以下不可约多项式定义:
\[ P(x) = x^{128} + x^7 + x^2 + x + 1 \]
域中的元素是 128 位的块。GHASH 函数求解的是一个以密文块为系数、以哈希子密钥 \( H \) 为变量的多项式。在生产环境中,这种多项式乘法会被 CPU 硬件指令极大地加速,例如 Intel 的 AES-NI 指令集(特别是用于无进位乘法的 vpclmulqdq 指令)。
四、生产工程:OpenSSL 与硬件加速
在高吞吐量的网关架构中(如每秒终结 10 万个 TLS 连接),CPU 开销是核心瓶颈。现代边缘负载均衡器完全绕过通用的 C 语言实现,直接调用汇编指令。例如,在 OpenSSL 的 evp_cipher API 中,AES-GCM 被直接路由至 AES-NI 向量计算单元。工程师在为 Envoy 或 Nginx 进行调优时,必须确保宿主机操作系统的 CPU 标志位(flags)将 aes, pclmulqdq, 以及 avx512f 暴露给用户态进程。
此外,OpenSSL 中用于 ECDSA/RSA 证书签名的 EVP_DigestSign 函数经常通过 engine 模块被卸载到异步密码学硬件加速引擎(如 Intel QAT)上。这使得 Nginx 的事件循环在硬件计算 ECDLP 标量乘法的过程中,仍能继续处理其他的 epoll 事件。
五、高阶诊断工具:eBPF 流量拦截与追踪
使用 tcpdump 来排查生产环境下的 TLS 问题(如握手延迟毛刺或加密套件协商失败)毫无意义,因为载荷已被加密。相反,顶尖 SRE 会部署 eBPF(扩展的伯克利数据包过滤器)在用户态动态追踪 OpenSSL。
下面是一段 BCC (BPF Compiler Collection) 代码片段,它通过挂载 uprobe 到 OpenSSL 的 SSL_do_handshake 函数,在内核态精准测量握手延迟分布:
#include <uapi/linux/ptrace.h>
BPF_HASH(start, u32);
BPF_HISTOGRAM(dist);
int probe_ssl_handshake_start(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid();
u64 ts = bpf_ktime_get_ns();
start.update(&pid, &ts);
return 0;
}
int probe_ssl_handshake_return(struct pt_regs *ctx) {
u32 pid = bpf_get_current_pid_tgid();
u64 *tsp = start.lookup(&pid);
if (tsp != 0) {
u64 delta = bpf_ktime_get_ns() - *tsp;
dist.increment(bpf_log2l(delta / 1000000)); // 以毫秒为单位的 Log2 直方图
start.delete(&pid);
}
return 0;
}
通过将此 eBPF 程序注入 libssl.so 的代码段,SRE 可以直观地监控 TLS 握手的 P99 延迟百分位图,而且完全无需修改应用代码或承受 tcpdump 抓包带来的上下文切换开销。
六、安全复盘:侧信道攻击防御
生产环境必须加固以抵御各种侧信道漏洞,例如 Bleichenbacher 攻击、Lucky Thirteen 或缓存时间攻击(如 Flush+Reload)。TLS 1.3 移除了 RSA 加密(PKCS#1 v1.5 填充)和 MAC-then-Encrypt 的 CBC 模式,从根本上消除了许多此类攻击。为了防御剩余的标量乘法时间泄漏,底层库采用了蒙哥马利阶梯算法,并在代码层面严格保证分支指令和内存访问模式与密钥的标量比特完全无关(恒定时间执行)。
七、TLS 握手排障证据矩阵
TLS 1.3 的问题通常表现为“HTTPS 慢”“证书错误”或“偶发握手失败”,但这些症状可能来自 DNS、TCP、证书链、密钥交换、ALPN、硬件加速或应用层重试。下面的矩阵把每类结论绑定到可观察证据,避免把加密层问题和网络层问题混在一起。
| 现象 | 需要收集的证据 | 优先判断 | 安全边界 |
|---|---|---|---|
| 首次 HTTPS 请求慢 | TLS 版本、握手 RTT、会话恢复状态、证书链大小。 | 是否失去 session resumption,或证书链过长。 | 不要为了省 RTT 降级到过时协议。 |
| 证书校验失败 | SNI、SAN、证书有效期、中间证书、OCSP/CRL 状态。 | 证书链是否完整,域名是否与证书匹配。 | 不要在客户端关闭证书验证来“修复”。 |
| 握手 P99 抖动 | eBPF/uProbe 握手直方图、CPU flags、OpenSSL 版本。 | 是否缺少 AES-NI/QAT,或事件循环被阻塞。 | 不要把加密计算瓶颈误判为带宽不足。 |
| 0-RTT 行为异常 | early data 开关、幂等性检查、重放保护和服务端日志。 | 请求是否适合 early data,是否触发重放风险控制。 | 非幂等写操作不应接受 0-RTT。 |
FAQ
证书会用来加密应用层数据吗?
不会。X.509 证书的数学作用是通过数字签名(例如基于 P-256 的 ECDSA)对握手 transcript 杂凑值进行签名,以验证服务器身份并证明对公钥的所有权。应用层数据的加密专属由 ephemeral ECDHE 交换并通过 HKDF 派生出的对称 AEAD 密钥负责,从而保障了前向安全性。
为什么不要从零开始自己写一个 TLS?
生产级别的 TLS 要求极其苛刻的恒定时间(constant-time)算术运算以防止微架构级别的侧信道数据泄漏。在高级语言中“造密码学轮子”经常会引入缓存时间漏洞、分支预测泄漏以及内存安全缺陷。请务必依赖身经百战的、支持硬件加速的开源库,如 OpenSSL、BoringSSL 或 Rustls。