本文转自:https://guozeyu.com/?p=2519

续之前的域名解析系统详解——基础篇,DNSSEC 是一组使域名解析系统(DNS)更加安全的解决方案。1993 年,IETF 展开了一个关于如何使 DNS 更加可信的公开讨论,最终决定了一个 DNS 的扩展——DNSSEC,并于 2005 年正式发布。然而,实际推行 DNSSEC 是一件非常难的事情,本文将讨论一下现有 DNS 系统所存在的一些不安全性,以及 DNSSEC 是如何解决这些问题的。

传统 DNS 的问题

上一篇文章中已经知道,在你访问一个网站,比如 www.example.com 时,浏览器发送一个 DNS 消息到一个 DNS 缓存服务器上去查询,由于 DNS 系统的庞大,这中间还需要经过好几层 DNS 缓存服务器。想要正确访问到这个网站,就需要这所有的缓存服务器都要正确的响应。

DNS 的中间人攻击

DNS 查询是明文传输的,也就是说中间人可以在传输的过程中对其更改,甚至是去自动判断不同的域名然后去做特殊处理。即使是使用其他的 DNS 缓存服务器,如 Google 的 8.8.8.8,中间人也可以直接截获 IP 包去伪造响应内容。我可以给大家演示一下被中间人攻击之后是什么个情况:

$ dig +short @4.0.0.0 facebook.com
243.185.187.39

向一个没有指向任何服务器的 IP 地址:4.0.0.0 发送一个 DNS 请求,应该得不到任何响应。可是实际上却返回了一个结果,很明显数据包在传输过程中“被做了手脚”。所以如果没有中间人攻击,效果是这样的:

$ dig +short @4.0.0.0 facebook.com
;; connection timed out; no servers could be reached

DNS 系统就是这么脆弱,和其他任何互联网服务一样,网络服务提供商、路由器管理员等均可以充当“中间人”的角色,来对客户端与服务器之间传送的数据包进行收集,甚至替换修改,从而导致客户端得到了不正确的信息。然而,通过一定的加密手段,可以防止中间人看到在互联网上传输的数据内容,或者可以知道原始的数据数据是否被中间人修改。

从密码学开始

讲到 DNSSEC,就不得不讲到一些密码学的知识。这里从最基本的密码学开始讲起。
密码学主要分为三大类,这里也列出每一列常用的加密算法:

  • 对称密码学:AES、DES
  • 公钥密码学:RSA、ECC
  • 数据完整性的算法:SHA、MD5

在 DNSSEC 中,主要使用到的是公钥密码学和数据完整性算法两种加密学。

公钥密码学实现数字签名

公钥密码主要是与对称密码进行区分:对称密码的加密与解密使用相同的密钥;而公钥密码使用的加密密钥叫做公钥,解密密钥叫做私钥——两种密钥相对独立,不能替代对方的位置,而且知道公钥无法推出私钥。这两种密码学都必须是可逆的(所以解密算法可以看作加密算法的逆)。以函数的形式表达的话如下:

  1. 对称密码
  • 密文 = 加密算法(密钥, 原文)
  • 原文 = 解密算法(密钥, 密文)
  1. 公钥密码
  • 密文 = 加密算法(公钥, 原文)

  • 原文 = 解密算法(私钥, 密文)
    当然,如果私钥充当公钥,公钥充当私钥,那么就是这样的:

  • 密文 = 加密算法(私钥, 原文)

  • 原文 = 解密算法(公钥, 密文)

假如服务器要向客户端发送一段消息,服务器有私钥,客户端有公钥。服务器使用私钥对文本进行加密,然后传送给客户端,客户端使用公钥对其解密。由于只有服务器有私钥,所以只有服务器可以加密文本,因此加密后的文本可以认证是谁发的,并且能保证数据完整性,这样加密就相当于给记录增加了数字签名。但是需要注意的是,由于公钥是公开的,所以数据只是不能被篡改,但可以被监听。

此处的服务器如果是充当 DNS 服务器,那么就能给 DNS 服务带来这个特性,然而一个问题就出现了,如何传输公钥?如果公钥是使用明文传输,那么攻击者可以直接将公钥换成自己的,然后对数据篡改。

所以,一个解决的方法是使用一个被公认的公钥服务器,客户端的操作系统中在本地先存好这个公钥服务器自身的公钥。当与服务器通信时,客户端从这个被公认的公钥服务器通信,用户使用操作系统中内置的公钥来解密获得服务器的公钥,然后再与服务器通信。

