什么是 DNSSEC,它如何保证 DNS 安全
DNSSEC(DNS Security Extensions) 是 DNS 协议的安全扩展,通过数字签名验证 DNS 响应的真实性和完整性,防止缓存投毒、DNS 欺骗等中间人攻击。需要明确的是,DNSSEC 只提供数据认证,不加密 DNS 查询——这是它和 DoH/DoT 的本质区别。
为什么需要 DNSSEC
传统 DNS 的安全缺陷
DNS 协议设计于 1980 年代,天生没有认证机制。解析器收到一条 DNS 响应后,无法判断这条响应是否来自真正的权威服务器,还是攻击者伪造的。
shell用户查询 www.bank.com ↓ DNS 查询(明文,无认证) ↓ 攻击者在响应到达前注入伪造 IP ↓ 用户被引导至钓鱼站点
这种攻击之所以可行,是因为 DNS 响应只需要匹配查询的事务 ID(16 位,仅 65536 种可能),攻击者可以通过大量发送伪造响应来碰运气。2008 年的 Kaminsky 攻击把这个问题推到了极限——攻击者可以在数秒内投毒 DNS 缓存。
主要威胁类型:
- DNS 缓存投毒:向递归解析器的缓存中注入伪造记录,影响所有使用该解析器的用户
- 中间人攻击:在 DNS 查询传输过程中截获并篡改响应
- DNS 欺骗:伪造 DNS 响应,将用户导向恶意站点
DNSSEC 的解决思路
DNSSEC 不加密流量,而是在 DNS 数据上附加数字签名。解析器收到响应后,用预先建立的信任链验证签名——签名不通过就拒绝这条响应。
shell用户查询 www.bank.com ↓ 返回 A 记录 + RRSIG 签名 ↓ 用 DNSKEY 验证 RRSIG ↓ 验证失败 → 拒绝伪造响应(SERVFAIL) 验证通过 → 返回正确 IP
DNSSEC 的信任链
DNSSEC 的核心设计是一个从根域到目标域名的信任链(Chain of Trust),每一级为下一级做担保。
信任锚点
shell根密钥(Root Trust Anchor) ↓ DS 记录担保 .com TLD 的 DNSKEY ↓ DS 记录担保 example.com 的 DNSKEY ↓ 用 DNSKEY 验证 example.com 的 A/AAAA/MX 等记录
根密钥是全球信任的起点,由 ICANN 管理。根密钥的公钥被硬编码在支持 DNSSEC 的解析器中(称为 trust anchor),不需要在线获取。2010 年根域完成签名,意味着整条信任链有了可靠的起点。
密钥双层架构:KSK 与 ZSK
DNSSEC 采用双密钥设计,将密钥签名和数据签名解耦:
KSK(Key Signing Key):
- 仅用于签名 DNSKEY 记录集
- 密钥较长(通常 2048-4096 位),长期使用(1-2 年轮换)
- 变更时需要更新父域的 DS 记录,操作成本高
- 私钥应离线保存,理想情况下存储在 HSM 中
ZSK(Zone Signing Key):
- 用于签名区域内的所有其他记录(A、AAAA、MX 等)
- 密钥较短(通常 1024-2048 位),频繁轮换(每 30-90 天)
- 轮换不影响信任链,因为 KSK 没变
为什么分成两层?如果只用一把密钥,轮换时必须同时更新父域的 DS 记录,而 DS 记录的传播可能需要数小时甚至数天。双密钥设计让 ZSK 可以独立轮换,安全性和运维效率兼顾。
DNSSEC 记录类型详解
DNSKEY——存储公钥
dnsexample.com. 3600 IN DNSKEY 256 3 8 ( AwEAAbX8qU... ) ; Base64 编码的公钥
三个关键字段:
- Flags:256 = ZSK,257 = KSK。判断这是哪类密钥
- Protocol:固定为 3,表示 DNSSEC
- Algorithm:8 = RSA/SHA256,13 = ECDSA/P256,15 = Ed25519。算法号决定了验证时使用的具体算法
RRSIG——资源记录的签名
dnswww.example.com. 3600 IN RRSIG A 8 3 3600 ( 20240101000000 20240108000000 12345 example.com. oKx8j3... ) ; Base64 编码的签名
关键字段:
- Type Covered:被签名的记录类型(这里是 A 记录)
- Labels:域名层级数,用于通配符验证
- Original TTL:签名时记录的 TTL,防止 TTL 被篡改
- Signature Expiration / Inception:签名的有效时间窗口
- Key Tag:指向用于签名的 DNSKEY
- Signer's Name:签名者域名
- Signature:数字签名本身
解析器验证时,用 Key Tag 找到对应的 DNSKEY,用公钥解密签名,与记录的哈希值比对。
DS——信任链的桥梁
dnsexample.com. 3600 IN DS 12345 8 2 ( 2BB183AF5F22588179A53B0A98631FAD1A2DD3475 )
DS 记录存储在父域(.com)中,内容是子域(example.com)KSK 的哈希值。它告诉解析器:"如果子域的 DNSKEY 对应这个哈希,那就是可信的。"
DS 记录是信任链的关键环节——没有 DS 记录,信任链就断裂了,解析器无法从父域验证子域。
NSEC / NSEC3——证明不存在
dns; NSEC:直接列出相邻域名 www.example.com. 3600 IN NSEC a.example.com. A AAAA ; NSEC3:对域名做哈希后再排列 2t7b...gpq.example.com. 3600 IN NSEC3 1 0 10 ABCDEF ( 2v91...kjm A AAAA )
DNS 的正常响应只有两种:有记录或没有。DNSSEC 需要对"没有"也做认证,否则攻击者可以谎称某个域名不存在。
- NSEC:直接返回域名排序列表中的下一条记录。问题是暴露了区域内的域名列表(zone walking)
- NSEC3:对域名做哈希后再排序,攻击者无法直接遍历域名。额外支持 opt-out 机制,让大量未签名的委托不需要单独签名
DNSSEC 验证流程
一个完整的 DNSSEC 验证过程:
shell1. 递归解析器查询 www.example.com A 记录 ↓ 2. 权威服务器返回 A 记录 + RRSIG(A) ↓ 3. 解析器查询 example.com 的 DNSKEY ↓ 4. 返回 DNSKEY(KSK) + DNSKEY(ZSK) + RRSIG(DNSKEY) ↓ 5. 用 ZSK 验证 RRSIG(A) → A 记录可信 ↓ 6. 用 KSK 验证 RRSIG(DNSKEY) → ZSK 可信 ↓ 7. 查询 .com 的 DS 记录,获取 example.com KSK 的哈希 ↓ 8. 验证 KSK 的哈希与 DS 记录匹配 → KSK 可信 ↓ 9. 重复步骤 7-8,沿 .com → 根 逐级向上验证 ↓ 10. 到达根信任锚点(硬编码在解析器中) ↓ 所有验证通过 → 返回 A 记录给客户端(AD 标志位置 1) 任一环节失败 → 返回 SERVFAIL
注意第 10 步:解析器不需要在线查询根密钥,它已经在本地配置了根信任锚。这也是 DNSSEC 安全性的基础——只要根密钥不泄露,整条链就是可信的。
DNSSEC 的局限性与常见误解
DNSSEC 不做什么
- 不加密 DNS 查询:DNSSEC 只认证响应数据,查询和响应仍然是明文传输。想加密需要用 DoH(DNS over HTTPS)或 DoT(DNS over TLS)
- 不提供机密性:任何人都能看到你查询了什么域名
- 不防 DDoS:DNSSEC 实际上增大了响应体积,反而可能放大 DDoS 攻击效果
- 不防钓鱼:如果域名本身就是钓鱼域名(如 paypa1.com),DNSSEC 照样认证通过
DNSSEC vs DoH/DoT
| 维度 | DNSSEC | DoH/DoT |
|---|---|---|
| 目的 | 数据认证(防篡改) | 查询加密(防窃听) |
| 防护对象 | 响应内容的真实性 | 传输过程的机密性 |
| 是否互相替代 | 否 | 否 |
| 理想组合 | 两者配合使用 | 两者配合使用 |
两者解决的是不同问题:DNSSEC 保证"收到的数据没被改过",DoH/DoT 保证"别人看不到你查了什么"。生产环境中应该同时启用。
DNSSEC 部署实践
生成密钥对
bash# 生成 KSK(密钥签名密钥) dnssec-keygen -f KSK -a RSASHA256 -b 2048 -n ZONE example.com # 生成 ZSK(区域签名密钥) dnssec-keygen -a RSASHA256 -b 1024 -n ZONE example.com
现代实践建议使用 ECDSA(算法 13)或 Ed25519(算法 15)替代 RSA,密钥更短、签名更快、安全性相当。
签名区域文件
bash# 对区域文件签名 dnssec-signzone -A -3 $(head -c 1000 /dev/urandom | sha1sum | cut -b 1-16) -N INCREMENT -o example.com -t example.com.db
-3 参数启用 NSEC3 并指定盐值,-N INCREMENT 自动递增序列号。
上传 DS 记录
bash# 从 KSK 生成 DS 记录 dnssec-dsfromkey Kexample.com.+008+12345.key # 输出类似: # example.com. IN DS 12345 8 2 2BB183AF5F22588179A53B0A98631FAD1A2DD3475
将这条 DS 记录提交给域名注册商,注册商会将其推送到父域(.com)的区域文件中。DS 记录生效后,信任链才算建立完成。
配置递归解析器
bind; BIND named.conf options { dnssec-validation auto; ; 自动验证,使用内置根信任锚 };
bash# Unbound 配置 server: auto-trust-anchor-file: "/var/lib/unbound/root.key"
auto 模式下 BIND 会自动维护根信任锚,包括处理根密钥轮换(Root KSK Roll)。
密钥轮换注意事项
ZSK 轮换相对简单,发布新密钥、用新密钥签名、停止使用旧密钥即可。但要注意预发布(pre-publish)策略:先发布新 ZSK 但不立即用它签名,给解析器足够的缓存时间获取新密钥,再切换签名。
KSK 轮换则涉及 DS 记录的更新,流程更复杂:
- 生成新 KSK,加入 DNSKEY 记录集
- 用新旧 KSK 同时签名 DNSKEY 记录集
- 向注册商提交新 DS 记录
- 等待 DS 记录全球传播(TTL 到期)
- 移除旧 KSK
ICANN 在 2018 年进行了第一次根 KSK 轮换(KSK-2010 → KSK-2017),整个过程耗时数月,需要各解析器运营商配合更新信任锚。
DNSSEC 部署现状与排错
全球部署情况
| 层级 | DNSSEC 状态 |
|---|---|
| 根域 | 2010 年完成签名 |
| .com / .net / .org | 已签名 |
| .cn | 已签名 |
| 二级域名(如 example.com) | 参差不齐,大型网站覆盖率仍低 |
根域和主流 TLD 已全部支持 DNSSEC,但二级域名的部署率仍然偏低。根据 APNIC 的统计,全球 DNSSEC 验证率大约在 30% 左右,说明很多解析器虽然支持 DNSSEC,但并没有严格验证。
常见排错命令
bash# 查询 DNSSEC 记录 dig +dnssec www.example.com # 检查 AD 标志(Authenticated Data) dig +dnssec +adflag www.example.com # 追踪整条验证链 dig +dnssec +trace www.example.com # 在线可视化工具 # https://dnsviz.net/ # https://dnssec-debugger.verisignlabs.com/
常见故障模式:
- DS 记录与 DNSKEY 不匹配:通常发生在 KSK 轮换后忘记更新 DS 记录
- 签名过期:RRSIG 有有效期,忘记重新签名会导致验证失败
- NSEC3 参数不一致:签名时和查询时的 NSEC3 参数必须一致
面试常见问题
DNSSEC 能防止 DNS 劫持吗?
DNSSEC 可以防止传输过程中的 DNS 劫持(伪造响应),但无法防止以下情况:客户端本地 DNS 配置被篡改、攻击者控制了权威 DNS 服务器本身、本地 hosts 文件被修改。DNSSEC 认证的是"数据来源的真实性",不是"数据本身是否安全"。
追问:如果权威服务器被攻破,DNSSEC 还能保护吗?——不能。攻击者拿到私钥后可以签发合法签名。所以 DNSSEC 的安全前提是私钥安全,KSK 私钥应存储在 HSM 中。
DNSSEC 和 DoH 是什么关系?
两者互补,不替代。DNSSEC 认证数据的真实性(防篡改),DoH 加密查询传输(防窃听)。即使使用 DoH,如果不用 DNSSEC,递归解析器到权威服务器之间仍可能被投毒。反之,DNSSEC 不加密查询,ISP 仍能看到你查了什么。
KSK 和 ZSK 为什么要分开?
单密钥方案下,每次轮换密钥都必须更新父域的 DS 记录,而 DS 记录的传播可能需要数小时到数天,期间信任链可能断裂。双密钥设计让 ZSK 可以频繁轮换(30-90 天)而不影响 DS 记录,只有 KSK 轮换时才需要更新 DS,而 KSK 的轮换周期通常是 1-2 年。
DNSSEC 对 DNS 性能有什么影响?
三个方面:响应体积增大(RRSIG 和 DNSKEY 附加数据可能让响应从几十字节膨胀到上千字节)、额外查询(需要获取 DNSKEY 和 DS 记录)、签名验证的 CPU 开销。现代硬件上验证耗时通常在微秒级,真正的瓶颈是额外的网络往返。DNS 缓存可以缓解大部分开销。
追问:为什么 DNSSEC 响应容易触发 TCP 回退?——传统 DNS 的 UDP 包限制为 512 字节,DNSSEC 签名后经常超出这个限制。虽然 EDNS0 可以扩展 UDP 包大小(通常到 4096 字节),但部分网络设备会截断大包或丢弃 EDNS0 选项,导致回退到 TCP 重试。