DKIM 的 simple 与 relaxed 规范化怎么选?为什么邮件一被改动签名就失效?

1 DKIM 的 simple 与 relaxed 规范化怎么选?为什么邮件一被改动签名就失效?
规范化要解决什么问题

邮件在传输途中会被各环节做无害的格式调整——空白压缩、头字段重新折行、行尾字符转换等。若签名对这些改动零容忍,DKIM 在真实网络中几乎无法存活。

RFC 6376 第 3.4 节(Canonicalization)为此定义了规范化算法:在计算哈希之前,先把邮件按约定规则整理成统一形式,从而在「抵抗篡改」与「容忍无害改动」之间取得平衡。

规范化分别作用于头部与正文,两者可独立选择,通过 DKIM-Signaturec= 标签声明,写法为 c=头部算法/正文算法

头部的两种算法
  • simple(第 3.4.1 节,The "simple" Header Canonicalization Algorithm)不做任何修改,头字段必须原样保持。任何空白变化、大小写变化、折行方式变化都会导致验签失败。
  • relaxed(第 3.4.2 节,The "relaxed" Header Canonicalization Algorithm):做一系列容忍性处理,包括把头字段名转为小写、展开折行、把连续空白压缩为单个空格、去除字段值首尾多余空白等。

结论很明确:头部几乎总是应当选 relaxed。中转 MTA 对头字段做折行与空白调整极为普遍,simple 的失败率显著更高。

关于大小写转换的细节,RFC 8616 第 5 节补充说明:头字段名限于可打印 ASCII,因此 relaxed 的小写转换仍然是 ASCII 大小写转换,国际化邮件场景下这一点不变。

正文的两种算法
  • simple(第 3.4.3 节,The "simple" Body Canonicalization Algorithm):仅忽略正文末尾的空行,其余内容必须完全一致。
  • relaxed(第 3.4.4 节,The "relaxed" Body Canonicalization Algorithm):进一步忽略行尾空白、把行内连续空白压缩为单个空格,并忽略末尾空行。

选择建议:正文同样推荐 relaxed。正文在中转与格式转换中被调整空白的情况同样常见。

因此最常用、也最稳妥的配置是:

c=relaxed/relaxed
为什么 relaxed 也救不了「内容被真改」

必须清楚 relaxed 的容忍边界:它只容忍空白与折行层面的等价变换,不容忍任何实质内容变化。以下改动会必然导致签名失效:

  • 在正文中追加内容(如附加说明段落)。
  • 修改已被签名覆盖的头字段(如改写主题行)。
  • 转换内容编码,例如在 8 位与 7 位表示之间转换。
  • 重写 MIME 结构,如重新组装分段、替换边界串。

这正是邮件列表与部分安全网关会破坏 DKIM 的根本原因——它们做的是实质性内容修改,超出任何规范化算法的容忍范围。

签名侧可控的抗改动措施

除了选 relaxed,签名方还可以从「签什么」入手降低脆弱性:

  1. 收敛 h= 列表。h= 声明哪些头字段被纳入签名。只签必要的头字段(如发件人、主题、日期、消息标识等),不要把大量易被中转环节改写的头都签进去。签得越多,失效面越大。
  2. 避免签入本就不稳定的头字段。某些头在投递路径中本就会被增删,纳入签名等于自找失败。
  3. 不要依赖单一签名。关键路径上可考虑多重签名策略,提高至少一个签名存活的概率。
  4. 对确实会修改邮件的中介环节,靠 ARC 传递原始认证结论,而不是指望 DKIM 签名本身能挺过修改。
排障:如何判断是规范化问题还是内容修改
  1. 取到原始邮件与收到的邮件,逐字节比对被签名覆盖的头字段与正文。
  2. 若差异仅在空白、折行、行尾:属规范化范畴,检查 c= 是否用了 simple,改为 relaxed 通常即可解决。
  3. 若差异涉及新增内容、编码变化或 MIME 结构变化:属实质修改,规范化无解,需从链路设计上处理(减少修改环节,或引入 ARC)。
  4. 若正文哈希不匹配但头部正常:优先怀疑正文被追加或被重新编码。

参考:RFC 6376 DomainKeys Identified Mail (DKIM) Signatures