主题
HTTP 与 HTTPS:TLS 1.3、证书链与排障
一句话:HTTPS 就是 HTTP 跑在 TLS 上,用非对称密钥交换协商会话密钥、用对称加密传数据;它换来机密性、完整性和服务器身份,代价是握手延迟、证书运维,以及中间设备对流量可见性的下降。
适用:TLS 1.2 / 1.3(RFC 5246 / RFC 8446),OpenSSL 1.1.1+(支持 TLS 1.3),curl 7.52+ 配合 OpenSSL 1.1.1 支持 TLS 1.3。端口 80/443 只是约定,不是协议强制。
为什么需要
HTTP 明文传输,同一路径上的代理、NAT、Wi-Fi 和镜像都能读、能改,客户端也无法确认对面是不是目标服务器。HTTPS 要解决三件事:
- 机密性:链路上看不到明文。
- 完整性:内容被改动会被发现。
- 身份认证:客户端能确认自己连的是证书对应的服务器,而不是中间人。
为什么不能只用一种加密:非对称加密慢,适合协商密钥,不适合传大流量;对称加密快,但双方得先有同一把密钥,而「怎么把密钥安全送到对方」本身就需要非对称或密钥交换。所以 HTTPS 采用混合加密:握手阶段用非对称/密钥交换,数据传输用对称加密。
核心机制
一次 HTTPS 连接
先完成 TCP 三次握手,再做 TLS 握手,之后才发送 HTTP 报文。TLS 握手做四件事:协商版本和套件、验证服务器身份、交换密钥材料、生成会话密钥。
TLS 1.2 与 1.3 的差别
| 维度 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 完整握手往返 | 2-RTT | 1-RTT |
| 密钥交换 | RSA 或 (EC)DHE | 证书握手只允许 (EC)DHE(恢复可用 PSK) |
| 对称算法 | CBC、GCM、ChaCha20-Poly1305 等 | 只允许 AEAD(GCM、ChaCha20-Poly1305) |
| 前向保密 | ECDHE 才有,RSA 密钥交换没有 | 证书握手强制具备 |
| 握手加密范围 | ServerHello、Certificate 等明文 | ServerHello 之后全部加密 |
| 旧算法 | 仍可协商 RC4、3DES、SHA-1 等 | 已移除 |
| 会话恢复 | Session ID / Session Ticket | PSK + 0-RTT(有重放风险) |
TLS 1.3 的 1-RTT 流程:
ClientHello (supported_versions, key_share, SNI, ALPN)
-> ServerHello (key_share)
<- {EncryptedExtensions, Certificate, CertificateVerify, Finished}
-> {Finished}
-> 应用数据{} 表示已加密。TLS 1.2 的 Certificate 在明文里,TLS 1.3 把它挪进加密段,中间设备能看到的更少。0-RTT 允许恢复连接时在第一个包里带应用数据,但早期数据可能被重放,只应对幂等请求开启。
证书链(certificate chain)验证
服务器通常发送「叶子证书 + 中间 CA 证书」,不发送根证书;根证书预置在客户端信任库。客户端从叶子往上验证签名,直到某个受信任的根,同时检查:
- 签名链完整、每张证书都在有效期内;
- 证书的 SAN(Subject Alternative Name)包含访问的域名,CN 已不再被依赖;
- 扩展密钥用法(EKU)包含
serverAuth; - 吊销状态(CRL / OCSP / OCSP stapling)。
服务器在同一个 IP 上托管多个域名时,靠 ClientHello 里的 SNI 选择证书。TLS 1.2 中 SNI 明文;TLS 1.3 默认也明文,ECH(Encrypted Client Hello)能加密但部署仍有限,所以「用了 HTTPS 就看不到域名」并不成立。
前向保密
ECDHE 每次握手生成临时密钥,服务器私钥泄露也无法解密过去的会话,这叫前向保密(forward secrecy)。RSA 密钥交换把会话密钥用服务器公钥加密,私钥一旦泄露,历史流量可被解密,所以 TLS 1.3 直接删掉了它。
最小上手
用 OpenSSL 自签一张证书,跑一个 TLS 服务,再验证链(OpenSSL 1.1.1+):
bash
openssl req -x509 -newkey rsa:2048 -nodes -days 1 \
-keyout key.pem -out cert.pem -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost"
openssl s_server -accept 8443 -cert cert.pem -key key.pem -www &
openssl s_client -connect 127.0.0.1:8443 -servername localhost \
-CAfile cert.pem -verify_return_error </dev/null期望输出包含 Verify return code: 0 (ok) 和 Protocol : TLSv1.3。去掉 -CAfile 再跑一次,会得到 self-signed certificate(返回码 18),说明验证确实发生了;不加 -verify_return_error 时,s_client 即使验证失败也会把连接做完。
看真实站点的证书链和协议:
bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/null \
| grep -E 'Protocol|Cipher'
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName本地证书文件用 openssl verify -CAfile root.pem -untrusted intermediate.pem leaf.pem;-untrusted 提供中间证书,-CAfile 只放信任根。
生产动作
- 到期监控:按续期周期留出至少一个完整续期窗口(例如 30 天)告警;用 ACME / cert-manager 自动续期,续期后要 reload 或热加载证书,否则文件换了进程还在用旧的。用上面的
openssl x509 -enddate做探针。 - 证书链完整性:服务器必须把中间证书一起发出。浏览器可能通过 AIA 自动补链,Java、Go、curl 通常不会,表现为「浏览器正常,客户端报
unable to find valid certification path」。用-showcerts数链,或在客户端机器上复现。 - 协议与套件:最低 TLS 1.2,优先 1.3;禁用 TLS 1.0/1.1、RC4、3DES、CBC。用
openssl ciphers -v 'DEFAULT'看本机策略,或nmap --script ssl-enum-ciphers -p 443 host从外部核对。 - HSTS:
Strict-Transport-Security: max-age=31536000; includeSubDomains。先上短max-age验证,再逐步加长;一旦把错误配置推给浏览器,回滚期内用户会被锁在 HTTPS 上。 - OCSP stapling:由服务器代取 OCSP 响应并随握手下发,减少客户端外联和隐私泄露;配置失败时通常 soft-fail,等于没生效,要在服务端日志里确认 stapling 成功。
- 0-RTT:只对 GET 等幂等请求开启;在 LB/WAF 层做重放防护,否则攻击者可以重放早期请求。
- TLS 卸载点:在 LB 终止 TLS 时,LB 到后端可能是明文;合规场景要后端 TLS 或 mTLS,并确认
X-Forwarded-Proto正确传递,避免应用生成错误的跳转链接。
排障按「现象 → 命令 → 判断」:
- 浏览器报证书错误 →
openssl s_client -showcerts -servername <域名>→ 看Verify return code、链长度、SAN、有效期;返回码 10(过期)、18(自签)、20(无法取得本地签发者)、21(链不完整)分别对应不同动作。 - 浏览器正常,Java/Go 客户端失败 → 在客户端侧抓证书链 → 多数是缺中间证书或客户端不信任该根。
- 握手慢 →
curl -w '%{time_connect} %{time_appconnect}\n' -o /dev/null -s https://host→time_appconnect - time_connect是 TLS 耗时;反复全握手说明会话复用没生效,或 OCSP 外联超时。
边界
- HTTPS 不隐藏目标 IP、端口、流量大小和时序;SNI 在多数部署中仍是明文,ECH 尚未普及。
- 证书只证明「私钥持有者与证书主体匹配」,不证明网站可信;钓鱼站也能拿到有效证书。
- 在 LB 终止 TLS 后,内网段可能是明文;「入口用了 HTTPS」不等于全链路加密。
- 自签名/私有 CA 适合内网,但必须把根证书分发到所有客户端信任库,运维成本只是转移,没有消失。
- 0-RTT 有重放风险;会话票据泄露会削弱前向保密,票据密钥要轮换。
- 加解密开销通常小于业务处理,尤其长连接和会话复用之后;「HTTPS 慢」很少是拒绝上 TLS 的正当理由,首次握手和证书链才是主要成本。
讲清楚
HTTPS 就是 HTTP 加一层 TLS。TLS 先用非对称密钥交换协商出对称会话密钥,TLS 1.3 的证书握手只允许 (EC)DHE 这类临时密钥交换,所以天然具备前向保密;之后用 AES-GCM 或 ChaCha20-Poly1305 加密数据,兼顾安全和大流量性能。身份靠证书链:服务器发叶子证书和中间证书,客户端一路验签到本地信任的根,同时检查域名、有效期和用途。TLS 1.3 把完整握手压到 1-RTT,删掉了 RSA 密钥交换和 CBC 等旧算法,ServerHello 之后的消息全部加密;代价是 0-RTT 恢复有重放风险。运维上最常翻车的不是算法,而是证书过期和链不完整:浏览器会自动补中间证书,Java、Go、curl 不会。排障第一步永远是
openssl s_client,先看 verify code、链长度、协议版本和有效期,再谈性能。