TLS/SSL 协议演进:从 SSL 3.0 到 TLS 1.3

聊到密码套件迁移,绕不开的就是协议版本本身。我见过不少团队,迁移时只盯着密码套件列表,却忽略了底层协议版本的限制。结果呢?新套件配老协议,照样被扫描出漏洞。

今天咱们就把 SSL/TLS 的演进脉络理清楚。你想想看,从 1996 年的 SSL 3.0 到 2018 年的 TLS 1.3,这二十多年到底变了什么?

一句话总结:每一代协议升级,核心都在做两件事——砍掉不安全的密码原语,优化握手性能。说白了,就是「做减法」。

TLS/SSL 协议演进时间线 SSL 3.0 1996 · 已弃用 RC4 + MD5 TLS 1.0 1999 · 已弃用 CBC + HMAC TLS 1.1 2006 · 已弃用 CBC IV 显式 TLS 1.2 2008 · 广泛使用 AEAD + GCM TLS 1.3 2018 · 推荐 1-RTT / 0-RTT ⚠ 安全等级:SSL 3.0 / TLS 1.0 / TLS 1.1 已正式弃用,TLS 1.2 为当前基线,TLS 1.3 为推荐目标 核心差异对比 握手性能 • SSL 3.0 ~ TLS 1.2:2-RTT • TLS 1.3:1-RTT(首次) • TLS 1.3:0-RTT(恢复) • 减少一半延迟 密码套件 • 老版本:RC4 / CBC / 3DES • TLS 1.2:AEAD 可选 • TLS 1.3:仅 AEAD • 移除静态 RSA / DH 签名与密钥 • 老版本:RSA 密钥交换 • TLS 1.3:仅 PFS 套件 • 移除 DSA / 静态 DH • 强制前向安全

SSL 3.0:一切的开端,也是噩梦的开始

1996 年,Netscape 发布了 SSL 3.0。说实话,在那个年代它已经很先进了——支持证书认证、握手协商、对称加密。但放到今天看,它浑身都是窟窿。

我记得 2014 年 POODLE 攻击爆发时,很多老系统还在用 SSL 3.0。攻击者利用 CBC 模式的填充校验漏洞,逐字节爆出明文。那次我帮一个金融客户做紧急升级,通宵把几百台设备上的 SSL 3.0 全禁了。

⚠ 严重警告:SSL 3.0 已于 2015 年被 RFC 7568 正式标记为禁止使用。任何还在启用 SSL 3.0 的系统,都相当于把大门敞开。我曾经在渗透测试中,仅用 5 分钟就通过 POODLE 攻击拿到了 HTTPS Cookie。

TLS 1.0:换了个名字,但本质没变

1999 年,IETF 把 SSL 3.0 拿过来改了个名,叫 TLS 1.0。其实改动很小——把 HMAC 从 MD5 换成了 SHA-1,修正了一些细节。但核心结构没变,CBC 模式的问题也没解决。

你想想看,TLS 1.0 和 SSL 3.0 的差异,说白了就是「换了个马甲」。所以后来 BEAST 攻击(2011 年)能打穿 TLS 1.0,我一点都不意外。攻击者利用 CBC 的 IV 预测问题,硬是把 HTTPS 会话给解密了。

💡 我的经验:如果你还在维护 2010 年以前的嵌入式设备或老 Java 应用,大概率默认启用了 TLS 1.0。我建议你立刻检查 System.setProperty("https.protocols", "TLSv1.2") 这类配置,别让老协议拖后腿。

TLS 1.1:补丁式升级,治标不治本

2006 年,TLS 1.1 出来了。它主要修了两个问题:

  • CBC IV 显式化——每次加密都生成随机 IV,不再依赖上一个数据块的密文。这就堵住了 BEAST 攻击的路。
  • 填充校验规范化——统一了填充格式,减少侧信道风险。

但说实话,TLS 1.1 就是个「补丁版」。它没有引入新的密码原语,也没有优化握手。所以在实际部署中,TLS 1.1 的存在感一直很低。很多厂商直接从 TLS 1.0 跳到了 TLS 1.2。

现状:PCI DSS、NIST SP 800-52 等标准已经明确要求禁用 TLS 1.0 和 TLS 1.1。2020 年以后,主流浏览器和操作系统都默认关闭了这两个版本。

TLS 1.2:真正的分水岭

2008 年发布的 TLS 1.2,是迄今为止生命周期最长、部署最广的版本。我个人认为,它是 SSL/TLS 历史上最重要的一个版本。

为什么这么说?因为它做了三件大事:

  1. 砍掉硬编码哈希——之前的版本把 PRF、HMAC 算法写死在协议里。TLS 1.2 允许协商,你可以用 SHA-256 甚至 SHA-384。
  2. 引入 AEAD 支持——虽然 GCM 模式在 TLS 1.2 里是可选的,但它的出现彻底解决了 CBC 模式的填充预言攻击问题。
  3. 扩展密码套件空间——从 TLS 1.2 开始,密码套件数量暴增。我记得最多的时候 IANA 注册了 300 多个套件。

