3. 密码套件(Cipher Suite)选择与配置归纳总结

密码套件,说白了就是 TLS 握手的“配方”。它决定了双方怎么交换密钥、怎么验证身份、怎么加密数据、怎么保证完整性。我见过不少团队,只管把 OpenSSL 默认套件一放,结果一半流量还在用 RC4 或 3DES——嗯,这其实挺危险的。

这一章我帮你把 TLS 1.2 常见密码套件捋一遍,按安全等级分组,再给出现代推荐配置。看完你就能拿回去直接改生产环境了。

3.1 密码套件的结构

一个密码套件长这样:

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
└── 协议    密钥交换  认证  加密算法    加密模式   HMAC

每个字段含义如下:

字段常见取值安全说明
密钥交换ECDHE, DHE, ECDH, RSA前向安全性:ECDHE > DHE,RSA 无前向安全
认证RSA, ECDSA, DSAECDSA 性能优于 RSA,推荐两者混合部署
加密算法AES-128, AES-256, CHACHA20, 3DES3DES 已废弃,CHACHA20 适合移动端
加密模式GCM, CBC, CCMGCM 提供认证加密,CBC 有 padding oracle 风险
HMACSHA384, SHA256, SHA1SHA-1 已不推荐,SHA-2 系列是底线
核心结论:优先使用 ECDHE + ECDSA(或 RSA)+ AES-256-GCM + SHA384 的组合。退而求其次是 ECDHE + RSA + AES-128-GCM + SHA256。

3.2 TLS 1.2 常用套件安全分级

我个人习惯把套件分成三个梯队——安全、可用、已死。碰到已死的那一批,直接下线。

  • ✅ 安全梯队(可保留 TLS 1.2 且推荐优先使用)
    • TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    • TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    • TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256(移动优先)
  • ⚠️ 可用梯队(过渡期允许,建议逐步移除)
    • TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
    • TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
    • TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384(仅当客户端不支持 GCM 时)
  • ❌ 已死梯队(必须立即下线)
    • TLS_RSA_WITH_AES_256_CBC_SHA(无前向安全)
    • TLS_RSA_WITH_3DES_EDE_CBC_SHA(3DES 已破)
    • TLS_ECDHE_RSA_WITH_RC4_128_SHA(RC4 被攻击)
    • TLS_RSA_WITH_RC4_128_MD5(MD5 + RC4 双废)
特别注意:即便你只开启 ECDHE + AES-GCM,如果服务器支持 RSA 密钥交换(套件名不含 DHE/ECDHE),中间人攻击者仍可能降级。建议在 Nginx 或 Apache 配置中明确只允许 ECDHE 和 DHE 类套件。

3.3 配置实战:Nginx + OpenSSL

下面是一个生产环境用的 Nginx 密码套件配置。我自己的服务器用了快两年,没出过兼容性问题。

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;

解释几点:

  • ssl_prefer_server_ciphers on:强制服务端优先选择,防止客户端用弱套件。
  • 把 CHACHA20 放在 GCM 后面——因为桌面端硬件加速 AES-GCM 更快,移动端才用 CHACHA20。
  • 没有加任何 CBC 模式套件。原因?我在 2019 年处理过一个 case:客户使用老旧 Java 客户端,只能连 CBC。我们不得不单独为那台服务器保留 CBC,其他服务器全禁用。后来统一升级了客户端才解决。

3.4 旧客户端兼容性处理

有些场景——比如银行柜面终端、工控机——还在用 Windows XP 或 Java 6。这些客户端不支持 ECDHE,甚至不支持 TLS 1.2。怎么办?

  • 方案 A:双端口。一个端口(443)只开 ECDHE + GCM 强套件,另一个端口(类似 8443)额外开 DHE + CBC 弱套件。用 IP 白名单控制弱端口访问。
  • 方案 B:应用层协议协商。客户端先通过 HTTP 请求头声明能力,服务端动态选择套件。但实现复杂,我一般不用。
  • 方案 C:升级客户端。最彻底的方法。我曾帮一家物流企业写过一个工具,检测所有内部系统依赖的 TLS 套件,强制要求升级到至少 OpenSSL 1.0.2。半年后全线切换完成。
小技巧:用 `openssl ciphers -v 'ECDHE+AES-GCM:!aNULL:!eNULL:!LOW'` 命令可以看实际生效列表。我每次改配置都会跑一遍这个命令,核对没有“已死”套件漏进去。

3.5 密码套件知识体系图

下面我把密码套件的选择逻辑总结成一张图。你跟着这个流程走,基本不会选错。

密码套件选择与配置流程 密码套件选择流程 开始:评估客户端环境 支持 ECDHE? (包含客户端证书) 是 ECDHE + GCM 套件 优先 CHACHA20(移动端) 否 DHE + GCM(兼容旧硬件) 评估 CBC 降级风险 最终配置清单 优先 ECDHE + GCM + SHA384 部署并验证 ⚠ 过渡期 DHE 可用但性能差 ✅ 推荐组合 ECDHE + AES-256-GCM

3.6 验证配置是否正确

配置完了别着急上线。先跑这几个检查。我每次都会做一遍:

  1. 命令行测试:openssl s_client -connect yoursite.com:443 -cipher ECDHE 2>/dev/null | grep "Cipher is",看返回的套件是否符合预期。
  2. 弱套件准入测试:用 openssl s_client -cipher 'RC4' 尝试连接,应该返回 handshake failure。
  3. 第三方扫描:用 sslscan 或 testssl.sh 扫一遍。我常用 testssl.sh 的 --cipher-per-proto 参数检查每个协议版本下的套件列表。
  4. 兼容性压测:在预发环境模拟真实用户流量,观察有多少连接因套件不匹配而失败。正常情况下失败率应低于 0.1%。
我曾经踩过的坑:有一次我配置了 ECDHE 优先,但忘了把非 ECDHE 套件全部删除。结果老客户端仍然协商到了 RSA 密钥交换。后来我强制在配置里加了 ssl_ciphers '...:!kRSA'; 才彻底堵住。

3.7 总结一张对照表

最后给一张快速对照表,方便你日常查阅:

应用场景推荐主套件备选套件备注
通用 Web 服务器(桌面+移动)ECDHE-RSA-AES256-GCM-SHA384ECDHE-ECDSA-AES128-GCM-SHA256OpenSSL 1.1.1+ 支持
移动 App 后端ECDHE-ECDSA-CHACHA20-POLY1305ECDHE-RSA-CHACHA20-POLY1305CHACHA20 省电
嵌入式/低功耗设备ECDHE-RSA-AES128-GCM-SHA256DHE-RSA-AES128-GCM-SHA256减少计算量
兼容老客户端(Java 6 / XP)DHE-RSA-AES128-GCM-SHA256DHE-RSA-AES256-CBC-SHA384仅限隔离端口
完全下线 TLS 1.2(无)仅保留 TLS 1.3 套件需确认客户端都支持 TLS 1.3