摘要

  • CBOR 工作组的活跃草案把序列化分为 general、preferred-plus 与 deterministic 三组。确定性序列化在 preferred-plus 之上,按键的确定性编码进行逐字节词典排序;它让编码结果可重复,却不会把无序映射变成有业务先后的对象。
  • 只有在不同参与方必须分别重建签名、哈希、内容地址、缓存键或相等比较所用的字节时,确定性才是必要契约。若被保护的原始字节随消息完整传递,通常无须重编码;而字节一致永远不能代替模式、时效、政策、授权和实际效果的证据。

故障最初被归入“验签失败”。发送端坚持签名正确,接收端则展示了完全相同的解码结果。直到工程师比较十六进制,差异才显现:一个库为参数选择了较长但合法的形式,并沿用映射插入顺序;另一个库使用最短形式并排序键。两份结果都符合 CBOR,却不是同一串字节。

这正是 draft-ietf-cbor-serialization-08 所处理的协调缺口。该草案日期为 2026 年 7 月 29 日,仍是 CBOR 工作组处于 Working Group Last Call 阶段的 Internet-Draft;Datatracker 的 IESG 状态仍为 I-D Exists。它不是最终 RFC,也不能证明任何具体产品已经正确实现。它的价值在于为三种不同的序列化契约命名,使协议作者能明确指出:哪一处必须保留编码自由,哪一处必须得到唯一字节结果。

CBOR 的数据模型与二进制表示不是同一层。同一个值可以拥有多种合法编码。这种弹性支持流式生成、受限设备和广泛兼容。只有当下游把某个二进制结果当成身份、签名输入或内容地址,却没有规定如何选取结果时,弹性才会变成冲突。

三种集合不是三档质量等级

当基于 CBOR 的协议没有另行说明时,general serialization 是理论默认。对于它声称支持的类型,解码器应接受 CBOR 允许的所有编码,包括确定长度和不确定长度形式。草案同时指出,这种宽广的解码承诺在现实实现中并不普遍。“支持 CBOR”因此只是标签,不是可验证的接收矩阵。

preferred-plus serialization 收紧编码端。参数使用最短形式;浮点数使用能够精确保留其值的最短表示;长度必须确定;NaN 使用规定的平凡表示;整数和大整数按继承规则归一化。它意在覆盖大多数普通协议,却不要求映射排序。

deterministic serialization 从 preferred-plus 出发,再加入一项主要规则:映射条目按各自键的确定性编码进行逐字节词典排序。只要数据模型及外围规则已经定义清楚,两个独立编码器就能从同一个值收敛到同一串字节。

这不是越往后越“正确”的阶梯。general 保留了一些设计确实需要的能力,例如在尚不知道总长度时进行流式输出、在应用模型中区分大整数与普通整数,或携带非平凡 NaN 负载。确定性序列化为了可重复而主动放弃这些选择。选择它必须指向一个具体互操作需求,而不是把其他合法编码贬为错误。

编码和接收也必须分开。所谓 deterministic decoding 并不比 preferred-plus decoding 增加要求。系统可以只生成一种窄形式,同时接受更广的输入。若端到端协议要拒绝任何非确定性输入,那是另一项兼容与安全政策,必须明确写出并单独测试,不能从“本系统输出确定性 CBOR”反推。

先问原始字节是否随消息传输

若发送方对 CBOR 负载的原始字节签名,并把同一串字节与签名一起送达,接收方可以直接验证所收到的内容,无须先解码再重新编码。草案以 COSE payload 说明这一情形。签名与哈希需要双方对被保护的字节达成一致,并不自动要求整个系统里的每个 CBOR 对象都只有一种编码。

若双方分别构造密码学输入,情况就相反。COSE 的 Sig_structure 由签名者构造,也由验证者重新构造。只要两边在参数宽度、浮点表示或映射排列上使用不同默认值,即使解码后的模型相同,验签输入也会不同。内容寻址、缓存键、去重、可复现清单和逐字节相等比较都有同类风险。

因此,确定性要求必须精确落在需要重建的边界。Sig_structure 需要确定性,并不意味着内嵌 payload、外层信封以及未来所有对象都自动受同一规则约束。若把局部规则扩张成平台总规,流式需求和历史数据可能付出没有收益的迁移成本。

架构评审可以只问两个问题:哪些原始字节会原样跨过系统边界?哪些对象会被解码后再编码,随后进入签名、哈希或身份判断?很多所谓密码学事故,最终是代理、网关或缓存悄悄重编码了一个本应保持原样的对象。

排序映射键不会产生业务顺序

CBOR 通用数据模型中的映射是无序的。确定性排序只为同一个映射挑选可重复的二进制排列,不会赋予“先处理谁”“谁覆盖谁”或“界面先展示谁”的语义。

误解往往来自调试工具:它总要把键排成一列。某种语言可能保留插入顺序,另一个库可能保留接收顺序,哈希表又可能产生不同迭代次序;它们仍可表示同一个映射。若应用需要优先级,就应通过数组、显式优先级字段或规范化规则来表达,而不是借用序列化副作用。

排序也不会替协议决定重复键、允许的标签、未知字段、模式演进或值等价性。没有映射时,preferred-plus 的输出可能天然确定,但这不能回答某个标签是否获准,也不能回答缺失字段是否改变权限。

字节相等可能比语义相等更严格:业务认为等价的两个值,逐字节比较可能不等。反过来,同一串字节也可能包含过期、恶意或不完整的数据。Unicode 归一化、数值域、默认值、标签解释都属于端到端协议,而非排序算法。

