4. TLS协议基础:握手过程、记录协议、扩展

拿咱们今天的场景来说,TLS 的核心其实就是三件事:谁是谁、用什么加密、数据怎么安全传。下面我把握手、记录层和扩展这三个关键部分拆开讲,顺便聊聊我在国密适配里踩过的坑。

4.1 握手过程——建立信任的第一步

记住一条规则:握手结束了,数据才能开始加密传输。整个流程说白了就是客户端和服务器互相交换随机数、证书、密钥协商参数,最后双方算出同一个主密钥。

我整理了一个简化的标准 TLS 1.3 握手流程图(国密场景下其实也相似,只是密码套件换了)。你一看就明白:

TLS 1.3 握手消息流程(国密兼容版) 客户端(Client) ① ClientHello ③ 证书 / 密钥交换 ⑤ Finished 服务器(Server) ② ServerHello + 证书 ④ 完成密钥计算 ⑥ 应用数据 SM2/SM4 套件协商

你仔细看看这六个步骤。关键在第二步和第三步:服务器发送证书时,里面包含 SM2 公钥证书;客户端收到后用 SM2 验签,然后双方通过 SM2 密钥交换算出预主密钥。

国密握手与标准 TLS 的不同点:

  • 证书签名算法使用 SM2 与 SM3 哈希
  • 密钥交换使用 SM2 椭圆曲线密钥交换协议
  • Finished 消息中的验证数据使用 SM3 计算

我在项目里就遇到过一个坑:客户端 ClientHello 里带了国密扩展,但服务器返回的证书是 RSA 的,直接报错。其实只要双方在 Hello 消息里确认好密码套件,后面就不会乱。

4.2 记录协议——数据运输的管道

握手完了,记录协议开始干活。它的职责简单讲就四个字:包好再发。

每一条记录都包含一个 5 字节的头部:

字段长度说明
ContentType1 字节记录类型:20(ChangeCipherSpec)、21(Alert)、22(Handshake)、23(Application Data)
ProtocolVersion2 字节比如 0x0303 表示 TLS 1.2
Length2 字节负载长度(不超过 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 三者如何串起来?

说实话,握手、记录、扩展不是割裂的。举一个场景你就明白了:

  1. 客户端发起 ClientHello,里面通过 supported_groups 扩展声明支持 SM2 曲线。
  2. 服务器回复 ServerHello,也带上 key_share 扩展,里面放 SM2 公钥。
  3. 握手阶段双方用 SM2 协商出密钥。
  4. 握手的 Finished 消息通过 记录协议 用 SM4-GCM 加密发送。
  5. 之后的业务数据同样由记录协议加密,每个包都带一个 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 即可,框架完全一样。