摘要

  • RFC 6376 允许 l= 指定进入正文哈希的规范化字节数;超出该界限的每一个字节都不受该份 DKIM 签名验证,即使结果为成功。
  • h= 另外规定按顺序覆盖哪些头字段实例。正文全部覆盖,也不代表 Subject、Content-Type 或其他可见头字段自然都在签名内。
  • 可复验的证据必须保留原始邮件、所选签名、规范化方法、头字段实例映射、前缀与尾部长度和哈希、当时观测到的 DNS 密钥、验证器,以及可信 Authentication-Results 的管理边界。

先问绿色结果检查到了哪里

假设取证记录只有一行:dkim=pass。它看上去像结论。再补两项数据,含义立刻收窄:规范化正文共 19,300 字节,签名中 l=14,000。

验证器实际检查的是开头 14,000 个规范化字节。其余 5,300 个字节没有“验证失败”,而是根本没有进入这份签名的正文哈希。它可能是一段正常的邮件列表退订说明,也可能是归档网关添加的标记、改变呈现方式的 MIME 延续,或试图夺走读者注意力的附加内容。pass 无法单独判断来源和意图。

这是 RFC 6376 明文定义的范围,不是实现漏洞。没有 l= 时,默认覆盖完整的规范化正文;出现 l= 时,数值表示从正文起点算起、有多少规范化字节进入哈希。规范还明确指出,界限以后的数据不受 DKIM 验证。l=0 是极端情形:正文完全未签名。

接收方可以制定更严格的本地策略,例如拒绝含有未覆盖尾部的邮件,或把部分覆盖视为永久失败。但策略决定不应改写密码学事实。一份清楚的记录要分成两列:签名在声明范围内是否验证成功;本地系统如何处置这个范围。

Murray Kucherawy 与这条边界的关系也应这样精确表述。RFC 6376 共同署名 Dave Crocker、Tony Hansen 和 Murray S. Kucherawy,不能把 DKIM 说成任何一人的独自发明。现行 Authentication-Results 规范 RFC 8601 则署名 Kucherawy 为作者。2026年9月2日抓取时,他的 IETF 官方档案表格列出34份 RFC,而同页第一人称简介仍写33份。即使是权威页面,字段和时间也不能省略。

l= 数的是规范化结果,不是原始文件

把 .eml 文件的字节数直接减去 l=,通常会得到错误的尾部长度。DKIM 先按 c= 声明的正文规则进行规范化,再在规范化结果上计数。simple 与 relaxed 对行尾、空白和末尾空行的处理不同,原始文件中的位置不一定等于密码学边界。

复验过程需要固定顺序:保存原始邮件;从可能存在的多份 DKIM-Signature 中选定一份;读取它的 a=、c=、d=、s=、h=、l= 与 bh=;按指定算法规范化正文;测量规范化长度;若有 l=,截取准确的起始前缀;计算正文哈希;再用当时观测到的公钥验证头部签名。

最直观的范围算式是:

未覆盖尾部 = 规范化正文总字节数 - l

同时保存完整规范化正文、已签名前缀、未签名尾部三份哈希。尾部长度为正,只证明签名范围没有走到正文末端;它不证明尾部恶意。若没有 l=,则记录“完整正文范围”,不要捏造一个不存在的标签值。

密钥也有观测时刻。d= 给出签名域,s= 选择 DNS 记录。后来再查询时,密钥可能已轮换、撤销或更换。要重现当时的判断,必须保存当时返回的密钥材料、查询时间、解析器结果和错误;只有实际测过 DNSSEC,才能把其状态写入证据。

正常追加与恶意插入共用同一空间

l= 的设计理由很现实。邮件列表经常在正文末尾添加退订方法或列表标识。如果原始签名覆盖到最后一字节,这类追加会破坏签名。限制在正文前缀,可以让接收方确认原始部分没有变化,再由本地策略决定是否接受额外内容。

问题在于,密码学边界不能识别追加者的动机。RFC 6376 的安全章节警告,恶意中间方也能利用这块空间。附加内容可以改变 MIME 结构,借助宽松 HTML 解析,甚至让收件人视觉上只看到后来加入的部分;它还可能绕过重复邮件检测。

因此有两种对称误判。把所有尾部都定性为攻击,会冤枉正常邮件列表;把签名前缀的完整性借给尾部,又会制造不存在的保证。正确做法是分别记录两段内容,识别中间方,核对其声明的转换规则,重建 MIME 树,并观察客户端实际选中了哪个部分。

Heng Lu 强调 running code,正适合这里。RFC 给出规范化和验证语法;只有实际邮件、验证器版本与呈现轨迹,才能说明某个系统算了哪些字节、读者看到了哪些字节。仅引用标准或展示徽章,不能替代运行时回执。

