4. TLS协议基础:握手过程、记录协议、扩展
拿咱们今天的场景来说,TLS 的核心其实就是三件事:谁是谁、用什么加密、数据怎么安全传。下面我把握手、记录层和扩展这三个关键部分拆开讲,顺便聊聊我在国密适配里踩过的坑。
4.1 握手过程——建立信任的第一步
记住一条规则:握手结束了,数据才能开始加密传输。整个流程说白了就是客户端和服务器互相交换随机数、证书、密钥协商参数,最后双方算出同一个主密钥。
我整理了一个简化的标准 TLS 1.3 握手流程图(国密场景下其实也相似,只是密码套件换了)。你一看就明白:
你仔细看看这六个步骤。关键在第二步和第三步:服务器发送证书时,里面包含 SM2 公钥证书;客户端收到后用 SM2 验签,然后双方通过 SM2 密钥交换算出预主密钥。
国密握手与标准 TLS 的不同点:
- 证书签名算法使用 SM2 与 SM3 哈希
- 密钥交换使用 SM2 椭圆曲线密钥交换协议
- Finished 消息中的验证数据使用 SM3 计算
我在项目里就遇到过一个坑:客户端 ClientHello 里带了国密扩展,但服务器返回的证书是 RSA 的,直接报错。其实只要双方在 Hello 消息里确认好密码套件,后面就不会乱。
4.2 记录协议——数据运输的管道
握手完了,记录协议开始干活。它的职责简单讲就四个字:包好再发。
每一条记录都包含一个 5 字节的头部:
| 字段 | 长度 | 说明 |
|---|---|---|
| ContentType | 1 字节 | 记录类型:20(ChangeCipherSpec)、21(Alert)、22(Handshake)、23(Application Data) |
| ProtocolVersion | 2 字节 | 比如 0x0303 表示 TLS 1.2 |
| Length | 2 字节 | 负载长度(不超过 2^14+2048 字节) |
之后就是负载。TLS 1.3 中记录协议必须使用 AEAD 加密模式。国密推荐 SM4-GCM——它把加密和完整性校验合在一起,省事。
关于序列号,我必须提醒你:
TLS 记录层内部维护一个每方向(读/写)的 64 位序列号,每次加密/解密递增。我曾经调过一个 bug,对端重复发送了同一条记录,序列号一致,解密直接失败——其实这正是防重放攻击的机制。如果你的实现里把序列号重置了,那就等于把历史记录当成了有效数据,后果很严重。
记录层还会处理分片和重组。比如一条大的应用数据可能被切成多个记录发送,每个记录单独加密。我在做国密改造时见过一个场景:某硬件加密机只支持最大 2KB 的 SM4-GCM 单次加密,我们就得在上层主动分片,确保每条记录负载不超过 2KB。
4.3 扩展——灵活协商的秘密武器
TLS 扩展机制给了协议极大的弹性。说白点,握手双方可以通过扩展字段告诉对方“我支持 XX 功能”,不需要改动核心协议版本。
常见扩展大概有几十种,挑几个和国密相关的说说:
- supported_groups:告诉对方我支持哪些椭圆曲线。国密场景下必须带上
Sm2p256v1(曲线 OID 1.2.156.10197.1.301)。 - key_share:TLS 1.3 里客户端提前把公钥放在这里,服务器直接选用,减少一次往返。国密 SM2 密钥交换的公钥也放这里。
- signature_algorithms:声明支持的签名算法。国密证书签名当然要写
sm2sm3。 - 国密专属扩展:有些厂商或标准会定义类似
gm_cipher_suites的 vendor extension,用来传递国密套件标识。这个不在 RFC 里,但实际部署中用得很普遍。
我还见过一种做法:在扩展里嵌入整个 SM2 证书链。客户端发送 ClientHello 时直接把国密证书链带上,服务端验证后就不用再重新发证书了。这能省掉一次网络往返,非常适合 IoT 或低带宽环境。
避坑指南: 我曾经遇到过服务器不认识客户端发的扩展 ID,直接忽略掉,导致国密套件协商失败。解决方法是:在实现时,如果服务器不支持某个扩展,必须发一个 unrecognized_name 的 Alert,而不是沉默。这个细节在 RFC 8446 的 4.2 节写得很清楚,很多人容易忽略。
4.4 三者如何串起来?
说实话,握手、记录、扩展不是割裂的。举一个场景你就明白了:
- 客户端发起 ClientHello,里面通过 supported_groups 扩展声明支持 SM2 曲线。
- 服务器回复 ServerHello,也带上 key_share 扩展,里面放 SM2 公钥。
- 握手阶段双方用 SM2 协商出密钥。
- 握手的 Finished 消息通过 记录协议 用 SM4-GCM 加密发送。
- 之后的业务数据同样由记录协议加密,每个包都带一个 5 字节头部+ AEAD 认证标签。
整个链路里,扩展负责“谈条件”,握手负责“定规则”,记录协议负责“执行”。少任何一个环节,国密连接都跑不通。
// 一个简易的伪代码示例:国密 TLS 1.3 协商的关键参数
ClientHello:
cipher_suites = [TLS_SM4_GCM_SM3, ...]
key_share = { curve: Sm2p256v1, public_key: ... }
signature_algorithms = [sm2sm3]
ServerHello:
cipher_suites = TLS_SM4_GCM_SM3
key_share = { curve: Sm2p256v1, public_key: ... }
// 后续握手使用 SM2 进行密钥交换
嗯,讲到这里基本把 TLS 基础的三个核心块说清楚了。你只要记住:握手建立信任,记录保护数据,扩展提供灵活性——结合国密,就只用把里面的数学算法换成 SM2/SM3/SM4 即可,框架完全一样。