然而 DNS 是一个庞大的系统,在这个系统中根域名服务器充当了被公认的公钥服务器,其中每一个一级域名服务器也是一个子公钥服务器。这就是 DNSSEC 的基本雏形了。

数据完整性算法,减轻公钥密码的运算压力

在密码学中,还存在一种检查数据完整性的算法,其 “加密” 无须密钥,密文不可逆(或很难求逆),而且密文与原文不是一一对应的关系。而且,通过此算法算出的密文通常是一个固定长度的内容。通过此算法算出的密文叫做哈希值。在 DNSSEC 里所运用到它的特性是:原文一旦修改,密文就会发生变化。

公钥密码学存在的一个很重要的问题:加密和解密的速度相对于对称密码太慢了。所以想要提高性能,就需要减短需要加密和解密的文本。如果只是对文本的哈希值加密,由于长度的减短,加密速度就能大大提高。

在服务器传送时,同时传送明文的文本和使用私钥加密的文本哈希值;客户端只需要先算出收到的明文文本的哈希值,然后再用公钥解密密文,验证两个值是否相等,依然能够防止篡改。

在 DNSSEC 中就运用了这种方法,无论是对密钥还是记录的加密。

DNSSEC

DNSSEC 这一个扩展可以为 DNS 记录添加验证信息,于是缓存服务器和客户端上的软件能够验证所获得的数据,可以得到 DNS 结果是真是假的结论。上一篇文章讲到过 DNS 解析是从根域名服务器开始,再一级一级向下解析,这与 DNSSEC 的信任链完全相同。所以部署 DNSSEC 也必须从根域名服务器开始。本文也就从根域名服务器讲起。

与 HTTPS 的区别

DNSSEC 和 HTTPS 是两个完全不同的东西,但是这里只是对其加密方式对比。即 DNSSEC 的加密方式与 TLS 进行对比。

信任链机制的不同

在配置 DNSSEC 的时候,如果与 HTTPS 比较,可以看出来:证书和私钥全部都是在自己的服务器上直接生成的,也就意味着这是 “自签名的”,不需要任何 “根证书颁发商”。二级域名所有者向一级域名注册商提交自己的公钥的哈希值,然后一级域名注册商就会给你的哈希值进行签名,从而也能形成一道信任链,远比 HTTPS 的信任链简单,操作系统也再不用内置那么多个 CA 证书,只需要一个根域名的 DS 记录即可。个人认为这是一个更先进的模式,但是它需要客户端一级一级的去依次解析,于是受到了速度的影响;HTTPS 则是直接由一个服务器返回整条证书链,与服务器进行 HTTPS 的连接时只需要与一个服务器通信。不过,DNS 记录是可以被缓存的,所以能够一定程度上的减少 DNSSEC 的延迟。

只签名,不加密

你发往 DNS 服务器的请求是明文的,DNS 服务器返回的请求是带签名的明文。这样 DNSSEC 只是保证了 DNS 不可被篡改,但是可以被监听,所以 DNS 不适合传输敏感信息,然而实际上的 DNS 记录几乎都不是敏感信息。HTTPS 的话会同时签名和双向加密,这样就能够传输敏感信息。
DNSSEC 的只签名,不加密主要是因为 DNSSEC 是 DNS 的一个子集,使用的是同一个端口,所以是为了兼容 DNS 而作出的东西,而 DNS 是不需要客户端与服务器建立连接的,只是客户端向服务器发一个请求,服务器再向客户端返回结果这么简单,所以 DNS 都可以使用 UDP 来传输,不需要 TCP 的握手,速度非常快。HTTPS 不是 HTTP 的子集,所以它使用的是另一个端口,为了做到加密,需要先与浏览器协商密钥,这之间进行了好几次的握手,延迟也上去了。

在哪里验证?

刚才所讲述的所有情况,都是在没有 DNS 缓存服务器的情况下。如果有 DNS 缓存服务器呢?
实际上,一些 DNS 缓存服务器就已经完成了 DNSSEC 验证,即使客户端不支持。在缓存服务器上验证失败,就直接不返回解析结果。在缓存服务器进行 DNSSEC 验证,几乎不会增加多少延迟。
但这也存在问题,如果缓存服务器到客户端之间的线路不安全呢?所以最安全的方法是在客户端上也进行一次验证,但这就会增加延迟了。

