主题
HTTPS 与密码学基础
一句话:TLS 用非对称手段解决「和陌生人安全地协商一把临时对称密钥」,再用对称加密搬运数据;证书链把「这把公钥属于谁」的信任从 CA 逐级传到站点,代价是链路更长、证书运维成为新的故障面。
适用版本:TLS 1.2(RFC 5246)、TLS 1.3(RFC 8446)、OpenSSL 3.x。算法参数与套件支持范围以官方文档为准。
为什么需要
明文 HTTP 同时暴露三件事:内容可被窃听、可被篡改、对端可被冒充。只靠对称加密解不了「第一把密钥怎么安全送到对方」;只靠非对称加密则太慢,无法承载大量数据;只靠哈希只能发现改动,既不能保密,也不能证明是谁发的。HTTPS 把三者组合起来,各自只做擅长的事。
核心机制
三类原语:各自解决什么,边界在哪
| 原语 | 解决 | 不解决 | 代价 | 典型用法 |
|---|---|---|---|---|
| 对称加密 | 大量数据的机密性 | 密钥怎么安全分发 | 密钥管理复杂 | AES-128/256-GCM、ChaCha20-Poly1305、SM4 |
| 非对称加密 | 密钥分发、身份绑定 | 大流量加密(慢几个数量级) | 密钥长、运算重 | ECDHE 密钥交换、RSA-OAEP、签名 |
| 哈希 | 完整性、唯一摘要 | 机密性、身份认证 | 有碰撞概率 | SHA-256/SHA-3、口令派生、签名前摘要 |
三条边界必须分清:
- 哈希不是加密。它没有密钥、不可逆,只保证「改过就能发现」。把口令直接做 SHA-256 仍然会被彩虹表和 GPU 爆破,正确做法是加盐并用慢速 KDF(Argon2id、scrypt、bcrypt)。
- 哈希也不等于认证。普通哈希谁都能重算,必须用带密钥的 HMAC,或用私钥做数字签名,才能让对端确认来源。
- 非对称不拿来加密业务数据。TLS 里它只做两件事:ECDHE 协商出会话密钥,证书/签名证明公钥归属;真正的大流量交给对称 AEAD。
对称加密选 AEAD(带关联数据的认证加密),因为它一次给出机密性和完整性。AES-GCM 的硬约束是 nonce 绝不能在同一密钥下重复,否则可恢复认证密钥;CBC 需要额外 MAC 且历史上反复出现 Padding Oracle,TLS 1.3 已移除 CBC。
TLS 握手:1.2 与 1.3 差在哪
TLS 1.2 完整握手是 2 个 RTT:ClientHello/ServerHello 与证书一个往返,ClientKeyExchange/ChangeCipherSpec/Finished 再一个往返;会话复用时才能降到 1 个 RTT。TLS 1.3 把密钥交换材料直接放进 ClientHello,完整握手 1 个 RTT 完成,恢复会话时可用 PSK 做 0-RTT。
| 维度 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 完整握手 | 2-RTT(恢复 1-RTT) | 1-RTT(恢复可 0-RTT) |
| 密钥交换 | RSA 或 ECDHE | 仅 (EC)DHE,前向安全强制 |
| 密钥派生 | PRF(Master Secret) | HKDF 密钥调度 |
| 证书消息 | 明文 | ServerHello 之后全部加密 |
| 密码套件 | 算法组合多,易配错 | 仅 5 个,认证/密钥交换与记录层算法分离 |
| 已移除 | — | RSA 密钥交换、CBC、RC4、3DES、重协商 |
TLS 1.3 的 1-RTT 来自「客户端先猜一组 ECDHE 参数并随 ClientHello 发出」;服务端选定后立即能算出共享密钥,因此证书和 Finished 可以同批加密返回。0-RTT 则是把恢复密钥和早期数据一起发,早期数据可被重放,只能用于幂等请求,且服务端要能拒绝。
两者都不保护「端点已经被攻破」的情况:TLS 只覆盖传输段,落盘、日志、内存转储仍要单独加密。
证书链:信任是怎么传递的
浏览器/操作系统内置受信 Root CA;Root 签发 Intermediate,Intermediate 签发站点叶子证书。客户端验证时做六件事:
- 用 Issuer 字段和随握手下发的 Intermediate 证书构建到受信 Root 的路径。
- 逐级用上一级公钥验证下一级签名。
- 检查每级有效期(
notBefore/notAfter)。 - 检查叶子证书的 SAN 是否匹配访问域名;CN 仅作兼容,现代校验以 SAN 为准。
- 检查 Intermediate 的
basicConstraints CA:TRUE、keyUsage与路径长度限制。 - 检查吊销状态(CRL/OCSP,生产优先 OCSP Stapling)与公开 CA 的 CT 日志(SCT)。
最常见的线上故障不是算法被攻破,而是 服务端没下发 Intermediate:桌面浏览器可能靠缓存补链,移动端或 Java 客户端直接报「unable to find valid certification path」。SNI 则是另一类:同一 IP 托管多个站点时,不带 -servername 会拿到默认站点的证书。
国密落地:替换算法不等于替换体系
| 国密 | 类型 | 对应国际算法 | 关键参数 |
|---|---|---|---|
| SM2 | 椭圆曲线公钥算法 | ECDSA/ECDH | 256-bit 密钥,签名与密钥交换 |
| SM3 | 密码杂凑 | SHA-256 | 输出 256-bit |
| SM4 | 分组密码 | AES | 128-bit 分组 + 128-bit 密钥(16 字节) |
| SM9 | 标识密码(IBE) | 无直接对应 | 以用户标识派生公钥 |
SM4 的密钥固定 16 字节,CBC 模式 IV 也是 16 字节;不存在「SM4 用 32 字节密钥」的用法。若业务要求 256-bit 密钥强度,应换 AES-256 或采用合规的组合方案,而不是给 SM4 塞 32 字节密钥。国密 TLS(TLCP,GB/T 38636-2020)与标准 TLS 是两套协议栈,不能把 SM4 直接填进 TLS 1.3 的套件列表;具体模式与参数以 GB/T 32907、GM/T 0002、GM/T 0081 等标准为准。
工程上常见的是双层密钥:KMS 生成 128-bit DEK 并用 SM2 加密传给网关,网关再用每个应用独有的 KEK(16 字节 SM4 密钥 + 16 字节 IV)二次加密,应用本地解出 DEK 后做业务加解密。网关全程不持有 DEK 明文,泄露面从「所有应用」收敛到「单个应用」。高安全场景还会把 SM2 私钥拆成两片,客户端与服务端各持一片做联合签名,单点被攻破也签不出完整签名。
最小上手
bash
# OpenSSL 3.x:验证证书链、主机名,并让校验失败直接返回非 0
openssl s_client -connect example.com:443 -servername example.com \
-showcerts -verify_hostname example.com -verify_return_error </dev/null
# 期望输出包含:Verify return code: 0 (ok)
# 只看叶子证书的关键字段
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# 强制 TLS 1.3,确认协议与套件
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null 2>&1 \
| grep -E "Protocol|Cipher"-servername 是 SNI,不带它测共享 IP 会得到错误证书;-verify_return_error 让校验失败变成非 0 退出码,适合放进巡检脚本。服务端至少应收口到:
nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;TLS 1.3 的套件由 ssl_conf_command Ciphersuites ... 单独配置,ssl_ciphers 只影响 1.2;具体指令以所用 Nginx/OpenSSL 版本为准。
生产动作
- 协议与套件:只开 TLS 1.2/1.3,禁用 RSA 密钥交换、CBC、RC4、3DES;用
nmap --script ssl-enum-ciphers或内部扫描确认没有回退。 - 证书生命周期:叶子证书剩余 30 天告警,续期失败单独告警;证书必须带完整 Intermediate,自动化部署后跑一次
openssl s_client验收。 - HSTS 顺序:先确认全站和子域都能 HTTPS,再开
includeSubDomains; preload;一旦 preload,回退 HTTP 的窗口以max-age为准,不能靠删响应头立刻恢复。 - 会话恢复与 0-RTT:TLS 1.3 的 ticket 要能轮换;0-RTT 只给幂等 GET,非幂等请求在服务端拒绝早期数据。
- 排障三段:握手失败 →
openssl s_client -showcerts→ 看是缺 Intermediate、证书过期、SNI/域名不匹配还是客户端信任库太旧;不要用「关闭证书校验」绕过。 - 内部链路:服务网格用 mTLS 时,确认 Sidecar 确实在拦截流量,而不是「装了 Istio 就默认加密」;证书轮换失败会表现为大面积 503。
边界
- TLS 不保护落盘数据、日志和被攻破的端点;它只保证「客户端到 TLS 终止点」这一段。
- 证书固定(pinning)能防错误 CA,但叶子证书轮换会让客户端集体失联;要固定就固定 Intermediate/公钥,并预留轮换窗口。
- 自签名证书只适合受控内部链路,且必须显式分发信任锚;不要把它当「免费的 HTTPS」。
- 国密替换是合规与生态选择,不代表同等参数下一定更强;客户端、网关、HSM 的支持矩阵要一起评估。
- 不要自己实现密码协议或「把明文异或一下」;用成熟库的 AEAD 接口,并确保 nonce 唯一。
讲清楚
HTTPS 解决的是「在不信任的网络上,和陌生人安全通信」。它先用证书确认对端身份,再用 ECDHE 协商一把本次会话专用的对称密钥,之后的数据全部用 AES-GCM 这类 AEAD 加密。非对称只负责身份和密钥分发,对称负责吞吐,哈希负责完整性和签名摘要,三者边界不能混。TLS 1.3 把密钥交换材料提前到 ClientHello,完整握手从 1.2 的 2 个 RTT 降到 1 个,强制前向安全并加密证书;代价是 0-RTT 有重放风险,只能用于幂等请求。生产上最常见的故障不是算法被破,而是证书链缺 Intermediate、SNI 不匹配或证书过期,所以巡检要直接跑
openssl s_client看验证结果,而不是只看进程活着。