“canonical CBOR”也不能当作省略说明的口号。独立的 draft-mcnally-deterministic-cbor-17 是进一步收紧 CBOR 的应用档案;它的限制不能被默认为工作组草案的规则。审计材料必须写出档案名称和修订版,而不是用“规范化”掩盖互操作差异。

有效签名只为一个字节边界作证

当确定性重建成功且签名有效时,系统获得了一项重要证据:某个明确算法与密钥保护了这一串字节。但它没有证明这串字节包含决策所需的全部事实。

签名不会证明模式完整、标签在当前语境被允许、时间仍然有效、签名者仍持有权力、关键可选字段没有被省略,或本地政策允许执行。它也不会观察获准操作最后是否真正完成。将这些结论塞进“verified”一个布尔值,只是让密码学收据替机构决策背书。

信息模型、CBOR 数据模型、序列化字节、密码学验证、语义验证、本地政策、授权、执行尝试和观察到的效果,都应拥有各自的收据、责任人和有效期。上一层的绿灯不能自动继承下一层的权力。

CDDL 可以描述 CBOR 数据形状。草案讨论的序列化控制机制也可以把编码要求附在描述旁边。但模式不是正在运行的代码,更不是授权或效果收据。CDDL 匹配无法说明究竟哪个解析器版本运行、重复键采用何种政策、选择了哪个信任锚,也无法证明状态变更已经提交。

COSE 和 CWT 等框架能承载许多端到端协议,不可能预先知道每个应用的流式、重建和等价需求。在框架未强制指定时,草案推荐 preferred-plus,再由接入框架的协议规定实际边界。库的默认选项不能替代协议:它会随版本变化,也可能在不同语言绑定中暴露不同开关。

对真实解码器做交叉测试

general 的理论接收范围与现实实现之间存在可测量的差距。供应商可以诚实地写“支持 CBOR”,但某个解码器拒绝不确定长度字符串,另一个从未测试合法的非最短宽度,第三个会悄悄改变大整数。

运行证据应写明库和版本、构建选项、编码模式、解码接受集、模式修订、输入值、实际字节、接受与拒绝的变体,以及下游结果。黄金向量必须在独立实现之间互换,不能只让同一个库自发自收。

负向用例用于画出边界:把合法 general 编码送给声称接受 general 的组件;把非最短形式送入 preferred-plus 门;改变映射插入顺序;跨越整数与大整数边界;比较确定与不确定长度;覆盖浮点宽度及 NaN。除了记录“解析成功”,还要保存得到的模型和随后走过的政策路径。

编码器测试不能证明解码范围,接收测试也不能证明输出确定。同一库的往返最容易掩盖问题,因为它会重复自己的假设。只有不同实现、不同语言和不同运行路径交换精确工件,才形成互操作证据。

NaN、大整数与流式需求暴露隐藏模型

preferred-plus 要求最短精确浮点表示,并使用规定的平凡 quiet NaN。但有些应用会保留 NaN 负载位,甚至赋予其业务含义。若这些位属于信息模型,就不能一面声称需要保留,一面无条件采用确定性集合;协议必须定义其他可移植的序列化契约。

若应用区分大整数与普通整数,或发送者在开始输出时尚不知道总长度,也存在同样问题。general 或特殊档案可能是正确选择。危险的不是例外,而是例外只藏在某个编码器选项里,没有成为端到端承诺。

最小初始规范所鼓励的是最小的共同协调面:独立重建处规定确定性;普通交换在合适时使用 preferred-plus;真正需要特殊表示的边界继续使用 general 或命名档案。没有必要为了固定一段签名输入而冻结整个数据生态。

缩小一个隐蔽信道,不等于消灭所有外泄

同一个值的多种合法表示可以携带解码后消失的信息。受控编码器可通过参数宽度、不确定长度分段或其他选择暗中传输比特。preferred-plus 与确定性规则减少这类自由,也使本应固定的输出变化更显眼。

但攻击者仍可选择语义上允许的值、时间戳和标签,也可利用其他层的填充、包长与时序。诚实的结论只能是:面对一组冻结的模型输入,这个构建产生了预期且唯一的字节。它不能证明系统没有任何隐蔽信道。

在敏感信任边界,可在隐私与留存控制下记录规范化模型和原始字节摘要。若承诺确定性的生产者对同一测试语料产生不同摘要,应检查版本、选项、输入和运行环境。异常是调查线索,不是自动的攻击判决。

能跨越库升级的证据链

完整收据应连接:协议与修订版;每个字节边界的序列化集合;CBOR 数据模型档案;允许的标签与键类型;重复键政策;编码器、解码器及其版本和选项;模式修订;原始输入;实际字节或可追溯摘要;密码学输入;密钥来源;验证结果;语义检查;政策决定;授权;执行尝试和观察效果。

收据还必须指出原始字节在哪里传输、在哪里重建。这张图通常会揭示真正故障:代理对签名负载解码再编码,缓存对解析对象求哈希,或两个服务在缺省字段上采用不同规则。

责任不应被一个“验证服务”吞并。协议作者负责字节边界;实现团队证明产生与接收的内容;安全团队证明解析器和密码学;应用所有者定义意义和等价;政策所有者决定权限;运维证明实际效果。一个有界事务标识与时间可以连接收据,却不应把它们压成同一个断言。

Lu Heng 的运行代码优先原则要求我们指出实际执行的库、版本和开关,而非只引用标准名称。现实层次原则阻止确定性字节借用模式的权威,也阻止有效签名借用业务授权。确定性 CBOR 解决的是一个精确问题:让独立编码器获得同一串字节。它不应被要求、也不应被允许,替其他层作证。

来源