4、轻量级证书方案:CWT/CBOR Web Token与最小证书设计
好,咱们进入第四章。这一章聊的是「轻量级」。在IoT里,重量级的东西往往活不下来——你想想看,一个传感器节点,RAM才几十KB,你让它去解析一个几KB的X.509证书,再算一次RSA-2048签名,那基本就卡死了。所以,我们必须找到更轻的路。
我个人习惯把这一章的内容归纳为「三个替换」:用CBOR替换JSON,用CWT替换JWT,用Ed25519替换RSA/ECDSA。下面我们一个一个拆开讲。
4.1 CWT/CBOR Web Token:比JWT瘦一圈
先说说CWT。全称是CBOR Web Token,标准在RFC 8392。它和JWT的关系,说白了就是「同一套逻辑,换了个更紧凑的编码」。JWT用JSON,CWT用CBOR。
CBOR是什么?它是一种二进制序列化格式,类似JSON但更小、更快。我做过一个对比实验:同样的payload,JWT大概250字节,CWT只有160字节左右。在LoRa或者NB-IoT这种MTU只有几百字节的链路上,这90字节的差距可能就是「能发」和「发不了」的区别。
CWT的结构和JWT几乎一样:
- 头部(Protected Header):算法标识、密钥ID等
- 载荷(Payload):iss、sub、aud、exp等声明
- 签名(Signature):MAC或数字签名
但CWT用CBOR的整数键映射代替了JSON的字符串键。比如:
// JWT 中的 "iss" 字段
{"iss": "device-001"}
// CWT 中对应的 CBOR 编码
A1 01 6A 64 65 76 69 63 65 2D 30 30 31
// 键 0x01 代表 "iss",值用文本字符串编码
你看,光是键名就从3个字节("iss")变成了1个字节(0x01)。积少成多,整个token就瘦下来了。
4.2 嵌入式证书格式对比:谁更合适?
接下来,我们对比一下几种常见的嵌入式证书格式。我列了个表,方便你一眼看清差异。
| 格式 | 编码方式 | 典型大小 | 解析复杂度 | 适用场景 |
|---|---|---|---|---|
| X.509 DER | ASN.1 DER | 500-2000 B | 高(需要ASN.1解析器) | 网关、服务器 |
| X.509 PEM | Base64 + DER | 700-2800 B | 高 | 调试、配置 |
| CWT | CBOR | 100-300 B | 低 | 传感器、终端节点 |
| Raw Public Key | 裸公钥 | 32-64 B | 极低 | 受限节点、静态配置 |
| Mini-Cert | 自定义二进制 | 50-150 B | 自定义 | 极受限节点 |
从这张表能看出来,X.509 DER虽然通用,但在IoT终端上太重了。我见过一个项目,工程师硬把X.509解析器塞进Cortex-M0,结果Flash占了一半,跑起来还动不动就栈溢出。后来换成CWT,问题全解决了。
Raw Public Key(RPK)是最轻的,只有公钥本身。但它没有元数据——没有过期时间、没有颁发者、没有密钥用途。所以RPK只适合在封闭网络里用,比如工厂内部传感器网络。
4.3 Ed25519曲线在IoT中的优势
说到轻量级签名,就绕不开Ed25519。这条曲线是Daniel J. Bernstein设计的,属于Curve25519家族。我最早接触它是在做安全固件更新的时候,当时被RSA-2048的签名速度折磨得不行——一个签名验证要几百毫秒,用户等得直骂娘。
换成Ed25519之后,验证时间降到了几毫秒。为什么这么快?因为Ed25519用了扭曲爱德华兹曲线,计算上比Weierstrass曲线(比如P-256)更高效。具体来说:
- 签名大小:64字节(32字节R + 32字节S)
- 公钥大小:32字节
- 私钥大小:32字节
- 验证速度:比ECDSA P-256快2-3倍
- 抗侧信道攻击:设计上天然免疫定时攻击
我拿一个实际项目的数据说话:在STM32F103(72MHz Cortex-M3)上,Ed25519签名验证平均耗时2.1ms,而ECDSA P-256需要5.8ms,RSA-2048更是高达28ms。这差距,在电池供电的设备上就是「一年换一次电池」和「三个月换一次电池」的区别。
还有一个容易被忽略的点:Ed25519的私钥生成不需要高质量随机数。因为它的私钥是直接随机生成的32字节,然后通过哈希派生公钥。而ECDSA的私钥生成需要检查是否在曲线阶的范围内,有时候会重试。在缺乏硬件随机数发生器的低成本MCU上,这个差异很要命。
4.4 最小证书设计原则
好了,前面讲了工具,现在讲方法论。最小证书设计,说白了就是「能砍的都砍掉,只留必须的」。我总结了五条原则,都是踩坑踩出来的。
- 原则一:只保留认证路径必需的字段
X.509证书里有一堆扩展字段:密钥用法、增强型密钥用法、证书策略、颁发者备用名……在IoT里,大部分都用不上。CWT的设计思路就是只保留iss、sub、exp、iat这几个核心字段,其他的一律砍掉。
- 原则二:用整数键代替字符串键
CBOR的整数键映射比JSON的字符串键省很多空间。比如CWT里,iss的键是0x01,sub是0x02,exp是0x04。一个字节搞定。
- 原则三:固定长度字段用二进制编码
时间戳用Unix时间戳(4字节或8字节整数),不要用字符串。公钥直接用原始字节,不要Base64。我见过有人把32字节的公钥用Base64编码成44字节,白白浪费12字节。
- 原则四:签名和公钥用同一曲线
不要混用。比如签名用Ed25519,公钥用RSA,那证书里就得同时存两种密钥材料,体积翻倍。统一用Ed25519,公钥32字节,签名64字节,加起来不到100字节。
- 原则五:考虑证书链的聚合
如果设备需要验证整个证书链(根CA→中间CA→设备证书),可以把链上的所有证书打包成一个CBOR数组。这样设备只需要一次解析,就能拿到所有公钥和签名。我在一个项目中用这个办法,把证书链从1.2KB压缩到了380字节。
下面这张图展示了最小证书的设计流程,从原始X.509到最终CWT的裁剪过程:
嗯,这张图把整个流程串起来了。从左边可以看到我们砍掉了哪些东西,右边是每一步的产出。最终CWT证书的大小只有原始X.509的十分之一左右。
最后说一句:轻量级证书不是「偷工减料」,而是「精准设计」。你砍掉的每一个字段,都要问自己一句:「这个字段在设备生命周期内真的需要吗?」如果答案是「不一定」,那就砍掉。如果答案是「可能用得上」,那就保留。IoT的世界里,每一字节都珍贵。