第 06 章:DNSSEC 签名与验证
本章概述
DNSSEC(DNS Security Extensions)为 DNS 响应提供数据完整性和来源验证,防止缓存投毒和中间人攻击。本章讲解 DNSSEC 的原理、密钥生成、区域签名、DS 记录配置和故障排查。
6.1 DNSSEC 原理
6.1.1 DNSSEC 解决什么问题
传统 DNS 存在严重的安全缺陷:
问题场景(DNS 缓存投毒):
1. 客户端向递归服务器查询 evil.com
2. 攻击者抢先回复伪造的 IP 地址
3. 递归服务器缓存了伪造结果
4. 后续所有查询都被误导到恶意服务器
DNSSEC 通过数字签名确保:
- 数据完整性:响应未被篡改
- 来源验证:数据确实来自权威服务器
- 不存在性验证:可以证明某个域名不存在
6.1.2 DNSSEC 记录类型
| 记录类型 | 全称 | 用途 |
|---|---|---|
| RRSIG | Resource Record Signature | 资源记录的数字签名 |
| DNSKEY | DNS Key | 公钥,用于验证签名 |
| DS | Delegation Signer | 父区域存储的子区域密钥哈希 |
| NSEC | Next Secure | 证明域名不存在(按序遍历) |
| NSEC3 | Next Secure v3 | 证明域名不存在(哈希隐藏) |
6.1.3 信任链
根 (.) ← 根区 KSK(全球信任锚)
└─ DS for .com ← 根区存储的 .com DS
└─ .com ← .com 区的 KSK/ZSK
└─ DS for example.com ← .com 存储的 example.com DS
└─ example.com ← example.com 的 KSK/ZSK
└─ www.example.com ← 签名的 A 记录
递归服务器从根开始逐级验证,直到找到可信的 DNSKEY。
6.1.4 KSK vs ZSK
| 密钥类型 | 全称 | 用途 | 密钥长度 | 轮转周期 |
|---|---|---|---|---|
| KSK | Key Signing Key | 签名 DNSKEY 记录,用于父区域 DS | 2048 bit | 1-2 年 |
| ZSK | Zone Signing Key | 签名其他资源记录 | 1024 bit | 1-3 月 |
6.2 生成 DNSSEC 密钥
6.2.1 安装工具
# Ubuntu/Debian
sudo apt install -y bind9utils dnsutils
# RHEL/CentOS
sudo dnf install -y bind-utils
6.2.2 生成 ZSK(Zone Signing Key)
# 进入区域文件目录
cd /var/cache/bind/primary
# 或 RHEL: cd /var/named/primary
# 生成 ZSK(1024 位,推荐)
dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
# 输出示例:Kexample.com.+013+12345
# 生成两个文件:
# Kexample.com.+013+12345.key (公钥)
# Kexample.com.+013+12345.private(私钥)
6.2.3 生成 KSK(Key Signing Key)
# 生成 KSK(2048 位,推荐使用 ECDSA)
dnssec-keygen -a ECDSAP256SHA256 -n ZONE -f KSK example.com
# 输出示例:Kexample.com.+013+67890
6.2.4 推荐算法
| 算法编号 | 算法名称 | 密钥长度 | 说明 |
|---|---|---|---|
| 13 | ECDSAP256SHA256 | 256 bit (等效 128 位安全) | 推荐,性能好,广泛支持 |
| 14 | ECDSAP384SHA384 | 384 bit (等效 192 位安全) | 高安全需求 |
| 8 | RSASHA256 | 2048 bit | 传统,兼容性最好 |
| 15 | ED25519 | 256 bit | 新算法,BIND 9.14+ 支持 |
💡 推荐使用 ECDSA (算法 13):密钥短、签名快、安全性高。
6.2.5 设置密钥文件权限
# 密钥文件权限(非常重要!)
sudo chmod 600 Kexample.com.+013+*.private
sudo chmod 644 Kexample.com.+013+*.key
sudo chown bind:bind Kexample.com.+013+*
6.3 签名区域文件
6.3.1 自动签名(inline signing)
BIND 9.9+ 支持自动签名,无需手动操作:
// named.conf
zone "example.com" {
type primary;
file "primary/example.com.zone";
// 启用 DNSSEC 自动签名
inline-signing yes;
auto-dnssec maintain; // 自动维护密钥轮转
// 密钥目录
key-directory "primary/keys";
};
# 创建密钥目录
sudo mkdir -p /var/cache/bind/primary/keys
# 将密钥文件放入目录
sudo mv Kexample.com.* /var/cache/bind/primary/keys/
# 设置权限
sudo chown -R bind:bind /var/cache/bind/primary/keys
sudo chmod 700 /var/cache/bind/primary/keys
# 重载配置
sudo rndc reload example.com
# 验证签名状态
sudo rndc signing -list example.com
6.3.2 手动签名
# 1. 生成区域签名用的临时文件
cd /var/cache/bind/primary
# 2. 签名区域文件
dnssec-signzone -A -3 $(head -c 1024 /dev/urandom | sha1sum | cut -b 1-16) \
-N INCREMENT -o example.com -t \
-K keys/ \
example.com.zone
# 参数说明:
# -A 区域签名时包含所有记录
# -3 <salt> 使用 NSEC3(替代 NSEC,隐藏区域内容)
# -N INCREMENT 自动递增 Serial
# -o example.com 区域名称
# -t 显示统计信息
# -K keys/ 密钥文件目录
# 3. 输出文件
# example.com.zone.signed → 签名后的区域文件
# dsset-example.com. → DS 记录(需提交给父区域)
# keyset-example.com. → 密钥集
6.3.3 使用签名后的文件
// 修改 named.conf
zone "example.com" {
type primary;
file "primary/example.com.zone.signed"; // 使用签名文件
// 不需要手动更新,rndc reload 后 BIND 自动读取
};
6.4 DS 记录配置
6.4.1 什么是 DS 记录
DS(Delegation Signer)记录存储在父区域中,包含子区域 KSK 的哈希值,用于建立信任链。
6.4.2 提取 DS 记录
# 从签名输出中获取
cat dsset-example.com.
# 或者从 DNSKEY 记录生成
dnssec-dsfromkey -2 Kexample.com.+013+67890.key
# 输出示例:
# example.com. IN DS 12345 13 2 AABBCCDDEEFF...
6.4.3 在父区域中添加 DS 记录
方式一:通过域名注册商
登录域名注册商 → DNS 管理 → DS 记录 → 添加
算法:ECDSAP256SHA256 (13)
摘要类型:SHA-256 (2)
密钥标签:12345
摘要:AABBCCDDEEFF00112233...
方式二:在父区域文件中添加
; 父区域(.com)文件中添加
example.com. IN DS 12345 13 2 AABBCCDDEEFF00112233...
6.4.4 DS 记录字段说明
| 字段 | 含义 | 值 |
|---|---|---|
| Key Tag | 密钥标签(指纹) | 数值 |
| Algorithm | 算法 | 13 (ECDSAP256SHA256) |
| Digest Type | 摘要类型 | 1 (SHA-1) 或 2 (SHA-256) |
| Digest | 哈希值 | 十六进制字符串 |
⚠️ 重要:SHA-1(Digest Type 1)已不安全,强烈建议使用 SHA-256(Digest Type 2)。
6.5 DNSSEC 验证配置
6.5.1 递归服务器启用验证
options {
// 自动 DNSSEC 验证(推荐)
dnssec-validation auto;
// 信任锚文件
bindkeys-file "/etc/bind/bind.keys";
managed-keys-directory "/var/cache/bind/managed-keys";
};
6.5.2 手动指定信任锚
// 如果自动更新不可用,手动指定根区信任锚
options {
dnssec-validation yes;
};
// 根区 KSK 信任锚(需要定期更新)
trust-anchors {
. initial-key 257 3 8 "
AwEAAaz/tAm8yTn4Mfeh5eyI96WSVexTBAvkMgJzkKTO
iH7vDe3CRnJjTGMKRQFOxyzKhF6j8M0t7RpU6N7LCK8Y
d8D5lL9Lf7Gt5v2Lb27pZ5H8y8R5G3Zh2OF3Ei0CZ7pCR
... (完整密钥)
";
};
6.5.3 获取当前根区信任锚
# 从 IANA 获取根区信任锚
wget https://data.iana.org/root-anchors/root-anchors.xml
# 查看内容
cat root-anchors.xml
6.6 DNSSEC 签名状态检查
# 查看区域签名状态
sudo rndc signing -list example.com
# 预期输出示例:
# Done signing with key 12345/ECDSAP256SHA256
# Done signing with key 67890/ECDSAP256SHA256
# 使用 dig 验证签名
dig @127.0.0.1 example.com A +dnssec
# 预期输出:
# ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2
# ;; ANSWER SECTION:
# example.com. 3600 IN A 93.184.216.34
# example.com. 3600 IN RRSIG A 13 2 3600 (
# 20260610120000 20260510120000 12345 example.com.
# AABBCCDD...)
# 查看 DNSKEY
dig @127.0.0.1 example.com DNSKEY +dnssec +multiline
# 验证 DS 记录
dig @127.0.0.1 example.com DS
# 使用 delv 工具验证 DNSSEC 链
delv @127.0.0.1 example.com A +rtrace
6.6.1 dig 输出中的 DNSSEC 标志
| 标志 | 含义 |
|---|---|
aa | 权威答案(Authoritative Answer) |
ad | 已验证(Authenticated Data)= DNSSEC 验证通过 |
cd | 检查禁用(Checking Disabled)= 不验证 DNSSEC |
# 检查 ad 标志(DNSSEC 验证成功)
dig @127.0.0.1 example.com A | grep "flags:"
# ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1
# ^^ ad = 验证通过
# 检查未签名域名(无 ad 标志)
dig @127.0.0.1 unsigned-domain.com A | grep "flags:"
# ;; flags: qr rd ra; QUERY: 1, ANSWER: 1
# 没有 ad = 未签名
6.7 密钥轮转(Key Rollover)
6.7.1 ZSK 轮转(Pre-Publish)
# 1. 生成新 ZSK
cd /var/cache/bind/primary/keys
dnssec-keygen -a ECDSAP256SHA256 -n ZONE example.com
# 2. BIND 自动轮转(auto-dnssec maintain 模式下)
# BIND 会在适当时间激活新密钥、停用旧密钥
# 手动触发轮转
sudo rndc dnssec -status example.com
6.7.2 KSK 轮转(Double-DS)
# 1. 生成新 KSK
dnssec-keygen -a ECDSAP256SHA256 -n ZONE -f KSK example.com
# 2. 获取新 DS 记录
dnssec-dsfromkey -2 Kexample.com.+013+NEWKEYID.key
# 3. 向父区域添加新 DS(保留旧 DS)
# 4. 等待 DNS 缓存过期(通常 24-48 小时)
# 5. 从父区域删除旧 DS
# 6. 删除旧 KSK 密钥文件
6.7.3 密钥轮转时间线
ZSK 轮转(约 30 天周期):
Day 0: 新 ZSK 发布(pre-publish)
Day 7: 新 ZSK 激活,旧 ZSK 停用签名
Day 14: 旧 ZSK 移除(撤回)
Day 30: 完成
KSK 轮转(约 6 个月周期):
Day 0: 新 KSK 发布
Day 30: 新 KSK DS 提交给父区域
Day 60: 新 KSK 激活
Day 90: 旧 KSK DS 从父区域移除
Day 120: 旧 KSK 移除
6.8 NSEC3(带盐值的否定存在证明)
6.8.1 NSEC vs NSEC3
| 特性 | NSEC | NSEC3 |
|---|---|---|
| 区域遍历 | 可以遍历所有域名 | 不可遍历(哈希隐藏) |
| 安全性 | 区域内容可被枚举 | 防止枚举 |
| 性能 | 较好 | 稍差 |
| 推荐场景 | 小型内部区域 | 公网权威服务器 |
6.8.2 使用 NSEC3 签名
# 手动签名时使用 -3 参数
dnssec-signzone -A -3 $(head -c 1024 /dev/urandom | sha1sum | cut -b 1-16) \
-N INCREMENT -o example.com -t \
-K keys/ \
example.com.zone
6.9 DNSSEC 故障排查
6.9.1 常见错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
SERVFAIL + ad 标志缺失 | DNSSEC 验证失败 | 检查签名是否过期 |
RRSIG expired | 签名已过期 | 重新签名 |
no valid RRSIG | 签名数据损坏 | 重新签名 |
DS not found | 父区域缺少 DS 记录 | 向注册商添加 DS |
DNSKEY not found | 区域缺少 DNSKEY | 生成并发布密钥 |
6.9.2 诊断命令
# 1. 检查区域签名状态
sudo rndc signing -list example.com
# 2. 使用 delv 验证
delv @127.0.0.1 example.com A +rtrace
# 3. 在线验证工具
# https://dnsviz.net/d/example.com/dnssec/
# https://dnssec-analyzer.verisignlabs.com/example.com
# 4. 检查 RRSIG 过期时间
dig @127.0.0.1 example.com RRSIG +dnssec | grep "RRSIG"
# 查看最后一行的过期时间
# 5. 检查密钥状态
sudo rndc dnssec -status example.com
6.10 完整 DNSSEC 配置示例
// /etc/bind/named.conf —— 带 DNSSEC 的权威服务器
options {
directory "/var/cache/bind";
listen-on port 53 { any; };
listen-on-v6 port 53 { any; };
recursion no;
allow-query { any; };
// DNSSEC 验证(权威服务器通常不需要)
dnssec-validation no;
pid-file "/run/named/named.pid";
};
zone "example.com" {
type primary;
file "primary/example.com.zone";
// 自动 DNSSEC 签名
inline-signing yes;
auto-dnssec maintain;
key-directory "primary/keys";
// 允许区域传输
allow-transfer { key transfer-key; };
};
6.11 本章小结
| 概念 | 要点 |
|---|---|
| DNSSEC | 为 DNS 提供数据完整性和来源验证 |
| ZSK | 签名区域记录,短密钥,频繁轮转 |
| KSK | 签名 DNSKEY,长密钥,不频繁轮转 |
| DS 记录 | 父区域存储的子区域密钥哈希,建立信任链 |
| RRSIG | 资源记录的数字签名 |
| inline-signing | BIND 自动签名,推荐方式 |
| auto-dnssec | BIND 自动维护密钥轮转 |
💡 小技巧
- 使用 ECDSA 算法 13:密钥短、签名快、安全性高。
- inline-signing + auto-dnssec 是生产环境推荐方案。
- 密钥文件权限必须 600:私钥泄露 = 安全全毁。
- 定期检查签名过期时间:避免签名过期导致解析失败。
- KSK 轮转前先提交新 DS:否则会中断域名解析。