摘要

  • RFC 9741 的 .b64u-sloppy 和 .b64c-sloppy 放宽的是未使用位必须为零这一项检查,不是对任意坏格式的容忍;.json 约束解码后的值,也不保证唯一文本。
  • 同值文本只有在审批、缓存、额度或账务使用不同身份规则时,才可能形成业务分歧。规范本身并不证明存在绕过、重放或错误收费。
  • 应分别定义可接受表示、业务身份和密码学输入,按真实计费单位分配兼容成本,并保留足以重建争议的有界证据。

审批单上的标签没有改变,工作队列却换了一种拼写

设想一个内容引用服务:客户提交一段文本,接收团队验证它所表示的字节;审批团队在工单上记录原文本;资源团队按自己的键维护缓存与额度;财务再从其中一份记录出账。这是假设的集成场景,不是已确认的生产事故。

客户第一次提交 Zg,第二次提交 Zh。如果接收层按下文描述的宽松未用位规则检查,两者可以指向同一个字节。但工单上的两个字符串仍不相等。服务究竟看见了一份资源的两种标签、两次应收费的请求,还是同一个需要只执行一次的业务动作?解码器没有权限回答这个问题。

这个区别比“系统应该统一做去重”更重要。两次请求即使内容相同,也可能消耗两次接收与验证工作;一份资源即使来了两个标签,也未必需要存两份;一项审批即使对象相同,也可能只允许某个租户在某个目的下执行一次。将三件事一起叫作“重复”,会让技术层悄悄替业务层选择收费与授权规则。

RFC 9741 的价值在于,它让可接受文本与解码值之间的关系能够被明确描述。它没有把业务身份一并标准化。理解这一限制,才有可能判断兼容性省下的工作,最后落到了哪一个团队的账上。

一个低位就能改变标签,而不改变内容

Carsten Bormann 的 RFC 9741 于 2025 年 3 月作为 IETF 标准轨道 RFC 发布,为 CDDL 增加文本转换与处理控制操作符。标准状态不意味着任何特定校验器已经实现,也不证明任何部署通过测试。

它的第 2.1 节区分无填充的 Base64url 操作符 .b64u、.b64u-sloppy,以及带填充的经典 Base64 操作符 .b64c、.b64c-sloppy。这里的 sloppy 有精确范围:省去对额外未使用位为零的验证。字母表、填充等相关规则并没有因此一起消失。

用一个字节可以看清这个差别。Zg 的两个六位组拼成十二位,其中前八位为 01100110,即 0x66;末四位是零。Zh 的前八位仍相同,末四位变成 0001。这是一项手工位推导,并非本次运行某个 CDDL 实现所得的合规测试。对允许 0x66 的同一字节控制值,严格 .b64u 与 sloppy 版本的相关区别,正落在这四个未使用位上。经典带填充形式的对应例子是 Zg== 与 Zh==,不是把填充随意删掉。

RFC 4648 第 3.5 节要求合规编码器将填充位设为零,同时说明解码器拒绝不合规位模式的选择要看引用规范。本文这一单字节布局有四个可变化的未用位,因此能形成十六种同值拼写;这不意味着任意长度的标识符都固定有十六个别名。

严格入口可以要求一种规范拼写;兼容入口可以接受相关别名。两者都是需要明确选择的接口行为。把 sloppy 解释成“忽略空白、混合字母表或容忍截断”,则扩大了规范没有授予的范围。

JSON 把同一个问题带到更熟悉的业务表单

Base64 的未用位很小,JSON 的表示差异却容易出现在日常对接里。比如下面两段文本,在固定的小整数与字符串模型下拥有相同成员值:

{"account":"team-a","units":1}
{ "units": 1, "account": "team-a" }

这里没有重复成员,也没有改变数组序列或数值精度。变化只有对象成员序列和不显著空白。RFC 8259 定义这些 JSON 语法空间;RFC 7493 的 I-JSON 约束进一步排除重复成员等互操作隐患,并指出成员顺序不改变消息含义。

RFC 9741 第 2.4 节的 .json 描述文本解码后的值,采用 RFC 8949 第 6.2 节的默认 JSON 到 CBOR 转换。它不能借此限制不显著空白或映射成员的序列化次序。业务表单通过 .json,不等于工单编号、缓存键或账单索引获得了一种唯一字节表示。

