2. 密码学基础回顾:TLS 的三大基石

搞 TLS 1.3 之前,我们得先把密码学的基本功练扎实。说白了,整个 TLS 握手就是一场精心设计的「密码组合拳」——对称加密、非对称加密、哈希函数各司其职。这一章我带你快速过一遍它们在 TLS 里的真实角色,顺便讲讲我在生产环境里踩过的坑。

2.1 对称加密:AES 为什么是 TLS 的默认主力

AES(Advanced Encryption Standard)是目前最广泛使用的对称加密算法。它的核心特点是:加密和解密使用同一个密钥。速度快、效率高,适合加密大量数据——TLS 握手完成后,真正传输的 HTTP 内容就是用 AES 保护的。

我记得 2018 年给一个金融客户调优 HTTPS 性能时,发现他们的老旧服务器只支持 AES-CBC,结果每次握手都要多耗几个毫秒。后来我建议升级到 AES-GCM,吞吐量直接翻了一倍。为什么?因为 GCM 模式自带认证,不需要额外的 HMAC 步骤。

AES 在 TLS 1.3 中的角色:
  • 握手完成后,用协商出的对称密钥加密所有应用数据(HTTP、HTTP/2 帧等)
  • TLS 1.3 强制要求使用 AEAD 模式(Authenticated Encryption with Associated Data),最常用的是 AES-128-GCM 和 AES-256-GCM
  • GCM 同时提供机密性和完整性,一次计算搞定两件事

几种 AES 模式对比

模式 是否认证 TLS 1.3 支持 我的建议
AES-CBC 否(需额外 HMAC) 不支持 别用了,Padding Oracle 攻击风险高
AES-GCM 是(内置认证) 是 首选,性能高、安全
AES-CCM 是(内置认证) 支持(较少见) GCM 不够时再用,效率略低
工程避坑: 对称密钥怎么来的?不是写死在代码里的。TLS 1.3 通过 ECDHE 密钥交换临时生成,用完即毁。我曾经见过有人把 AES 密钥硬编码在配置文件中……嗯,后果你懂的。

2.2 非对称加密:RSA 与 ECC 的分工

非对称加密解决了一个根本问题:两个从未见过面的人,怎么安全地商量出一个共同的对称密钥? 答案是用公钥加密、私钥解密。但 RSA 和 ECC 在 TLS 里的戏份完全不同——尤其在 TLS 1.3 里。

RSA:曾经的王者

RSA 依赖大整数分解的数学难题。在 TLS 1.2 及之前,它既用来做密钥交换(Client 用 Server 的公钥加密 Pre-Master Secret),也用来做数字签名。但 TLS 1.3 做了一个重大改变:不再使用 RSA 做密钥交换,只保留它的签名功能。

为什么?因为 RSA 密钥交换是「静态」的——如果私钥泄露,之前所有通信记录都能被解密(前向安全性缺失)。而 ECDHE 每次握手都生成临时密钥对,私钥用完就丢掉,彻底解决了这个问题。

⚠️ 注意: 证书中的 RSA 公钥在 TLS 1.3 中只用于验证签名。你配置的 RSA 证书依然可以用,但密钥交换已经交给 ECDHE 了。别以为证书是 RSA 的,服务就会用 RSA 交换密钥——不会了。

ECC:TLS 1.3 的黄金搭档

椭圆曲线密码(Elliptic Curve Cryptography, ECC)在同等安全强度下,密钥更短、计算更快。TLS 1.3 官方推荐的密钥交换就是基于 Curve25519 或 P-256 的 ECDHE。签名则可以用 ECDSA 或 Ed25519。

我在一个 IoT 项目里尝到了甜头——设备端 CPU 很弱,RSA 2048 做签名验证要几十毫秒,换成 ECDSA 之后变成了 2 毫秒。关键是功耗也跟着降下来了。你想想看,对于移动端或嵌入式设备,ECC 几乎是最优解。

RSA vs ECC 签名对比