DNSSEC 的时效性和缓存

DNSSEC 相比 HTTPS 的一个特性就是 DNSSEC 是可以被缓存的,而且即使是缓存了也能验证信息的真实性,任何中间人也无法篡改。然而,既然能够缓存,就应该规定一个缓存的时长,并且这个时长也是无法篡改的。

签名是有时效性的,这样客户端才能够知道自己获得到的是最新的记录,而不是以前的记录。假如没有时效性,你的域名解析到的 IP 从 A 换到了 B,在更换之前任何人都可以轻易拿到 A 的签名。攻击者可以将 A 的签名保存下来,当你更换了 IP 后,攻击者可以继续篡改响应的 IP 为 A,并继续使用原本 A 的签名,客户端也不会察觉,这并不是所期望的。

然而在实际的 RRSIG 签名中,会包含一个时间戳(并非 UNIX 时间戳,而是一个便于阅读的时间戳),比如 20170215044626,就代表着 UTC 2017-02-15 04:46:26,这个时间戳是指这个记录的失效时间,这意味着在这个时间之后,这个签名就是无效的了。时间戳会被加进加密内容中去参与签名的计算,这样攻击者就无法更改这个时间戳。由于时间戳的存在,就限制了一个 DNS 响应可以被缓存的时长(时长就是失效时间戳减去当前时间戳)。然而,在有 DNSSEC 之前,控制缓存时长是由 TTL 决定的,所以为了确保兼容性,这两个时长应该是完全一样的。

通过这样做,即能够兼容现有的 DNS 协议,有能够保证安全,还能够利用缓存资源加快客户端的请求速度,是一个比较完美的解决方案。

DNSSEC 的实际部署

其实,即使完全不了解,或者没看懂上面的内容,也不影响你去部署 DNSSEC,只不过你应当非常仔细的对待它,稍有不慎可能导致用户无法解析的情况。

使用第三方 DNS(Managed DNS)部署 DNSSEC

由于是使用第三方的 DNS,部署 DNSSEC 就必须需要第三方支持。常见的支持 DNSSEC 的第三方 DNS 有 Cloudflare、Rage4、Google Cloud DNS(需要申请)、DynDNS 等。开启 DNSSEC 前首先需要在第三方服务上激活 DNSSEC,这样第三方 DNS 才会返回关于 DNSSEC 的记录。
在第三方的 DNS 上激活了 DNSSEC 之后,第三方会给你一个 DS 记录,大概长这样:

tlo.xyz. 3600 IN DS 2371 13 2 913f95fd6716594b19e68352d6a76002fdfa595bb6df5caae7e671ee028ef346

此时,你就需要前往域名注册商,给你的域名提供这个 DS 记录(有些域名注册商可能不支持添加 DS 记录,此时你可以考虑转移到本站的域名注册商或者其他支持 DS 记录的注册商。此外,一些域名后缀也不支持添加 DS 记录,建议你使用主流后缀,如 .com 等,此处就以本站的域名注册商为例):
添加然后保存,一切就 OK 了。注意关键标签(Key Tag)就是 DS 记录里的第一项(此处对应的是 2371),算法(Algorithm)就是第二项(此处对应的是 13),算法类型(Digest Type)就是第三项(此处对应的是 2),整理分类(Digest)就是最后一项。剩下的内容不需要填写。

有的第三方 DNS(比如 Rage4)会给你一下子提供多个 DS 记录(相同的关键标签但是不同的算法和算法类型),然而你不需要都填写上。我建议只填写使用算法 13 与类型 2 或者算法 8 类型与类型 2 的 DS。这两个分别是 Cloudflare 推荐的参数和根域名目前所使用的参数。填写多个 DS 记录不会给你带来多少的安全性提升,但可能会增大客户端的计算量。

使用自建 DNS 部署 DNSSEC

使用自建 DNS 首先需要先生成一对密钥对,然后将其添加到 DNS 服务中去。我已经介绍了关于 PowerDNS 的添加 DNSSEC 的方法

在此之后,你需要生成 DS 记录,通常你生成 DS 记录也是很多个,和第三方 DNS 一样,我建议只向域名注册商提交使用算法 13 与类型 2 或者算法 8 类型与类型 2 的 DS。

参考资料