2. HTTPS 协议简介与 TLS 关系
HTTPS 说白了就是 HTTP 的安全版本。我记得刚入行时有个老前辈跟我说:“HTTP 是明信片,HTTPS 是装在信封里的信。” 这个比喻很形象。你在网上填的密码、银行卡号,如果走 HTTP 那就是明牌,路径上的任何节点都能看光光。HTTPS 就是在 HTTP 外面套了一层加密保护壳。
这层壳到底是什么?就是 TLS(传输层安全协议)。TLS 的前身是 SSL,后来改了个名。现实中大家还是习惯混着叫“TLS/SSL”。我建议你统一叫 TLS,毕竟 SSLv3 已经被明文禁止了。
2.1 HTTPS = HTTP + TLS
很多人以为 HTTPS 是“更安全的 HTTP 协议”,其实不全对。更准确的描述是:HTTPS 是 HTTP over TLS。HTTP 负责业务数据的格式,TLS 负责把它们加密后再丢给 TCP。两者是上下层关系,各自干各自的活。
关键认知: 改造 SDK 时你不需要修改 HTTP 层的业务逻辑,只要在底层插入 TLS 握手和加密通道即可。我最初做接入改造时总想把业务请求体改掉,折腾了一周才发现方向错了。
来看一个简化的协议栈对比:
| 协议栈层级 | HTTP(明文) | HTTPS(加密) |
|---|---|---|
| 应用层 | HTTP 请求/响应 | HTTP 请求/响应 |
| 安全层 | 无 | TLS 记录协议 + 握手协议 |
| 传输层 | TCP | TCP |
| 网络层 | IP | IP |
你想想看,TLS 层像不像一个“透明代理”?它对上层的 HTTP 完全不可见,HTTP 仍然按自己的方式构造报文。TLS 只负责把这些报文碎片化、压缩(可选)、加密、加上 MAC 校验,然后塞给 TCP。反过来接收时先解密、校验完整性,再拼回 HTTP 报文交给上层。
提示: 调试阶段可以用 Wireshark 抓包,设置 TLS 解密密钥(通过 SSLKEYLOGFILE 环境变量导出)。我个人习惯在开发环境先抓明文包确认业务逻辑,再抓加密包确认 TLS 流程。
2.2 TLS 的核心能力
TLS 不是一套简单的加密方案,它同时干三件事:
- 身份认证:通过证书链验证服务器(可选验证客户端)的真实身份,防止中间人冒充。
- 机密性:使用对称密钥加密传输数据,防止被窃听。
- 完整性:使用消息认证码(MAC)校验数据是否被篡改。
我曾经遇到过一个坑:某团队自建 CA 证书,但证书配置错误(通用名不匹配),客户端在校验时直接拒绝握手。他们排查了两天没找到原因,最后发现是服务端证书的 SAN 里没加 IP 地址。嗯,这里要提醒你:证书的通用名或 SAN 必须与客户端访问的域名严格一致。
警告: 不要在正式环境使用自签名证书,也不要随意信任未知的根证书。我曾经见过一个生产事故:某内部 SDK 硬编码信任了一个过期测试证书,导致服务迁移后全部客户端握手失败。教训是:证书生命周期管理和安全策略要写进 SDK 的监控告警里。
2.3 从 HTTP 到 HTTPS 的连接变化
一个完整的 HTTPS 请求,比 HTTP 多加了一步“TLS 握手”。握手完成后才会进入正常的数据传输。
大致流程是这样:
- 客户端发起 ClientHello,告诉服务端支持的 TLS 版本、密码套件列表、随机数等。
- 服务端回复 ServerHello,选定密码套件和版本,并下发证书(Certificate)。
- 客户端验证证书,生成预主密钥,用服务端公钥加密后发送。
- 双方各自计算会话密钥,然后发送 ChangeCipherSpec 和 Finished 消息确认。
- 握手完成,进入加密通信阶段。
是不是觉得步骤挺多?其实大部分 SDK 框架(比如 OkHttp、URLSession)都已经帮我们封装好了。但作为接入改造的工程师,你必须知道握手中哪个环节可能会失败,否则排查问题时两眼一抹黑。
下面这张图把核心流程画了出来:
你看这个图,左右两边是对称的。客户端从上往下构造请求、握手、加密、发送;服务端从下往上接收、解密、握手、解析响应。TLS 层在中间扮演了“加解密过滤器”的角色。
2.4 SDK 改造关注点
作为 SDK 接入改造的开发者,你根本不需要手动实现 TLS 握手。现代操作系统和语言运行库都已经内置了 TLS 实现。你需要关注的是:
- 协议版本选择:我建议最低支持 TLS 1.2,尽量启用 TLS 1.3。TLS 1.0/1.1 已被 RFC 宣布废弃,再用它们就是安全漏洞。
- 证书校验证:信任锚(根证书列表)怎么维护?是否允许用户自定义信任 CA?这些策略在 SDK 中要明确。
- 密码套件:不要使用 RC4、DES、3DES 以及 CBC 模式的旧套件。推荐 AEAD 套件,比如 TLS_AES_128_GCM_SHA256。
- 会话复用:开启 session ticket 或 session ID 复用可以减少握手开销。我在项目中见过一个诡异的性能问题:客户端每次请求都重新握手,导致 RTT 成倍增加。原因就是 SDK 没启用会话缓存。
小技巧: 可以用命令 openssl s_client -connect host:port -tls1_2 快速测试服务端的 TLS 配置是否符合预期。我经常拿这条命令在对接前做“握手检查”,比写代码测试快多了。
2.5 避坑指南:容易混淆的几个概念
- HTTPS 用 443 端口 ≠ 只用 TLS:端口只是约定,IP 层网络安全(比如加密)由 TLS 提供,端口只是传输层标识。
- TLS 握手延迟 ≠ 网络延迟:握手本身会引入 1-2 个额外 RTT(TLS 1.2 是 2-RTT,TLS 1.3 是 1-RTT)。如果你在做高并发长连接接入,一定要考虑握手开销。我曾经把一个 MQTT 长连接的握手由每次重建改为复用 session,延迟从 300ms 降到了 50ms。
- 证书过期 ≠ 服务器故障:很多排查手册上来就抓包看 TCP,其实 90% 的 HTTPS 连接失败都是证书问题。我建议把证书状态检查作为 SDK 的启动自检项之一。
再强调一次: 不要在代码里硬编码“跳过证书校验”!我曾经在客户代码里看到 checkServerTrusted() 直接 return,原因是为了方便调试。这个习惯一旦上生产,等于把门敞开。正确做法是:调试环境使用自定义 TrustManager 并打印证书信息,正式环境使用系统默认 TrustManager。