第三章:密码套件(Cipher Suite)组成与选择
握手失败,十次有八次是密码套件没谈拢。这不是夸张,我调过的HTTPS问题里,服务端和客户端各说各话是最常见的病因。密码套件到底是什么?说白了,它就是一把组合锁——锁定了用什么算法来交换密钥、怎么认证身份、怎么加密数据、怎么保证完整性。
这章我把密码套件的里里外外拆开给你看。读完你就能看懂WireShark里那串乱码似的套件名称,也能在服务端配置文件里做出合理选择。
3.1 密码套件的四件套
一个密码套件由四个核心组件拼成。按TLS 1.2的标准格式,它长这样:
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
│ │ │ │ │
│ │ │ │ └─ 散列算法(HMAC,用于完整性校验)
│ │ │ └─────────── 对称加密算法 + 模式
│ │ └─────────────────── 认证算法(证书签名)
│ └────────────────────── 密钥交换算法
└───────────────────────────── 协议前缀(TLS)
每个组件都有它的使命,少一个都不行。我们来逐个拆解。
3.1.1 密钥交换算法
这是TLS握手的第一个坎。客户端和服务端需要在不安全的信道上协商出一个共享密钥。常见的就这么几类:
| 算法 | 类型 | 特点 | 常见问题 |
|---|---|---|---|
| RSA | 静态 | 客户端用服务端公钥加密随机数 | 没有前向安全性(FS) |
| ECDHE | 临时(Ephemeral) | 临时椭圆曲线DH,每次握手生成新密钥 | 曲线选择不当会慢(比如P-521) |
| DHE | 临时 | 基于整数模的DH,兼容性好 | 性能开销大(1024位以上) |
| PSK | 预共享密钥 | 适合IoT或内部网络 | 密钥分发麻烦 |
我个人习惯:公网服务优先选ECDHE,前向安全性加上性能优势。DHE虽然也前向安全,但慢得能让你在压测时抓狂。几年前我在一个金融项目里,服务端配了DHE+2048位素数,结果吞吐量比预期掉了40%。后来换成ECDHE+X25519,问题直接解决。
3.1.2 认证算法
认证就是让客户端确认服务端的身份(或者反过来)。大部分情况下用的是RSA或ECDSA签名:
- RSA签名:最普及,只要有RSA证书就能用。签名过程是服务端用私钥加密握手摘要。
- ECDSA签名:椭圆曲线签名,签名短、性能好。但证书必须用ECC密钥对。
避坑指南:我曾经遇到客户从CA申请了ECC证书,但服务器只配置了RSA套件,结果握手直接断在CertificateVerify阶段。记住——证书类型和签名算法必须匹配。RSA证书配ECDSA套件,或者反过来,都会握手失败。
3.1.3 对称加密算法
握手完成后,应用数据就用对称加密来保护。常用选项:
| 算法 | 密钥长度 | 模式 | 备注 |
|---|---|---|---|
| AES-GCM | 128/256 | GCM(Galois/Counter) | AEAD加密,同时提供认证,推荐 |
| ChaCha20-Poly1305 | 256 | 流加密+Poly1305认证 | 移动端性能好,无硬件加速时比AES快 |
| AES-CBC | 128/256 | CBC(密码分组链接) | 已不推荐,有padding oracle攻击风险 |
| 3DES/RC4 | — | — | 已废弃,很多新TLS库直接拒掉 |
选型建议:现代TLS 1.2推荐AES-256-GCM或ChaCha20-Poly1305。AES-128-GCM也安全,但有些合规要求硬性规定256位。CBC模式我不建议使用,除非你要兼容十年前的客户端。嗯,这里要注意——有些老旧银行系统还在用CBC,我在做金融网关对接时没少为这个扯皮。
3.1.4 散列算法(HMAC)
用来保证数据没有被篡改。在TLS 1.2里,它同时用于PRF(伪随机函数)和Finished消息校验。常见选项:
- SHA-384:目前最均衡的选择,安全余量足。
- SHA-256:性能好,兼容性广,TLS 1.3默认使用。
- SHA-1:已不推荐,碰撞攻击早已实用化。
- MD5:早就别用了。
我记得有一次调试一个老服务器,它只支持SHA-1套件,客户端强制要求SHA-256以上,结果双方妥协失败。最后只能升级服务端OpenSSL版本才解决。
3.2 密码套件的选择策略
怎么选?不是越多越好,也不是越新越好。我给你两套策略,你自己按场景对号入座。
3.2.1 策略A:高安全性优先
适合金融、政务、医疗等对安全等级有硬要求的场景。
- 只允许TLS 1.2及以上。
- 禁用所有非AEAD套件(CBC、RC4、3DES)。
- 优先ECDHE密钥交换,曲线用X25519或P-256。
- 对称加密只用AES-256-GCM或ChaCha20-Poly1305。
- 散列用SHA-384或SHA-256。
实际配置(Nginx为例):
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers on;
3.2.2 策略B:兼容性优先
适合面向广泛用户的通用服务,需要兼顾老旧客户端(如Windows XP/Android 4.x)。
- 支持TLS 1.0、1.1、1.2(但1.0和1.1已废弃,建议评估实际用户比例)。
- 保留少量AEAD+CBC组合,比如AES-128-CBC-SHA。
- 密钥交换保留RSA(但会失去前向安全性)。
- 把最安全的套件放前面,客户端通常选第一个。
3.3 密码套件协商的完整流程
来看一张我亲手画的结构图,把本章知识串起来:
这张图里,密码套件扮演了三个角色:在ClientHello里列出候选;在ServerHello里被选中;在后续的密钥交换和认证阶段,决定实际运行的算法。任何一个环节的算法不匹配,握手都会失败。
3.4 常见密码套件故障排查
光说不练假把式。最后我列几个实际踩过的坑:
| 现象 | 根因 | 解决办法 |
|---|---|---|
| 握手卡在ClientHello,无响应 | 服务端没有与客户端列表交叉的套件 | 查看客户端支持的套件,在服务端补全(如启用DHE或CBC兼容项) |
| 握手成功但一会儿断开 | 协商的套件使用了服务端不支持的模式(如GCM但硬件加速未开启) | 检查OpenSSL版本,升级或更换为ChaCha20 |
| 老旧iOS客户端无法连接 | 服务端只配了ECDSA套件,但客户端只支持RSA | 同时保留ECDSA和RSA套件,或要求客户端更新 |
| 安全扫描报CBC套件漏洞 | 配置中仍然保留了AES-CBC系列 | 移除所有CBC套件,只保留AEAD |
openssl s_client -connect host:port -cipher 'ECDHE' 可以测试特定套件族是否可用。如果连接失败,通常服务端没开启对应算法。我曾经靠这个一行命令,十分钟定位了一个跨国CDN配置不一致的问题。
密码套件说深不深,说浅不浅。你把四件套分清楚,把选择策略想明白,80%的握手故障都能自己搞定。剩下的20%,基本上就是证书链和协议版本的问题了。