TLS/SSL 基础概念与工作原理

从 HTTP 到 HTTPS,我们到底在怕什么?

说实话,十年前我第一次接触网络编程,觉得 HTTP 挺好的——简单,直接,跑得快。直到有一次公司做内部工具,我在公网上传用户登录数据,抓包一看,用户名密码全是明文。那一刻我才意识到:不对,这玩意儿谁都看得见。

你想想看,未加密的 HTTP 就像在明信片上写密码。每个路由节点、每个 WiFi 热点都能抄走内容。我们真正要防的,是三类威胁:

  • 窃听 —— 第三方截获通信内容,比如抓包看你的银行卡号
  • 篡改 —— 中间人往响应里塞恶意脚本,你浏览器还一脸无辜
  • 冒充 —— 钓鱼网站伪装成银行,你根本分不清真假

而 TLS/SSL 就是解决这些问题的标准方案。它工作在传输层之上、应用层之下,给 TCP 连接加了一层「加密隧道」。我在项目中见过太多人以为 HTTPS 只是「HTTP + 证书」,其实它背后的握手协商机制才是精髓。

TLS/SSL 是什么?

SSL 是 Netscape 在 90 年代搞出来的,后来标准化成了 TLS。目前主流是 TLS 1.2 和 TLS 1.3。我个人习惯把 TLS 看作两个阶段:

  1. 握手阶段 —— 双方确认协议版本、选择加密套件、交换密钥、验证身份
  2. 记录阶段 —— 用协商好的对称密钥,加密所有应用数据

说白了,非对称加密只在握手时用,握手完就换成对称加密。为什么?非对称太慢了,你拿 RSA 加密几百 K 的数据包,CPU 直接冒烟。

我建议你记住一个核心原则:握手的终极目标是让双方安全地共享一个对称密钥。这个密钥叫「预主密钥」(Pre-Master Secret),后续会派生出会话密钥。

小提示:TLS 1.3 把握手从 2-RTT 砍到了 1-RTT,甚至 0-RTT(有风险)。如果你从零接入,优先考虑 TLS 1.3。

TLS 握手流程详解

我们先看经典 TLS 1.2 完整握手。我会用实际项目里踩过的坑来讲解每一步。

步骤方向消息我的经验
1Client→ServerClientHello版本、密码套件列表、随机数
2Server→ClientServerHello选定版本+套件,发服务器随机数
3Server→ClientCertificate服务器证书(含公钥)
4Server→ClientServerHelloDone到此为止,该你出牌了
5Client→ServerClientKeyExchange用服务器公钥加密的预主密钥
6BothChangeCipherSpec告诉对方:我要切换加密了
7BothFinished用会话密钥加密握手消息的 HMAC,验证完整性

我曾经调试一个「连接卡死」的 bug,抓包发现服务器发完 Certificate 后客户端没响应。查了两天,原因是服务器证书链中间 CA 没配全,客户端验证失败直接丢掉了连接。嗯,证书链千万别漏。

警告:预主密钥加密用的公钥长度有限制。如果你用 RSA 2048 位公钥,最多只能加密 256 字节的数据,且不能大于模长。所以握手消息里放的是「对称密钥种子」,不是真正的密钥。

握手完成后,客户端和服务器各自计算会话密钥:

master_secret = PRF(pre_master_secret, "master secret", 
                     ClientHello.random + ServerHello.random)
key_block = PRF(master_secret, "key expansion", 
                ServerHello.random + ClientHello.random)

PRF 是伪随机函数,在 TLS 1.2 里用 SHA-256 做 HMAC。这里要注意,两个随机数必须都参与,否则如果有人能预测其中一个,就可以伪造密钥。

加密套件与安全参数

加密套件就是握手时双方选的那个「组合包」。TLS 1.2 的套件名字很长,例如 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。拆开看就四个部分:

  • 密钥交换算法 —— ECDHE(临时椭圆曲线 Diffie-Hellman),支持前向安全性
  • 身份验证算法 —— RSA(用 RSA 签名验证证书)
  • 对称加密算法+模式 —— AES 128 GCM(认证加密,AEAD)
  • HMAC 哈希算法 —— SHA-256(用于 Finished 校验和 PRF)

我强烈建议你在 SDK 里禁用所有不带 PFS(前向安全)的套件,比如纯 RSA 密钥交换。为什么?如果服务器私钥泄露,攻击者可以解密之前所有录制的流量。用 DHE 或 ECDHE,每次握手生成临时密钥对,私钥丢了你也没法解历史数据。

另一个坑是 GCM 与 CBC 的选择。TLS 1.2 里 CBC 模式曾被曝出 Padding Oracle 攻击。我个人习惯直接砍掉 CBC,只用 GCM 或 ChaCha20。

证书体系与信任链

证书就是把公钥绑定到实体身份的东西。你不信服务器自己报名字,得有个大家公认的「介绍人」——CA(证书颁发机构)。流程大致是:

  1. CA 用自己的根证书私钥签发中间 CA 证书
  2. 中间 CA 签发你的服务器证书
  3. 客户端收到证书链,从叶子证书往上验证,直到信任的根证书

我记得有个同事图省事,直接把自签名证书丢到线上。结果 iOS 客户端死活连不上,因为系统根信任列表里没那个根证书。最后只好走 MDM 推送安装根证书,折腾了一周。

核心要点:证书验证必须检查四点——有效期、域名匹配、吊销状态(OCSP/CRL)、整条链的签名。缺一项都不安全。

TLS 1.2 完整握手流程 Client Server ① ClientHello(版本+套件+随机数) ② ServerHello(选定参数+服务器随机数) ③ Certificate(服务器证书链) ④ ServerHelloDone ⑤ ClientKeyExchange(预主密钥) 计算主密钥 ⑥ ChangeCipherSpec(切换加密) ⑦ Finished(加密的握手校验)

这张图里每个箭头对应一次消息交互。你可能会问:怎么证书验证在握手里没画出来?其实客户端收到 Certificate 后会立即做链校验,不过它不产生新消息。如果验证失败,客户端直接发 Alert 并断开,不会有下一步。

最后提一句,TLS 1.3 精简了很多步骤,把 ServerHello 和 Certificate 合并,而且砍掉了静态 RSA 密钥交换。如果你的 SDK 支持,直接上 TLS 1.3,减少握手延迟。