2、SSL/TLS 基本工作原理

要说清楚 SSL/TLS,我们得先聊聊它到底解决了什么问题。我早年调一个 HTTPS 接口时,数据抓包一看全是明文,瞬间冷汗就下来了——要是中间有人偷听,密码、 全白送。所以,SSL/TLS 本质上就是给通信上了三把锁:加密(别人看不懂)、身份校验(确保对方是真的)、完整性校验(数据没被改过)。

2.1 两层协议:握手层和记录层

SSL/TLS 并不是一个单一的协议,而是分上下两层。每次连接都得先走握手,然后才能安全地传数据。我个人习惯把这两层理解为「建房子」:握手协议是打地基和搭框架,记录协议是往房间里放家具。

  • 握手协议(Handshake Protocol)——负责协商密码套件、交换密钥、验证证书。这一层决定了你们用什么加密、用什么 hash。
  • 记录协议(Record Protocol)——负责把应用层数据分段、压缩(TLS 1.3 取消了)、加 MAC、加密,然后丢给 TCP 层。
嗯,这里要注意:握手协议产生的所有消息其实也是通过记录协议发送的——握手本身也是数据,只不过它的内容类型是 Handshake(22),而不是 Application Data(23)。

2.2 握手流程:一句“你好”来回四次

拿最经典的 TLS 1.2 完整握手来说,不算恢复会话,大概需要 2 个 RTT(往返延迟)。我曾在生产环境遇到过因为握手延迟太长导致 App 超时,后来切了 TLS 1.3 的 0-RTT 才好。下面这张图能帮你快速理清流程:

TLS 1.2 完整握手流程 客户端 (Client) 服务端 (Server) ① ClientHello ② ServerHello + Certificate + ServerHelloDone ③ ClientKeyExchange + ChangeCipherSpec + Finished ④ ChangeCipherSpec + Finished 加密应用数据 加密应用数据 说明:TLS 1.2 完整握手共 2 个 RTT。步骤 ③ 中 ChangeCipherSpec 和 Finished 一起发出,步骤 ④ 同理。

2.3 记录协议到底做了什么?

握手完成后,记录协议就上场了。每次发数据时,它会先把应用层数据切成合适大小的片段(默认最大 16KB),然后可选地压缩(TLS 1.3 移除了这一步),接着加上消息认证码(MAC),最后用协商好的对称密钥加密。我举个例子帮你理解:

原始数据: "Hello, 这是支付请求"
→ 分段: ["Hello, 这是", "支付请求"]
→ 加 MAC: [片段1 || MAC1, 片段2 || MAC2]
→ 加密: [AES-GCM加密(片段1 || MAC1), AES-GCM加密(片段2 || MAC2)]
→ 添加记录头 (5字节): [内容类型(1B) || 版本(2B) || 长度(2B)]
→ 发送
曾经有同事在迁移密码套件时把记录协议的 MAC 算法配成了空(NULL),结果数据包被篡改导致业务异常。记住:NULL 加密 + NULL MAC 只在调试时用,生产环境永远不要碰。

2.4 密码套件的“三要素”

密码套件是握手协议的核心产物。说白了,就是双方共同拍板的一个套餐——密钥交换算法 + 身份认证算法 + 对称加密 + MAC/认证加密。下面这张表是常见的几个例子:

密码套件 密钥交换 认证 对称加密 & MAC
TLS_RSA_WITH_AES_128_CBC_SHA RSA RSA 证书 AES-128-CBC + SHA-1 HMAC
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 ECDHE RSA 证书 AES-128-GCM (AEAD)
TLS_AES_128_GCM_SHA256 (TLS 1.3) (由密钥协商隐式完成) 任意证书 AES-128-GCM + HKDF

我个人建议:新项目直接使用 TLS 1.3,因为它把密码套件精简到了 5 个,而且把「密钥协商 + 认证 + 加密」全部设计成了强安全组合,几乎没有配置错误的空间。

2.5 避坑:证书链和信任锚

你可能会觉得证书验证是 CA 的事,跟你没关系。我以前也是这么想的,直到自己搭内部 CA 时,忘了把中间证书配置到服务端,结果移动端一直报错“无法建立安全连接”。检查之后才发现:服务端必须发送完整的证书链(不包括根,因为客户端信任的只有根),否则客户端没法拼出信任路径。

  • 服务端:在 ServerHello 后发 Certificate 消息,带上自己的证书 + 中间 CA 证书(可多个)。
  • 客户端:用本地信任的根 CA 去校验证书链,检查有效期、颁发者、扩展(如 SAN)。
  • 证书吊销检查:可配 OCSP Stapling 或 CRL,但很多默认不启用——我建议至少开 OCSP Stapling 以减少客户端延迟。