嗯,这里要注意。套件多了不一定是好事。我在项目中遇到过客户配置了 40 多个密码套件,结果 OpenSSL 握手时协商出一个弱套件。后来我帮他们精简到 6 个,反而更安全。

💡 避坑指南:我曾经在 TLS 1.2 迁移中踩过一个坑——某些老版本的 Java 8(u31 之前)不支持 GCM 套件。如果你用 ECDHE-RSA-AES128-GCM-SHA256,客户端会直接握手失败。解决方案是升级 JCE 或改用 ECDHE-RSA-AES128-SHA256 作为 fallback。

TLS 1.3:做减法,做到极致

2018 年,TLS 1.3 终于发布了。从 1.2 到 1.3,中间隔了整整 10 年。为什么这么久?因为工作组在「砍东西」上吵了很久。

最终结果呢?TLS 1.3 砍掉了几乎所有不安全的东西:

移除项 原因 替代方案
静态 RSA 密钥交换 不支持前向安全 ECDHE / DHE
静态 DH 密钥交换 不支持前向安全 ECDHE / DHE
CBC 模式加密 填充预言攻击 AEAD (GCM / ChaCha20-Poly1305)
RC4 流密码 偏倚攻击 AEAD
3DES 加密 64 位块大小,SWEET32 攻击 AEAD
DSA 签名 性能差,安全性弱 ECDSA / EdDSA
压缩 CRIME 攻击 无
重新协商 中间人攻击 无

你想想看,TLS 1.3 的密码套件从 300 多个砍到了 5 个。这 5 个套件全部强制 AEAD、强制前向安全、强制 PFS。说白了,就是「要么用安全的,要么别连」。

另外,TLS 1.3 的握手也做了大手术:

  • 1-RTT 握手——首次连接只需要一次往返。相比 TLS 1.2 的 2-RTT,延迟直接减半。
  • 0-RTT 恢复——如果之前连过,第二次可以直接发数据。我在 CDN 场景下测过,0-RTT 能把首屏加载时间缩短 30% 以上。
  • 密钥协商与证书验证并行——TLS 1.2 是先协商密钥再验证证书,TLS 1.3 把这两步合并了。

核心变化:TLS 1.3 的密码套件不再指定密钥交换算法和证书签名算法。握手时,密钥交换由 supported_groups 扩展决定,证书签名由 signature_algorithms 扩展决定。密码套件只负责「握手后的对称加密 + HMAC」。

版本兼容性:老协议不能与新套件混用

这里有个容易踩的坑——密码套件和协议版本是绑定的。你不能在 TLS 1.0 上用 TLS_AES_128_GCM_SHA256,因为 TLS 1.0 根本不认识这个套件。

我整理了一个对照表,方便你排查:

协议版本 支持的密码套件类型 典型套件示例 安全状态
SSL 3.0 仅老式套件(RC4 / CBC / 3DES) TLS_RSA_WITH_RC4_128_MD5 ❌ 禁止使用
TLS 1.0 老式套件 + CBC 套件 TLS_RSA_WITH_AES_128_CBC_SHA ❌ 禁止使用
TLS 1.1 老式套件 + CBC 套件 TLS_RSA_WITH_AES_128_CBC_SHA ❌ 禁止使用
TLS 1.2 CBC 套件 + AEAD 套件 ECDHE-RSA-AES128-GCM-SHA256 ✅ 推荐(仅 AEAD)
TLS 1.3 仅 AEAD 套件(5 个) TLS_AES_128_GCM_SHA256 ✅ 强烈推荐

⚠ 重要提醒:迁移时不要只升级协议版本,不更新密码套件。我见过一个案例——某团队把 Nginx 的 ssl_protocols 改成了 TLSv1.2,但 ssl_ciphers 还留着 RC4。结果呢?协议是 1.2,但协商出来的套件还是 RC4-SHA。安全扫描照样亮红灯。

我的迁移建议

如果你现在要做一个密码套件迁移项目,我的建议是:

  1. 先禁用老协议——SSL 3.0、TLS 1.0、TLS 1.1 全部关掉。别犹豫,这些版本已经没有任何存在的理由了。
  2. 以 TLS 1.2 为基线——如果你的客户端生态比较老(比如 Android 4.x、Windows 7、Java 7),TLS 1.2 是最稳妥的选择。
  3. 逐步推进 TLS 1.3——从 2023 年的数据看,全球 TLS 1.3 的部署率已经超过 60%。如果你的服务面向现代浏览器和移动端,直接上 TLS 1.3 没问题。
  4. 精简密码套件——TLS 1.2 环境下,我建议只保留 4-6 个套件。优先选 ECDHE + GCM 的组合。

💡 一个小技巧:你可以用 openssl s_client -tls1_2 -connect example.com:443 -cipher 'ECDHE' 来测试服务端支持的套件。如果返回 no shared cipher,说明你的套件列表需要调整。