前段时间看gmail的时候,发现邮件头上都有一个mailed by的字段,比如mailed by: yahoo.com,不知道这个东西是怎么来的,于是查看了一下整个header,发现有一个DKIM-Signature的字段,于是又去google了一下,了解到这个原来是一个新的标准,叫DKIM,定义在RFC4871里。它的功能是:让企业可通过密码对进出站的电子邮件进行签名,证实所发送邮件的真实性。

DKIM的全称是Domain Key Identified Mail(域名密钥识别邮件)。简单的说,就是发送邮件的服务器对邮件做签名,表示这个邮件的确是该服务器发送出去的。如果一个接收端能够验证这个签名,一定程度上表明这个邮件可能不是垃圾或者钓鱼邮件——原因是现在大多数的垃圾和钓鱼邮件都使用伪造的email地址。当然,这个技术不能单独解决问题,因为一个垃圾邮件发送者也可以自己做一个服务器来对发出的所有垃圾邮件签名。但是,由于邮件是签名的,我们就可以通过黑名单和白名单来进一步了解某个域名发出的邮件是否为垃圾邮件,甚至可以在网上维护一个域名的积分系统。

签名另外一个好处就是,DKIM提供一定的完整性(integrity)的保护,保证这个邮件在发送过程中没有收到篡改。

那么这个签名是怎么做的呢?基于公私钥的签名系统。发送者在服务器上生成一对RSA的key,私钥自己保存好,公钥放在DNS的TXT记录中,比如一个站点叫example.org,那么其DKIM的公钥通过_domainkey.example.org的TXT记录就可以查询到,这样接收方就很容易知道发送方的公钥了,也就可以验证这个签名。这个方案好的一点是,不需要一个第三方认证的CA——毕竟,每年通过CA签名一个证书也要好多钱。这也是DKIM跟以前的签名技术如S/MIME, PGP等不同的一个地方。当然,通过DNS的TXT记录也有其安全性问题,完全有人可以hack掉DNS,或者通过类似GFW的方法改变DNS查询的结果,但是DKIM只是一个辅助技术,是用来防范垃圾邮件的,并不是一个strong authentication system,如果真的对安全性要求那么高,就应该使用PGP之类的其他安全系统。

接下来的问题是,签名是如何加入邮件的呢?通过DKIM-Signature邮件头。这是符合邮件协议(RFC2822)规定的。这是跟其他签名方法又一个大不同之处,避免了任何不理解S/MIME的客户端把签名当成一个附件(比如gmail),或者把签名显示为一段跟内容没有关系的base64编码。接收方则根据DKIM-Signature头的内容来验证该签名。

一些签名的具体细节:

  1. 算法,必须支持rsa-sha256,可选支持rsa-sha1。key size建议1024。王小云老师在这里贡献不小。
  2. 标准化(Canonicalization). 有的邮件服务器可能会少量修改文件内容,比如换行或者移除一些空格等等。因此DKIM定义了两种标准化方法,simple和relaxed。simple最简单,就是一个字节也不能改,改了就错。relaxed就是可以少量的修改一些空格等等。邮件头和内容这两部分的标准化可以选择不同的方法,表示起来用/隔开,比如simple/relaxed表示头部用simple方式,内容用relaxed方式来标准化。
  3. 过程。签名是先对内容(body)部分hash,然后把这个body hash放在header里面,再对头部做签名。头部也不是所有字段都签名的,只有一些常用的字段,或者比较有意义的,会被签名。像Received和Return-Path这样的字段一般不被签名。而From则必须被签名。

差不多啦,来看个真实的DKIM头吧:

DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=gmail.com; s=gamma;
        h=message-id:date:from:to:subject:mime-version:content-type;
        bh=LKY45ARqaaOMfM6XOs6BBkYA44a4+H26qQdm9oC55X0=;
        b=QhTXCrvIhk......XQbXhFl4Xk575ACw=

可以看到一个DKIM-Signature由一系列name=value的tag组成。其中:

  • v=1表示版本1,将来要有新的版本,就扩展之。
  • a=rsa-sha256,表示算法(algorithm)。这里用了rsa-sha256的算法。
  • c=relaxed/relaxed,表示标准化方法(Canonicalization),头部和内容都用的relaxed方法。
  • d=gmail.com,发送者的域名。
  • s=gamma,表示域名的selector,通过这个selector,可以允许一个域名有多个public key,这样不同的server可以有不同的key。
  • h=…,表示后面这些header是签名的一部分。
  • bh=…,是body hash。也就是内容的hash。
  • b=…,是header的签名。也就是把h=那个里面所有的字段及其值都取出来,外加DKIM-signature这个头(除了b=这个值,因为还不存在),一起hash一下,然后用rsa加密。

最后看一下如何获得上面这个例子的public key。对于s=gamma, 这个gmail服务器的public key存在gamma._domainkey.gmail.com的TXT字段里。在Linux上dig一下:

$ dig gamma._domainkey.gmail.com TXT
;; QUESTION SECTION:
;gamma._domainkey.gmail.com.    IN      TXT

;; ANSWER SECTION:
gamma._domainkey.gmail.com. 300 IN      TXT     "k=rsa\; t=y\; p=MIGfMA0GCSqGSIb3...DAQAB"

nslookup也是一样的:

C:\>nslookup
> set q=txt
> gamma._domainkey.gmail.com
Non-authoritative answer:
gamma._domainkey.gmail.com      text =
"k=rsa; t=y; p=MIGfMA0GCSqGSIb3.....AB"

其中的p=那一段就是public key了。

DKIM不能直接的解决垃圾邮件问题,但是随着使用DKIM技术的公司和软件越来越多,通过其他一些手段的辅助,迟早对我们解决问题会有很大的帮助的。

你部署了DKIM了没有?