也不能反过来认定“人看起来相同”就一定同值。大数、精度、重复键与应用自行使用的数字字符串模型,都需要另外审查。RFC 9741 要求应用约束反映在控制值中;转换不会替应用发明这些约束。本文刻意选取 1 与固定字符串,避免把数值转换的选择偷渡进例子。

规范本身也不鼓励一刀切宽松:.hex 与固定大小写的 .hexlc、.hexuc 区分不同表示选择,.base10 不接纳任意前导零。真正值得借鉴的不是“所有文本都先清理”,而是逐字段说明哪些差异可接受、接受之后如何使用。

同值不是重放证据,也不是免单理由

回到假设服务,只有在原文本键与解码值之间存在实际联接,别名才会影响审批或成本。如果缓存总是按已定义的资源身份索引,那么两个标签可能命中同一份内容。如果额度按原文本累计,那么它们可能得到不同计数。但这两句话都是条件推论,必须检查真实实现与合同,才能判断结果是预期行为还是缺陷。

按请求收费与按资源收费尤其不能混淆。接收两次相同内容也要做两次工作,合同可以据此收费;按独立资源计费的合同则可能要求表示别名不增加资源数。客户看到两张账单,并不能仅凭同值就证明收费错误。运营方说“字节相同所以没有额外成本”,也可能漏掉验证、排队、审计和争议处理的请求成本。

审批还带着内容之外的上下文。租户、目的、操作、版本和请求身份可以改变动作的授权范围。把同一字节的所有记录全局合并,可能把本来应分离的客户或用途混在一起。资源去重不能代替租户隔离;一次批准不能因为负载相同,就自然延伸成另一种操作的许可。

因此,要证明审批绕过,必须展示本应阻止动作的规则,以及别名如何改变了该规则的判断或联接。要证明错误计费,必须展示计费单位、重复归属与账务结果。要证明重放,则需要动作身份与状态转移证据。RFC 和两段示例只说明表示空间,不提供这些事故结论。

签名可能故意不承认你选定的“相同”

值相等、业务身份相等和签名输入相等,是三种可以不同的关系。普通 Base64url 编码的 JWS 就是一个边界鲜明的例子:RFC 7515 定义的签名输入使用编码后的受保护头与编码后的负载,中间由点连接。某段 JSON 解析后与另一段同值,不构成改写这些输入后仍保留原认证证据的理由。

如果应用确实选择对数据模型的规范表示签名,可以采用一个明确的方案。RFC 8785 的 JCS 是独立提交、信息类 RFC,为受其输入限制的数据提供确定表示;它不是 .json 的隐藏义务,也不是随处可插入的字符串修复器。本文不讨论未编码负载扩展,不能将普通 JWS 的规则推广到所有签名封装。

最危险的操作顺序是:先将原输入“整理成相同”,再用整理后的内容代替所选封装要求验证的原输入。这样做可能丢掉认证本来绑定的表示。资源层想消除别名,并不获得改写密码学层证据的权限。

可接受的共同层,应当比业务裁决薄

Lu Heng 关于最小共同规范的文章 提供了一个分析视角:把需要共同、确定且可独立验证的规则,与参与者的业务安排分开。这不是他对 RFC 9741 的审稿结论,也不是 IETF 对账务的要求。用在这里,接收层应说清可接受表示与值模型,业务层则明确自己的身份和计费单位,而不是让规范合规标签代替它们的决策。

Running-Code Primacy 的视角则提醒我们:规则的发布与规则在运行系统中的实现,是两件事。要评估本篇假设,应该沿接收、审批、资源与账务记录验证实际使用的键,而不是只看接口写着“支持 RFC 9741”。关于现实与符号层次的讨论 在此同样只是分析框架;一张通过校验的凭据并不等于一次业务动作的完整证据。

RFC 8610 第 5 节本已提醒系统不能仅凭 CDDL 规范与匹配器的正确性就省去其他防护。文本转换的确定性越清楚,越容易看出哪些选择还没有被决定。兼容性不是免费获得的宽容;它是一份需要有人承接的身份与证据工作。