摘要

  • Want-Content-Digest 与 Want-Repr-Digest 表达的是算法偏好,不是完成协商后的硬性承诺。收到偏好的一方可以忽略它、选择列表之外的算法,或者不返回摘要;这些情况本身都不构成 HTTP 协议错误。
  • 完整性链路至少有四个彼此独立的状态:发出了什么偏好、实际收到了什么字段、该字段究竟覆盖消息内容还是选定表示,以及接收方计算后的本地处置结果。把四者压成一个“已启用摘要”会制造虚假的确定性。
  • 摘要只能帮助发现所覆盖字节是否不一致。它不能证明发送者身份,不能授权操作,不能提供保密性,也不会自动保护方法、URI、状态码和其他元数据。

报出偏好,不等于交换承诺

一家服务在采购表里勾选“支持内容摘要”,并不意味着每个响应都带有可用、可接受且已经验证的摘要。真正的差别藏在一个很容易被忽略的词里:偏好。

RFC9530 定义了两个偏好字段。Want-Content-Digest 表示希望对方为当前 HTTP 消息的内容提供哪些摘要算法;Want-Repr-Digest 则针对选定表示。两个字段都采用结构化字段字典:键是算法名称,整数是相对偏好。1 表示最低偏好,10 表示最高偏好,0 表示不可接受。

这个整数不是安全评分,也不是验证成功率。将某算法标为 10,既不会让它“更完整”,也不会把对端锁进义务。收到偏好的一方可以不理会该字段,可以返回偏好表中没有列出的算法,也可以完全省略 Content-Digest 或 Repr-Digest。RFC9530 明确说明,这些选择不会仅因未满足偏好而自动变成协议错误。若业务要求“没有指定算法就必须失败”,那是应用合同新增的约束,而不是 Want-* 字段自身产生的效力。

响应中的 Want-Content-Digest 与 Want-Repr-Digest 还有另一层容易误读的含义:它们是在告诉客户端,服务器希望以后的请求带上什么摘要,而不是证明当前响应已经被计算。偏好字段负责表达意愿,证据字段负责承载结果;两个角色不能互换。

RFC9530 附录 C 给出了更接近运营现场的分支:服务器可能使用较低偏好的可支持算法,也可能发现所有候选都不支持,或者按照应用规则返回某种 4xx、5xx。标准没有为“没有满足偏好”指定唯一状态码和统一问题格式。需要确定行为的系统,应在自己的接口约定里说明并测试,而不能靠接收者猜测。

四本账,不能合并成一个开关

很多中间件只暴露“digest=true”,监控也只统计“校验成功”。这种单一状态省事,却无法回答事故调查最基本的问题。

第一本账是请求偏好:发的是哪个 Want-* 字段,列了哪些算法,顺序和权重如何。第二本账是实际到达的证据:收到的是 Content-Digest 还是 Repr-Digest,包含哪些成员,位于头部还是尾部。第三本账是覆盖对象:计算针对本次消息内容,还是 HTTP 语义下的完整选定表示。第四本账才是本地结论:接收方选择了哪个可接受成员,计算值是否一致,以及业务决定继续、隔离、重试还是拒绝。

只有后三本账同时清楚,才有资格描述接收到的完整性证据。即使如此,措辞仍需精确。“客户端偏好 SHA-512”不等于“服务器返回 SHA-512”;“响应含有 sha-256”不等于“接收方完成了 sha-256 计算”;“摘要一致”如果没有字段类型和覆盖对象,仍是不完整结论。

这种拆分也能防止兼容策略被粉饰为强保证。接收方偏好一种算法却接受了另一种,可能完全符合本地政策,但应当留下“采用较低偏好算法”的事实。没有摘要而业务继续,也可能是合理选择;正确记录应是“在本地缺省政策下接受缺失”,而不是把空值计成验证成功。

消息内容与选定表示不是同一个对象

RFC3230 使用的 Digest/实例摘要概念留下了对象边界的歧义。RFC9530 以两个名字拆开:Content-Digest 覆盖这条 HTTP 消息实际携带的内容,Repr-Digest 覆盖与消息相关的完整选定表示。这个差异会改变接收方到底要对哪些字节做计算。

消息内容属于一次具体的请求或响应;选定表示则由 HTTP 语义、内容协商和表示元数据共同确定。部分传输会让两者差别格外明显,但内容编码变化、资源存在多个表示时,同样不能含糊处理。

