错误信息
dkim=fail (body hash did not verify)
也可能输出为
- dkim=neutral (body hash did not verify)
mailauth 在严格模式下写出 `dkim=fail (body hash did not verify)`,在默认的宽松模式下写出 `dkim=neutral (body hash did not verify)`(两者均在 8.0.1 上复现)。OpenDKIM 把同样的情况报告为 `dkim=fail reason="signature verification failed"`(在 2.11.0 上复现)。
DKIM verifiers (Gmail, mailauth, Rspamd…) — Authentication-Results after the body changed in transit
含义
DKIM 签名中的 `bh=` 标签保存的是发件人签名时邮件正文的哈希。接收方对收到的正文(规范化之后)计算哈希,得到了不同的值,说明正文在传输途中被改动了。问题甚至不在头部签名上:RFC 6376 规定正文哈希不匹配属于永久性失败。典型的元凶是邮件列表的页脚、免责声明,以及会改写链接的安全网关。
原因
邮件被附加了页脚或免责声明
常见邮件列表(退订页脚)、出站网关添加的企业免责声明,或者中继添加的“Sent from…”行,都会改变规范化后的正文。
安全网关改写了正文
常见入站或出站过滤器所做的链接改写(Safe Links、URL defense)、附件剥离或横幅插入。
MIME 传输编码发生了变化
偶见不支持 8BITMIME 的中继会把 8 位内容转换为 quoted-printable 或 base64。解码后的文本相同,但签名所覆盖的字节已经不同。
签名者计算哈希时使用了不同的换行符
偶见如果签名代码对 LF 换行符计算哈希,而邮件发送时使用的是 CRLF(或者行尾空白随后被删除),产生的正文哈希在 `simple` 规范化下永远不会匹配。
修复方法
- 1.
自己重新计算 bh=
分别对发出的副本和收到的副本重新计算正文哈希,再比较两者,就能判断是签名者还是中继出了问题。
bash # Recompute bh= for relaxed body canonicalization and compare with the header's bh= value python3 - message.eml <<'EOF' import base64, hashlib, re, sys raw = open(sys.argv[1], "rb").read().replace(b"\r\n", b"\n") body = raw.split(b"\n\n", 1)[1] lines = [re.sub(rb"[ \t]+", b" ", l).rstrip(b" \t") for l in body.split(b"\n")] canon = b"\r\n".join(lines).rstrip(b"\r\n") + b"\r\n" print("computed bh=", base64.b64encode(hashlib.sha256(canon).digest()).decode()) EOF grep -o "bh=[^;]*" message.eml - 2.
在所有修改完成后再签名
把 DKIM 签名放在出站路径的最后一步;对于你无法控制的中间环节,尽可能依靠 ARC 以及通过 SPF 实现的 DMARC 对齐。
text # Postfix: run content filters (disclaimers, antivirus) BEFORE the DKIM signing milter smtpd_milters = inet:127.0.0.1:10025, inet:127.0.0.1:8891 # ^ disclaimer milter ^ OpenDKIM (signs last)
相关错误
dkim=fail reason="signature verification failed"OpenDKIM (Authentication-Results header added by the opendkim milter); mailauth reports the same as "bad signature"dkim=permerrorOpenDKIM (Authentication-Results); mailauth reports syntax errors as dkim=neutraldkim=fail reason="key not found in DNS"OpenDKIM (Authentication-Results); mailauth reports it as dkim=neutral (no key)