RFC 6377: DKIM 和邮件列表
ELI5: 当你将一封带有 DKIM 签名的电子邮件发送到邮件列表时,列表软件通常会更改你的邮件——添加页脚、修改主题、改写发件人地址。这些更改会破坏你的 DKIM 签名,就像打开防篡改密封一样。RFC 6377 记录了这个问题,并向列表运营者和发件人提供了关于如何最小化损害的建议。
为什么存在这个问题
DKIM(RFC 6376)使用密码学哈希对电子邮件头和正文内容进行签名。签名后的任何修改都会使签名失效。邮件列表在设计上会在传输过程中修改邮件。这造成了根本性的矛盾:
- DKIM 希望邮件完全按照签名时的样子送达。
- 邮件列表 添加页脚、在主题前加"[列表名]"、添加 List-* 头、重写 From 地址、并在摘要中包装邮件。
当 DMARC 出现时(要求 DKIM 或 SPF 对齐),这种矛盾成为了可交付性危机。来自带有 p=reject DMARC 策略域的邮件发送到邮件列表时,开始对所有列表订阅者退件。RFC 6377 发布用来记录该问题并推荐缓解策略。
工作原理
邮件列表对邮件的修改
典型的邮件列表管理器(Mailman、Sympa、LISTSERV、Google Groups)可能执行以下任意组合的修改:
| 修改 | 破坏 DKIM? | 详情 |
|---|---|---|
| 添加 List-* 头 | 仅当 h= 覆盖它们时 |
List-Unsubscribe、List-Id、List-Post 等 |
| 添加/修改主题前缀 | 是,如果主题已签名 | 例如 [列表名] 原始主题
|
| 添加正文页脚 | 是 | 附加取消订阅说明或免责声明 |
| 重写 From 头 | 是 | 将 From 改为列表地址(DMARC 缓解) |
| 改变 Reply-To | 仅当 Reply-To 已签名时 | 将 Reply-To 设置为列表地址 |
| 改变信封发件人 | 否(DKIM 签名头,不签名信封) | 改变 MAIL FROM 用于退件处理 |
| MIME 重构 | 是 | 包装在 multipart 中以添加文本页脚 |
签名验证流程
; 步骤 1:作者向列表发送 DKIM 签名邮件 From: alice@example.com To: dev-list@lists.example.org Subject: Proposed API change DKIM-Signature: d=example.com; h=from:to:subject; ... ; 步骤 2:列表软件修改邮件 From: alice@example.com ← 未改变(或已重写) To: dev-list@lists.example.org Subject: [dev] Proposed API change ← 已修改:添加了前缀 List-Id: <dev-list.lists.example.org> ← 已添加 DKIM-Signature: d=example.com; h=from:to:subject; ... ; 原始正文 + 附加页脚 The body text here... -- To unsubscribe, visit https://lists.example.org/unsub ← 已添加 ; 步骤 3:收件人服务器验证 DKIM ; 结果:失败(主题和正文在签名后被修改)
列表运营者的推荐做法
RFC 6377 推荐邮件列表最小化修改以保留 DKIM 签名:
-
不要修改主题头。 避免加前缀
[列表名]。改用List-Id头 — 邮件客户端可以基于它进行筛选。 - 不要添加正文页脚。 如果需要页脚内容,使用 MIME 封装,或将页脚作为单独的 MIME 部分而不是追加到现有正文。
-
添加新的 DKIM 签名。 列表应使用自己的 DKIM 密钥(
d=lists.example.org)对修改后的邮件进行签名。这不会修复原始签名,但提供了来自列表域的有效签名。 -
为 DMARC 考虑 From 重写。 当原始发件人的域发布
p=reject时,将 From 头重写为列表域,并将原始发件人放在 Reply-To 中。这被广泛实现为"From 修改"。
发件人的推荐做法
-
使用宽松规范化。 使用
c=relaxed/relaxed而非c=simple/simple进行签名。宽松规范化容许某些列表软件引入的轻微空白变化。 -
仅签名必要的头。
h=标记应覆盖你关心的头(From、Subject、Date、To、Message-ID),但不包括列表常见添加的头(List-Id、List-Unsubscribe)。 -
谨慎使用
l=正文长度标记。l=标记限制正文中有多少被签名,所以附加页脚不会破坏签名。但这有安全影响 — 攻击者可以在签名部分下方附加恶意内容。
关键技术细节
DMARC 的复杂性
RFC 6377 早于 DMARC(RFC 7489),但它描述的问题在 DMARC 部署后变得更加严重。使用 DMARC p=reject 时:
; 作者的域发布严格的 DMARC _dmarc.example.com TXT "v=DMARC1; p=reject; ..." ; 邮件通过邮件列表,DKIM 破坏 ; 收件人服务器检查 DMARC: ; - SPF:失败(信封发件人是列表,不是 example.com) ; - DKIM:失败(签名被列表修改破坏) ; - DMARC:失败 → 拒绝 ; 结果:邮件对所有列表订阅者退件
这就是为什么开发了 ARC(RFC 8617) — 在邮件列表等中介修改前保留身份验证结果。
ARC 作为现代解决方案
经过验证的接收链(ARC)协议让邮件列表等中介在修改邮件前记录原始身份验证结果。收件人的服务器之后可以使用 ARC 链恢复原始 DKIM/SPF 通过,即使列表已破坏原始签名。
From 重写策略
邮件列表最常见的 DMARC 缓解是 From 重写:
; 原始邮件 From: Alice Smith <alice@strict-dmarc.com> ; 列表 From 重写后 From: Alice Smith via Dev List <dev-list@lists.example.org> Reply-To: Alice Smith <alice@strict-dmarc.com>
这在显示文本和 Reply-To 中保留原始发件人身份,同时使 From 域与列表的 DKIM 签名匹配。
常见错误
-
使用
c=simple/simple规范化。 简单规范化在即使琐碎的空白变化上也会失败。如果你的邮件可能通过邮件列表或其他中介,始终使用c=relaxed/relaxed。 -
签名过多头。 在 DKIM
h=标记中包含List-Id或List-Unsubscribe等头保证在列表添加这些头时失败。仅签名你生成的头。 -
发布
p=rejectDMARC 而不考虑列表。 如果你的用户发送到邮件列表,严格的 DMARC 拒绝策略会导致他们的列表邮件对所有订阅者退件。考虑p=quarantine或确保用户理解后果。 - 列表运营者不添加自己的 DKIM 签名。 修改邮件后,列表应使用自己的密钥对其进行签名。否则,邮件到达时没有有效的 DKIM 签名。
-
仅依赖
l=正文长度。 虽然它可以保留通过附加页脚的签名,但许多最佳实践指南已弃用l=,因为它允许在签名边界下方注入内容。 - 不实施 ARC。 现代列表软件应实施 ARC 以保留原始身份验证链。没有 ARC,收件人无法知道邮件最初是否经过身份验证。
可交付性影响
-
列表邮件上的破坏 DKIM 导致退件。 使用 DMARC
p=reject时,破坏的 DKIM 意味着被拒绝的邮件。列表订阅者停止接收邮件,列表运营者被退件淹没。 - From 重写保留可交付性但改变发件人身份。 收件人在 From 中看到列表地址,不是原始发件人。这可能让用户困惑,但是确保送达的最可靠方式。
- ARC 改进列表邮件的送达。 主要提供商(Gmail、Microsoft、Yahoo)尊重 ARC 链。来自有信誉列表的有效 ARC 印章可以覆盖破坏的原始 DKIM 签名。
- 列表信誉重要。 接收服务器评估列表域的信誉。维护良好、发送实践规范、实施正确 DKIM 签名和 ARC 的列表会比运营不善的列表看到更好的送达率。
- 监控来自列表流量的退件率。 如果你看到发送到邮件列表的邮件退件率飙升,检查列表是否以破坏 DKIM 签名的方式修改邮件,以及他们是否实施 ARC。