h= 在头字段上再画一条边界

即使正文全部签名,头字段也不是自动全覆盖。h= 是有顺序的头字段名称列表。若同名字段出现多次,DKIM 会按规则从底部向上选择相应实例。把它压成“已签名字段集合”,会丢掉决定实际覆盖对象的顺序。

RFC 6376 要求 From 必须在范围内,并强烈建议覆盖 Date、Subject、Reply-To、Sender 以及影响呈现或处理的 MIME 字段。使用 l= 时,Content-Type 尤其重要:如果它未签名,替换该字段可能让同一段字节以完全不同的方式呈现。

审计记录应列出全部原始头字段及顺序,逐项标记这份签名实际选中的实例,并指出与显示相关却留在范围外的字段。邮件还可能同时带有发信域、邮件列表和后续网关的不同签名;每份签名都要有自己的覆盖图,不能合并成一个模糊的“通过”。

签名域的通过不是对内容世界的认证

DKIM 通过的准确含义是:一份选定的签名,使用某个域和 selector 下观测到的密钥,对它选择的材料验证成功。它不证明可见 From 中的人亲自写了邮件,不证明密钥使用经过内部授权,也不证明正文真实、附件安全、邮件会被投递或读者会看到什么。

RFC 5585 把结论限定为已签消息或已签部分在签名后未被改变,且明确不把有效性等同于信任。RFC 5863 要求评估者只把范围内的内容视为已认证,并直接以 l= 说明局部范围。RFC 8301 更新算法与密钥要求;合格的强密钥是独立条件,不会把短前缀延伸到正文末尾。

这也与 DMARC 文章的证据对象不同。DMARC 检查 SPF 或 DKIM 身份是否与可见 From 域对齐,并向接收方提供策略信号;它不会扩大 l= 和 h= 的覆盖。一个对齐的 DKIM 签名仍可能留下未签名尾部,完整 DKIM 签名也不必然带来 DMARC 通过。

密码学有效、身份对齐、签名域信誉、内容安全、投递策略、最终呈现,应当是不同列。把它们压成“认证邮件”,相当于让一个组件替其他所有组件背书。

Authentication-Results 自己并不会自动可信

许多下游过滤器不再执行 DKIM,而是读取上游验证器写入的 Authentication-Results。RFC 8601 定义了这种传递,但该字段通常没有自身完整性保护。它的证据价值来自信任边界。

authserv-id 标识产生结果的认证服务。消费方需要明确配置哪些标识可信;邮件进入管理域时,还要移除或遮蔽声称由域内产生、实则来自外部的伪造字段。任何发件人都能在邮件里输入 dkim=pass 字样,只有接收架构证明谁写入它时,它才成为本地测量。

因此要保存 Authentication-Results 原文、所在位置、生产者、authserv-id、建立信任的配置,以及它对应的准确 DKIM-Signature 实例。如果网关把三份签名压成一个没有限定对象的 pass,下游就无法恢复 d=、s=、h= 与 l= 的关系。

方法结果和处置结果仍需分开。有的验证器在密码学上判定成功,却因部分覆盖而拒收;有的会接收并提示。记录两者的理由,比把后续每个动作都归因于 pass 更可靠。

建立范围账本,而不是认证贴纸

最小回执从内容寻址的原始邮件开始,按原顺序保存每个 DKIM-Signature。每份签名记录 d=、s=、可选 i=、a=、c=、h=、l=、bh=、签名值,以及存在时的签发和过期时间。

随后保存 DNS 密钥与观测时间、可复现的规范化版本、实际解析到的头字段实例、完整规范化正文长度、前缀和尾部长度及三份哈希、验证器软件与版本、结果和原因。Authentication-Results 的生产者和管理边界放在旁边,不能藏进密码学结果。

最后才是呈现层:MIME 树、客户端选择的正文部分、渲染器版本,以及显示内容是否用了 l= 之后的字节。隐私保护不要求永久存储用户屏幕,可以保留部分标识、渲染哈希与版本,让关键连接仍可复验。

Heng Lu 的 agency 原则要求把每项权力还给真正的行动者:标准作者定义语法,密钥保管方选择范围,中间方追加或改写,验证器计算,管理域担保结果通道,客户端呈现,读者判断含义。任何一方都无权把自己的结论扩展到整条链。

严谨的结论应该写成:第几份签名使用当时观测到的密钥,验证了哪些按顺序选择的头字段和多少规范化正文前缀;哪段尾部没有进入验证;哪个可信验证器记录了结果;哪个客户端显示了哪些部分;至于人类作者、内容安全与意图,仍需独立证据。

来源