因此,接收方不能随手拿磁盘上的某个文件计算后就宣称验证了 RFC9530 字段。Content-Type、Content-Encoding 会影响表示及其解释;同一语义资源在不同编码下可以产生不同字节序列。状态变更方法也要求仔细区分:PATCH 请求里的表示可能是补丁文档,响应里的表示可能是更新后资源的选定表示。Content-Location 与 Location 也不能被当作可随意替换的身份指针。

内部平台常用“payload checksum”一栏试图包办一切,结果是上下游各自对不同材料计算,却都显示绿色。更可靠的做法是保留字段类型、表示元数据、捕获字节的处理阶段,以及是否经过内容编码转换。完整性不是一个脱离语义的哈希字符串,而是一条对特定对象作出的可复核陈述。

支持算法与接受算法应当分开

当前 IANA HTTP Digest Algorithm Values 注册表把 SHA-512 和 SHA-256 列为 Active;MD5、SHA、两种 UNIX sum、Adler-32 与 CRC32C 等旧项处于 Deprecated。RFC9530 允许弃用算法在某些非对抗性的偶然损坏检测中继续出现,但规定其不得用于存在攻击者或数字签名的情境。

注册表状态只是共同起点,不会替应用完成威胁建模。接收方可以忽略任意或全部摘要成员,也可以限制愿意计算的算法和成员数量。这样做既有密码学原因,也有资源原因:让远端无限选择算法并要求对大对象反复计算,会无谓扩大 CPU 和内存带宽的消耗面;而把所有语法上认识的算法一概接受,则会让最弱算法决定整体保证的下限。

算法敏捷并不能自动阻止降级或替换。一个响应可以同时带有强算法和弃用算法。如果组件甲只要最容易计算的成员一致就算通过,组件乙却以为系统遵循了最高偏好,两者将对同一次传输作出不同的安全陈述。日志不应只写“digest valid”,而应写明实际采用的算法、字段、覆盖对象和比对结果。

工程上应区分“能解析/能计算”与“政策允许接受”两张列表,并限制单次计算数量。还要预先决定:一个可接受成员一致是否足够;只有未知或弃用成员时怎么办;多个成员相互矛盾时谁优先;未返回字段是否可以继续。这些都是参与者自己的风险选择,无须为每次请求建立一个中央摘要审批机关。

尾部到达之前,证据还没有完成

Content-Digest 与 Repr-Digest 都可以位于头部或尾部。发送者需要边传输边计算时,尾部很实用;但它也带来时间边界。中间设备可能丢掉 trailer,应用也可能在尾部到达前就消费数据并触发不可逆动作。

如果接收方在校验尾部之前已经提交状态变更、释放文件或转发内容,就不能事后把先前动作描述成“经过完整性门控”。校验仍可用于审计或发现传输损坏,但发生顺序必须如实记录。基础设施吞掉尾部时,结论是“证据缺失”,不是某个不可见组件已经替应用完成验证。

存储同样有边界。一次摘要一致,只说明验证时点上覆盖的字节相符。除非完整内容被持久化并再次核验,它不会持续证明磁盘内容未变。内容编码、解码和后续转换还可能生成多个有效但不同的字节序列。完整性回执因此应注明处理阶段,不能只留算法名。

摘要不包办 HTTP 的其余安全属性

RFC9530 刻意把能力边界写得很窄。摘要字段不覆盖整条消息,不会自动绑定请求方法、目标 URI、响应状态码或全部表示元数据;它不认证提供字段的主体,不决定操作是否获授权,也不隐藏内容。

RFC9421 的 HTTP Message Signatures 可以把摘要字段纳入签名,但签名者必须明确覆盖该字段以及需要绑定的表示元数据。把签名和摘要并排放在消息里,并不会自然形成关联。签名本身还需要密钥、身份、时间和防重放政策。摘要不一致可以说明计算字节与字段值不同,却不能单独证明是谁改了数据、是否出于恶意或操作原本是否允许。

这并非功能不足,而是清晰分工。摘要负责可互操作的字节完整性证据;传输保护、身份、授权、签名和存储控制根据真实威胁组合。只有当一个小机制被包装成完整安全结论时,治理风险才会出现。

资料与证据边界

本文依据公开标准讨论治理含义。文中的交易与部署情景均为假设,不代表已测得的采用率、事故数量、攻击频率、处理成本或普遍安全阈值。