bigywater.tech
网络协议 · 71

DNSSEC:签名链与验证

DNS 本来没有任何认证:谁抢先回应,谁说了算。DNSSEC 给每条记录加上数字签名,再用“父区担保子区的密钥”的方式串成一条一直通到根的信任链。这篇不依赖任何解析器,自己从根的信任锚开始把 example.com 的链一环一环验完,再故意篡改看验证怎么失败,最后看有多少常见名字真的签了名。

8 环
从根信任锚到 example.com 的 A 记录,自己验证通过的环节数
4 / 12
随手选的 12 个常见名字中已启用 DNSSEC 的个数
50.0 小时
example.com 的 A 记录 RRSIG 的有效期长度

AD 与 RRSIG

实测 · 本机执行输出
  标志 QR RD RA AD;响应 179 字节(不带 DO 时 61 字节);回答区:2 条 A + 1 条 RRSIG
  RRSIG 的字段:覆盖的类型 A;算法 13(ECDSAP256SHA256);标签数 2;原始 TTL 300;签名过期 2026-10-16 08:28 UTC;签名开始 2026-10-14 06:28 UTC;密钥标签 34505;签名者 example.com.;签名本身 64 字节
  这条签名的有效期长度 50.0 小时;距离过期还剩 25.0 小时
  不带 DO 位时:标志 QR RD RA(没有 AD),72 字节,没有 RRSIG

带 DO 位的查询会多返回 RRSIG,验证过的解析器置 AD 位。响应从 72 字节变成 179 字节。

信任链

实测 · 本机执行输出
  根的 DNSKEY:3 个(flags [<Flag.ZONE: 256>, <Flag.SEP|ZONE: 257>, <Flag.SEP|ZONE: 257>];256 = 区签名密钥 ZSK,257 = 密钥签名密钥 KSK);签名者 [38696]
  内置的信任锚(IANA 公布的根 KSK 的 DS 摘要,密钥标签 20326 和 38696)与响应里的 KSK 匹配:[20326, 38696]
  ✓ 根的 DNSKEY 记录集被某个 KSK 签名,签名验证通过(用信任锚确认过的那个 KSK)
  ✓ com. 的 DS 记录(在父区 . 里,由父区的密钥签名)验证通过;DS 内容:密钥标签 [19718],算法 [<Algorithm.ECDSAP256SHA256: 13>],摘要类型 [<DSDigest.SHA256: 2>]
  ✓ com. 的 DNSKEY 里有一个 KSK(密钥标签 [19718])的哈希等于父区 DS 里的摘要
  ✓ com. 的 DNSKEY 记录集(3 个密钥)的签名验证通过
  ✓ example.com. 的 DS 记录(在父区 com. 里,由父区的密钥签名)验证通过;DS 内容:密钥标签 [2371],算法 [<Algorithm.ECDSAP256SHA256: 13>],摘要类型 [<DSDigest.SHA256: 2>]
  ✓ example.com. 的 DNSKEY 里有一个 KSK(密钥标签 [2371])的哈希等于父区 DS 里的摘要
  ✓ example.com. 的 DNSKEY 记录集(4 个密钥)的签名验证通过
  ✓ example.com 的 A 记录集的 RRSIG 验证通过(用 example.com 的 ZSK)
  整条链全部通过:根(信任锚)→ com. 的 DS → com. 的密钥 → example.com. 的 DS → example.com. 的密钥 → A 记录

根 KSK 的 DS 摘要是信任锚;根签名 com. 的 DS,com. 签名 example.com 的 DS,example.com 的密钥签名 A 记录。每一环都验证通过。

篡改与过期

实测 · 本机执行输出
  把 104.20.23.154 改成 104.20.23.155 → 验证失败:ValidationFailure
  把签名中的 1 个比特翻转 → 验证失败:ValidationFailure
  把验证时间设到签名过期后 1 小时 → 验证失败:ValidationFailure(签名有有效期,过期的签名即使没被改过也无效)

改地址、翻转一个签名比特、签名过期,验证都失败。

验证失败的域名

实测 · 本机执行输出
  1.1.1.1:默认 → RCODE SERVFAIL,回答 0 条,标志 QR RD RA,扩展错误(EDE) [(<EDECode.DNSKEY_MISSING: 9>, 'no SEP matching the DS found for dnssec-failed.org.')];加 CD → RCODE NOERROR,回答 1 条 A,标志 QR RD RA CD
  8.8.8.8:默认 → RCODE SERVFAIL,回答 0 条,标志 QR RD RA,扩展错误(EDE) [(<EDECode.DNSKEY_MISSING: 9>, 'No DNSKEY matches DS RRs of dnssec-failed.org')];加 CD → RCODE NOERROR,回答 1 条 A,标志 QR RD RA CD

dnssec-failed.org 在验证型解析器上 SERVFAIL 并带 EDE 9;加 CD 位才能拿到数据。

部署现状

实测 · 本机执行输出
    名字              RRSIG   AD   DS
    example.com      有      是    有
    cloudflare.com   有      是    有
    github.com       无      否    无
    google.com       无      否    无
    wikipedia.org    无      否    无
    ietf.org         有      是    有
    iana.org         有      是    有
    python.org       无      否    无
    amazon.com       无      否    无
    microsoft.com    无      否    无
    apple.com        无      否    无
    kernel.org       无      否    无
  已签名(有 DS)的名字 4 / 12

随手选的 12 个常见名字里,只有 4 个有 DS;没签名的域名得不到保护。

分步演示

DNSSEC
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。向 1.1.1.1 / 8.8.8.8 取 DNSSEC 记录,用 dnspython 的 dns.dnssec 在本机验证;信任锚的摘要是我按 IANA 公布的数值写进代码的,只用来确认响应里的 KSK;有效期剩余时间随运行时刻不同;第 5 节的 12 个名字是随手挑的
    • 没有验证:NSEC / NSEC3(不存在性证明,即“证明这个名字不存在”);密钥轮转(KSK / ZSK roll-over)与 RFC 5011 自动更新信任锚;DS 摘要类型 / 算法降级;验证型解析器如何在被伪造的应答面前表现(第 67 篇看到的伪造应答对应的名字没有签名,因此这里无法演示);DANE / TLSA;在没有 DS 的名字上 AD 位的条件
    京ICP备2026067502号-1京公网安备11010502063487号