特性 RSA-2048 ECDSA P-256 Ed25519
公钥大小 256 字节 32 字节 32 字节
签名大小 256 字节 64 字节 64 字节
验证速度 慢 快 非常快
TLS 1.3 推荐度 兼容(不推荐新业务) 推荐 推荐(RFC 8446 可选)

2.3 哈希函数:SHA-256 的守护任务

哈希函数是密码学里的「数字指纹」。它把任意长度的数据变成固定长度的摘要,而且不可逆。在 TLS 1.3 里,SHA-256 几乎无处不在:证书签名验证、密钥派生、握手消息完整性保护,每一步都离不开它。

具体来说,SHA-256 在 TLS 中有这三项核心职责:

  1. 签名验证: 验证证书链时,CA 的签名就是先对证书内容做 SHA-256 哈希,再用私钥加密这个哈希值。客户端用公钥解密后比对哈希值,就知道证书有没有被篡改。
  2. 密钥派生: TLS 1.3 使用 HKDF(基于 HMAC 的密钥派生函数),而 HMAC 底层就是 SHA-256。每次握手会跑很多次 SHA-256,把共享秘密「拉伸」成加密密钥、认证密钥、初始化向量等。
  3. Finished 消息: 双方在握手最后发送 Finished 消息,里面包含了之前所有握手消息的 SHA-256 哈希。一旦中途被篡改,哈希值对不上,握手立刻失败。这防止了中间人攻击。
我自己踩过的坑: 有一次调试自定义 TLS 实现,发现 Finished 消息总是不匹配。查了两天才发现——我计算哈希时把 ClientHello 的顺序搞反了(TLS 规范要求按消息到达顺序)。SHA-256 本身没毛病,但输入顺序错了,一切白费。教训:哈希函数的输入顺序和格式必须严格遵守协议规定。

2.4 三种密码原语在 TLS 1.3 中的协作流程

说了这么多,它们到底怎么一起工作的?我画了一张图,把核心逻辑理清楚。

TLS 1.3 密码学原语协作流程 对称加密 AES-128/256-GCM 非对称加密 ECDHE + 签名 哈希函数 SHA-256 对应用数据加密 AES-GCM 保护 HTTP 密钥协商 + 身份认证 ECDHE + 证书签名 完整性 & 密钥派生 HKDF + Finished 安全目标 机密性(AES) + 身份认证(签名) + 完整性(SHA-256) 三者缺一不可:AES 保证数据只有通信双方能看 签名验证对方身份,SHA-256 确保没人篡改任何握手消息

2.5 综合实战要点

讲完理论,我直接给你几个在工程里用得上的 Checklist:

  • 配置 TLS 1.3 加密套件时: 只保留 TLS_AES_128_GCM_SHA256 和 TLS_AES_256_GCM_SHA384 就够了。别再把那些过时的 ECDHE-RSA-AES128-SHA 加进去——TLS 1.3 根本不认。
  • 证书选择: 新业务直接上 ECDSA P-256 或 Ed25519。如果兼容老旧客户端(比如 Windows XP/Android 4.x),必须保留 RSA 证书,但仅做签名用途。
  • 哈希强度: 当前最少用 SHA-256。SHA-1 已经彻底被禁止在 TLS 1.3 中使用。我曾在一个遗留系统里看到 SHA-1 证书链……浏览器直接报了 ERR_CERT_WEAK_SIGNATURE_ALGORITHM,用户无法访问。
  • 随机数质量: 密钥交换的安全性强烈依赖随机数生成器。我在生产环境遇到过因为容器 /dev/urandom 熵不足导致 ECDHE 生成慢的问题。解决办法是挂载 haveged 或使用专用硬件熵源。
一个小测试: 用 OpenSSL 查看你当前服务的密码套件:
openssl s_client -connect yourdomain.com:443 -tls1_3 -ciphersuites TLS_AES_128_GCM_SHA256
如果返回握手成功,说明你的服务器已经用上了今天讲的这套密码组合。