3. TLS/SSL 协议基础:从握手到加密,一次讲透
好,咱们进入正题。TLS 这东西,说白了就是给 MQTT 通信加一把锁。没有它,你的设备数据就是在网上裸奔。我刚开始做物联网那会儿,总觉得 TLS 配置太麻烦,结果客户现场被抓包,设备证书和密钥全被扒走……嗯,从那以后,我再也不敢偷懒了。
3.1 非对称加密 vs 对称加密:谁负责什么?
先搞清楚两个基本概念。你想想看,如果两个人要悄悄传纸条,最直接的办法是约定一个暗号(密钥),双方都用这个暗号加密解密——这就是对称加密。速度快,但问题来了:暗号怎么安全地交给对方?
非对称加密解决了这个难题。它有一对钥匙:公钥公开,私钥保密。公钥加密的数据只有私钥能解开。我在项目中见过不少新手把私钥也上传到代码仓库,那简直是给黑客送大礼。
核心分工:
- 非对称加密(RSA/ECC):用于 TLS 握手阶段的身份认证和密钥交换。慢,但安全。
- 对称加密(AES/ChaCha20):用于握手完成后的大量数据传输。快,但密钥必须保密。
实际 TLS 握手中,非对称加密只用来交换一个临时对称密钥,后续所有数据都用对称加密。这叫“混合加密体系”,兼顾安全与性能。
3.2 TLS 握手过程:四次挥手?不,是四次握手
我画了一张流程图,帮你把 TLS 1.2 的完整握手过程看清楚。别被吓到,其实就四个主要阶段。
我挑几个关键点说说:
- ClientHello:客户端告诉服务端“我会这些密码套件,这是我的随机数”。随机数很重要,后面生成对称密钥要用。
- ServerHello + 证书:服务端选定密码套件,发回自己的证书。如果是双向认证,还会附带
CertificateRequest,要求客户端也出示证书。 - 客户端证书:这一步是双向认证的关键。客户端把自己的证书发过去,还要用私钥签名一段数据(CertificateVerify),证明“我就是证书上写的那个人”。
- Finished:双方确认握手完成,之后所有数据都用协商好的对称密钥加密。
我的经验: 调试 TLS 握手问题时,用 openssl s_client -connect host:port -debug 可以看完整握手日志。我曾经花了两天排查一个双向认证失败的问题,最后发现是客户端证书的 KeyUsage 字段缺少 digitalSignature——证书本身没问题,但服务端不认。
3.3 证书链验证:为什么需要中间 CA?
你拿到的服务器证书,通常不是根 CA 直接签发的。中间还有一层甚至多层中间 CA。为什么搞得这么复杂?
安全考虑。根 CA 的私钥如果泄露,整个信任体系就崩了。所以根 CA 的私钥通常离线保存,平时只用中间 CA 签发证书。就算中间 CA 出问题,吊销它就行,根 CA 还是安全的。
验证证书链时,客户端会做这几件事:
- 从服务器证书开始,逐级向上找签发者,直到信任的根 CA。
- 验证每级证书的签名是否有效(用上一级证书的公钥解密签名,比对证书摘要)。
- 检查证书是否在有效期内,是否被吊销(CRL/OCSP)。
- 检查证书用途(Extended Key Usage)是否匹配——比如服务器证书要有
serverAuth,客户端证书要有clientAuth。
避坑指南: 我曾经遇到一个案例,设备上报“证书验证失败”,查了半天发现是中间 CA 证书没部署在设备信任库里。很多 IoT 设备只内置了根 CA,但服务器用的是中间 CA 签发的证书。设备必须把中间 CA 证书也装进去,否则链断了。记住:证书链必须完整,缺一环都不行。
3.4 TLS 1.2 vs TLS 1.3:到底差在哪?
TLS 1.3 是 2018 年发布的,改动很大。我直接给你对比表,一目了然。
| 对比项 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 握手轮次 | 2-RTT(4 次消息交换) | 1-RTT(3 次消息交换,首次连接) |
| 0-RTT | 不支持 | 支持(恢复会话时可直接发数据) |
| 密码套件 | 支持几十种,含大量老旧算法 | 仅支持 5 种强安全套件 |
| 密钥交换 | 支持 RSA、DH、ECDH 等多种 | 仅支持 (EC)DHE,前向安全性强制 |
| 证书加密 | 证书明文传输 | 证书加密传输(可选) |
| 算法协商 | 客户端列出所有支持的,服务端选 | 客户端列出支持的,服务端选,但剔除了不安全算法 |
说白了,TLS 1.3 更快、更安全、更简单。我建议新项目直接上 TLS 1.3。但要注意,有些老旧 MQTT Broker 或设备 SDK 还不支持 1.3,这时候只能退回到 1.2。
MQTT 场景下的选择建议:
- 如果设备性能较强(如 Linux 网关、树莓派),优先 TLS 1.3。
- 如果设备是 ESP8266 这类资源受限的 MCU,TLS 1.2 更稳妥,因为 1.3 的握手计算量略大。
- 双向认证在 TLS 1.2 和 1.3 中都支持,但 1.3 的握手流程略有不同,客户端证书在 EncryptedExtensions 之后发送。
3.5 密码套件选择:别用那些过时的
密码套件长这样:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256。拆开看:
- 密钥交换:ECDHE(椭圆曲线 Diffie-Hellman 临时密钥)——提供前向安全性
- 身份认证:RSA(用 RSA 证书签名验证身份)
- 对称加密:AES_128_GCM(128 位 AES,GCM 模式带认证)
- 哈希:SHA256(用于消息完整性校验)
我见过不少项目还在用 TLS_RSA_WITH_AES_128_CBC_SHA,这玩意儿既不支持前向安全,CBC 模式还有 padding oracle 攻击风险。赶紧换掉。
推荐套件(按优先级排序):
TLS_AES_128_GCM_SHA256(TLS 1.3 默认)TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(ECC 证书,性能更好)TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256(RSA 证书,兼容性好)
我个人习惯在服务端只开这 3 个套件,其他全关。少即是多,攻击面也小。