SPF、DKIM、DMARC 到底各管什么
三条记录,三件不同的事。SPF 和 DKIM 各自验的是什么,为什么对齐才是 DMARC 真正的机关,以及一封信怎么会 SPF 通过却 DMARC 失败。
有人转给你一张截图:一封催款邮件,发件人写着你公司的名字,付款链接却不是你们的。你翻发信服务器日志,什么都没发出去过。这封信根本没碰过你的基础设施——对方只是在发件人栏里敲了你的域名,就跟在信封上随手写个回信地址一样。
SPF、DKIM、DMARC 要堵的就是这个洞。它们总被并排念出来,像是同一件事的三个版本,而这个印象恰恰是混乱的源头。三者既不重复也不能互相替代:每一个回答的问题都不一样,而且只有第三个管的才是收件人真正看到的那个地址。
先说没人告诉你的那两个「发件人」
记录本身不难懂,难的是它们底下那层收发机制——几乎所有让人费解的 DMARC 结果都能追到这里:一封邮件有两个发件人地址,而且它们不必一致。
- 信封发件人,也叫
MAIL FROM,投递之后落成Return-Path头。这是 SMTP 层的东西:发信服务器在握手时报出的地址,退信往这儿回。收件人根本看不到它。 - From 头,邮件客户端里显示的那个。这是人读到、并且据此产生信任的地址。
两者是各自独立的字段。你通过营销平台正常群发的一封信,信封发件人通常在 bounces.mailer-provider.com,From 头写的却是 yourcompany.com——完全正常,不是伪造。但这个缝隙也正是攻击者的全部机会所在:在自己控制的域名上通过认证,显示一个不属于自己的域名。
把这个区别记住。SPF 单独拦不住伪造是因为它,DMARC 不得不发明「对齐」也是因为它。
SPF:哪些服务器可以替这个域名发信
SPF(发件人策略框架,RFC 7208)在 DNS 里公布一份允许替某域名发信的 IP 清单,形式是一条 TXT 记录:
v=spf1 include:_spf.google.com include:sendgrid.net ip4:198.51.100.25 -all
从左往右读:来自 Google Workspace 地址段的信通过,来自 SendGrid 地址段的通过,来自那台指定服务器的通过,最后的 -all 说其余一律失败。收信方拿连接过来的 IP 逐项比对,给出一个结果。
结尾那个机制的分量比它的长度大得多:
| 结尾 | 名称 | 含义 |
|---|---|---|
-all | fail | 未授权。严格设置,也是本来该用的那个 |
~all | softfail | 未授权,但仍接收并做标记。过渡期设置 |
?all | neutral | 不表态,跟没发布记录差别不大 |
+all | pass | 谁都算授权。不要发布这个 |
实际用起来有三条限制要留神。第一,SPF 有十次 DNS 查询的预算上限(RFC 7208 §4.6.4)。include、a、mx、ptr、exists 和 redirect 各消耗一次,而 include 是递归消耗的——四五家 SaaS 服务商就能悄悄把你顶出上限。超过十次,求值直接返回 PermError,在 DMARC 眼里等同 SPF 失败。记录本身看上去一点毛病没有,只是对某些发信路径不再工作了。
第二条更根本:SPF 验的是信封发件人,不是 From 头。攻击者注册一个一次性域名,给它配一条完全合法的 SPF 记录,从自己服务器发出去,SPF 照样通过——而 From 头写的是你公司。SPF 没做错任何事,它的职责本来就不包括保护读者看到的那个地址。
第三条是个实践中的别扭事:SPF 遇到转发就会断。收件人设了自动转发,转发服务器用的是它自己的 IP,这个 IP 不在你的记录里,SPF 于是失败——不怪你。
想看自己的预算被展开成什么样,包括 include 套 include 消耗掉的那些,把域名丢进 SPF/DMARC 检测工具,它会把整棵树走一遍,把每一个消耗查询的项都数出来。这个总数要当上限读,不是判决:求值在第一个命中的机制处就停了,所以超过十次意味着「路径走到第十次之后的那些发信方会挂」,而不是「你发的每一封都挂」。
DKIM:一个跟着信走的签名
DKIM(域名密钥识别邮件,RFC 6376)把密码学签名附在邮件本身上。发信服务器用私钥签一组头部加正文,加上一个 DKIM-Signature: 头;公钥放在 DNS 里,收信方取回来验证。
签名头自己就写明了去哪儿找钥匙:
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=selector1;
h=from:to:subject:date; bh=...; b=...
d= 是签名域名,s= 是 selector。两者拼出 DNS 名 selector1._domainkey.example.com,公钥就在那儿。把钥匙位置拆出一个 selector,好处是同一个域名可以同时挂多把钥匙——每家服务商一把,或者轮换期间新旧各一把。
因为证据是装在信里走的,DKIM 扛得住中转:一封签好的信转发出去,只要内容没动,签名照样验得过。反过来说就是——DKIM 怕改动。邮件列表往正文尾巴上贴一段声明、或者改写主题,就把经手的那个签名弄失效了。
读签名时有两处细节值得知道。h= 列的是到底签了哪些头,一个没覆盖 From 的签名基本等于没有——攻击者可以在不惊动它的前提下把可见发件人换掉。还有 l=,它给正文设一个长度上限,只签前 N 个字节,超出部分追加的内容既没签也没人察觉。
审一个域名时还有一件事必须先知道,否则容易得出反的结论:DNS 没有任何办法列出一个域名发布了哪些 selector。你可以查一个已知的 selector,但你没法把它们枚举出来。任何能报告 DKIM 状况的工具,要么在探测常见名字,要么在问你要——所以「没找到密钥」的意思永远是「我试的这些名字都没命中」,而不是「这个域名没配 DKIM」。
DMARC:把前两者拴到可见的发件人上
到这儿前两者才真正变得有用。SPF 验的是信封,DKIM 验的是一个签名域名,这两个都不要求跟 From 头有任何关系——而 From 头是收件人唯一会读的那部分。
DMARC(RFC 9989)用一条叫「对齐」的规则把这个缺口补上:SPF 或 DKIM 的通过只有在它验的那个域名与 From 头里的域名相符时才算数。
一封信通过 DMARC 的条件是下面任意一条成立:
- SPF 通过,并且信封发件人域名与 From 域名对齐;或者
- DKIM 通过,并且签名的
d=域名与 From 域名对齐。
有一条就够——这也正是 SPF 因转发而断掉之后,光靠 DKIM 还能让信继续通过的原因。
现在回头看开头那次攻击,它散架了。攻击者那个一次性域名 SPF 仍然通过,但那个域名跟 From 头不对齐,所以对 DMARC 毫无贡献。一条对齐的通过都没有,DMARC 判失败,接下来由你公布的策略决定收信方怎么处置。
对齐分两档,用 aspf 和 adkim 分别设置:
- 宽松(
r,默认):组织域相符即可,mail.example.com与example.com算对齐。 - 严格(
s):必须完全一致,mail.example.com与example.com不算对齐。
绝大多数情况宽松是对的默认值。严格值得有意识地选:它排掉了一整类基于子域的滥用,但凡是「被认证的域名是 From 头域名的子域」这种形态的信件都会挂——而多数 SaaS 发信恰好就长这个样子。
组织域怎么算,这一版改了。 RFC 9989 改用 DNS tree walk——逐级查询更短的名字看有没有策略记录——来确定组织域,而不再像 RFC 7489 那样查 Public Suffix List。对一个普通的
company.com两种算法结果一样,所以多数域名什么都感觉不到。它影响的是边缘情况:多段后缀,以及在命名空间的好几层上都发布了策略的组织。
策略本身是 _dmarc.yourdomain.com 上的一条 TXT 记录:
v=DMARC1; p=reject; sp=reject; np=reject; adkim=r; aspf=r;
rua=mailto:dmarc-reports@yourdomain.com
| 标签 | 作用 |
|---|---|
p | 未对齐的信怎么处理:none、quarantine 或 reject |
sp | 已存在子域的策略,不写则回落到 p |
np | DNS 里不存在的子域的策略,不写则回落到 sp,再回落到 p |
adkim / aspf | 对齐档位,r 或 s,默认 r |
t | 测试模式。t=y 请求收信方不要直接套用已公布的策略;预期会低一档 |
fo | 哪些失败要生成报告:0、1、d、s |
rua | 聚合报告发到哪儿 |
ruf | 单封失败报告发到哪儿 |
np 是最值得专门加上的一个。攻击者很爱编造你从没建过的子域——billing.yourcompany.com 在 DNS 里没有任何记录,于是你真实的邮件配置管不到它。np=reject 一句话堵死这条路,同时完全不影响你在用的那些子域。
t 则是最容易被读错的一个。t=y 不是「忽略我的策略」,它请求收信方不要直接套用已公布的策略。RFC 9989 预期会低一档:reject 变成 quarantine,quarantine 变成 none;但最终怎样处置仍由收信方决定。这是一个上线用的刹车,因此带着 t=y 的 p=reject 在标签被移除前,实际请求的是隔离。
如果你是 2026 年之前学的 DMARC,回头对一下现在的规范。 DMARC 在 2026 年 5 月完成修订,发布为 RFC 9989(协议本体)、RFC 9990(聚合报告)、RFC 9991(失败报告)三份文档,一并废止 RFC 7489,并首次把 DMARC 提升为标准轨协议。这次修订移除了
pct、rf、ri三个标签——pct原本承担的事情交给了t——并把p从必需降为推荐。p缺失,或p、sp、np有无效值时,只有记录中带有效rua才会按p=none继续;否则收信方不做 DMARC 处理。已有的v=DMARC1记录不受影响:收信方是忽略这些被移除的标签,而不是拒绝整条记录。下次改 DNS 时顺手清掉即可。
串起来看:一封信是怎么被判的
对一封进来的信,收信方大致跑这么一遍:
- 读 From 头的域名,后面所有判断都以它为基准。
- 对信封发件人的域名跑 SPF,用连接过来的 IP 比对。记下通过与否,以及验的是哪个域名。
- 验所有 DKIM 签名,记下通过与否和每个签名的
d=。 - 判对齐:SPF 验的那个域名跟 From 域名相符吗?某个通过的
d=跟它相符吗? - 有任意一条对齐的通过 → DMARC 通过。域名使用已通过认证,但正常投递和过滤规则仍照收信方的规则走。
- 一条都没有 → DMARC 失败。查发信方请求的
p(或sp/np)策略;若t=y,预期会低一档,但最终处置仍由收信方决定。
意外都出在第 4 步,它也解释了那个让人以为撞见 bug 的结果:spf=pass 和 dmarc=fail 同时出现并不矛盾。这恰恰是「信从某家服务商发出去、但从没配过对齐」的典型特征——信封发件人是服务商的退信域名,SPF 在那个域名上通过,而没有任何东西把它和你的 From 头拴在一起。修法是在那家服务商上把 d=yourdomain.com 的 DKIM 签名配起来,那正是对齐这半边检查在找的东西。
怎么看自己的配置
有两个方向要查,需要的证据不一样。
对外——你公布了什么。 三条记录都是公开 DNS,谁都能读,你自己当然也能。把域名过一遍 SPF/DMARC 检测工具:它按十次预算展开 SPF 树,逐个标签读 DMARC 记录,验证外部报告地址是否已经授权你往那儿发,并探测常见的 DKIM selector。想看不加解释的原始记录,用 DNS 查询工具 直接查 yourdomain.com 和 _dmarc.yourdomain.com 的 TXT。
对内——某一封信实际经历了什么。 公布的记录告诉你规则,不告诉你对某封具体邮件的判决。判决在邮件头里,收信服务器把它查到的结果写在那儿:
Authentication-Results: mx.example.net;
spf=pass smtp.mailfrom=bounces.provider.com;
dkim=pass header.d=yourcompany.com;
dmarc=pass header.from=yourcompany.com
把这几行读透,整个模型就在一处摊开了:SPF 在服务商的域名上通过,DKIM 在你的域名上通过,而因为 DKIM 的 d= 与 header.from 对齐,DMARC 通过。把任意一封信存成原始邮件源码,把头部丢进邮件头分析工具就能得到这样一份拆解——它完全在你浏览器里跑,邮件不会离开你的机器。完整的读法,包括 Received 链和伪造发件人的那几处破绽,见怎么读邮件头。
一句话各自记住
- SPF 回答「这个 IP 能替这个域名发信吗」——验的是信封发件人,收件人看不见,十次 DNS 查询封顶,转发即断。
- DKIM 回答「这封信是被该域名公布过的密钥签过的吗」——跟着信走,扛转发,怕改动,而且 selector 无法枚举。
- DMARC 回答「SPF 或 DKIM 的那次通过,属不属于读者真正看到的那个域名」——这就是对齐,三者里只有它保护 From 头。它同时还告诉收信方失败了该怎么办,并且给你寄报告。
三个都需要,而且需要它们对齐。没有 DMARC 的 SPF 和 DKIM,认证的是没人看的地址;而没有一条通过且对齐的 SPF 或 DKIM 撑着,DMARC 拒的就是你自己的信。
各块拼图归位之后,下一个问题就变成操作层面的了:怎么从一条什么都不做的策略走到 p=reject,中间还不至于把自己的账单邮件一起送进黑洞。那是上线节奏的问题而不是协议的问题,从 p=none 走到 p=reject 讲的就是那套分阶段的做法。