DKIM(DomainKeys Identified Mail) 是一种用于验证电子邮件的身份和完整性的技术。它通过给发件人的电子邮件头部附加一个数字签名,来确保邮件没有在传输过程中被篡改,并确认邮件确实来自声明的域名。DKIM 是电子邮件安全生态系统中重要的组成部分,通常与 SPF(Sender Policy Framework)和 DMARC(Domain-based Message Authentication, Reporting & Conformance)共同使用,以减少垃圾邮件和欺诈性邮件的风险。

DKIM 的工作原理

  1. 生成密钥对:域的管理员生成一对公钥和私钥。公钥发布在 DNS 记录中,私钥则保留在邮件服务器上,作为签名邮件的依据。
  2. 邮件签名:当邮件从发件人服务器发出时,邮件服务器使用私钥对某些邮件头部字段(例如 FromToSubject 等)和邮件体内容生成数字签名,并将该签名作为 DKIM-Signature 头部添加到邮件中。
  3. 邮件接收:当接收方的邮件服务器收到这封邮件时,它会提取邮件中的 DKIM-Signature 头部信息,并从发件人的 DNS 记录中获取相应的公钥。
  4. 验证签名:接收方邮件服务器使用获取到的公钥解密 DKIM-Signature,并将解密后的值与邮件头部和内容的哈希值进行比较。如果两者匹配,说明邮件在传输过程中未被篡改,且邮件确实是由拥有对应私钥的服务器发出的。
  5. 结果处理:如果验证通过,接收方邮件服务器可以将邮件标记为通过 DKIM 验证。这一结果可以影响邮件的处理方式,例如进入收件箱或垃圾邮件箱。

DKIM 的关键组成部分

  • 域名:发件人邮箱的域名,用于与 DKIM 签名关联。
  • 选择器(Selector):用于标识用于签名的密钥对。这允许在同一个域名下使用不同的密钥对来签名不同的邮件。
  • 公钥和私钥:私钥用于生成签名,公钥用于在 DNS 中发布,供接收方邮件服务器验证签名。
  • Canonicalization:规范化方法,用于处理在传输中可能发生的细微内容变化。常见的规范化方式有 simplerelaxed,分别表示严格和宽松的处理方式。

DKIM 的优势

  • 防止邮件篡改:确保邮件内容在传输过程中没有被篡改。
  • 验证发件人身份:帮助接收方确认邮件确实来自声明的域名。
  • 配合 SPF 和 DMARC:共同构建起邮件身份验证和反垃圾邮件的综合防护体系。

DKIM 实施步骤

  1. 生成密钥对:通过 OpenSSL 等工具生成一对公钥和私钥。
  2. 配置 DNS 记录:将公钥发布到 DNS 记录中,通常是 TXT 记录。
  3. 配置邮件服务器:在邮件服务器上配置 DKIM 签名,以便发送的邮件自动附加签名。
  4. 测试和验证:使用 DKIM 验证工具(如 dkimvalidator.com)测试 DKIM 签名的正确性。

示例

以下是 DKIM 签名的示例头部:

1
2
3
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=example.com; s=default;
t=123456789; bh=abc123456789...; h=From:To:Subject;
b=abcd1234...
  • v=1 表示 DKIM 版本。
  • a=rsa-sha256 表示使用的签名算法。
  • c=relaxed/simple 表示 Canonicalization 方式。
  • d=example.com 表示签名的域名。
  • s=default 表示使用的选择器。
  • bh=abc123456789... 是邮件体的哈希值。
  • h=From:To:Subject 是被签名的头部字段。
  • b=abcd1234... 是数字签名。

总结

DKIM 是一种强大的技术,通过数字签名确保邮件的安全性和完整性。它对防止邮件欺诈、垃圾邮件和钓鱼邮件非常有效,因此在现代邮件服务器和域名管理中广泛应用。

Golang 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package main

import (
"bytes"
"log"
"net/smtp"

"fmt"
"github.com/toorop/go-dkim"
gomail "gopkg.in/gomail.v2"
)

var (
SmtpUser = "user"
SmtpPass = "password"
Host = "smtp@163.com"
Port = 587
Sender = "noreply@test.com"
)

func main() {
// 发件人、收件人和邮件内容
from := Sender
to := "test@test.com"

subject := "Test Email with DKIM"
body := "This is a test email with DKIM signature."

// 设置邮件
m := gomail.NewMessage()
m.SetHeader("From", from)
m.SetHeader("To", to)
m.SetHeader("Subject", subject)
m.SetBody("text/plain", body)

// 将邮件内容转换为字节数组
var buf bytes.Buffer
_, err := m.WriteTo(&buf)
if err != nil {
log.Fatal("Error writing email to buffer:", err)
}

// DKIM 选项
dkimOptions := dkim.SigOptions{
Domain: "example.com", // 域名
Selector: "default", // DKIM 选择器
//Signer: "sender@example.com", // 签名者
PrivateKey: []byte(`-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAnP8IOGt4Hj+GzPGzvbMr2ZBJqzjWeinaNz2HKq6Q3Q2S3bcE
gEdHXxqSzEa9/GcSquc6A22+mqUkFGH/ahGf3OXb77x/dcsh+ODe7jE=
-----END RSA PRIVATE KEY-----`), // DKIM 私钥
Algo: "rsa-sha256",
Headers: []string{"From", "To", "Subject"}, // 要签名的头字段
Canonicalization: "relaxed/simple", // Canonicalization 参数
}

// 对邮件进行 DKIM 签名
emailBytes := buf.Bytes()
err = dkim.Sign(&emailBytes, dkimOptions)
if err != nil {
log.Fatal("Error signing email:", err)
}

// 使用 smtp.SendMail 发送签名后的电子邮件
smtpHost := Host
smtpPort := Port
smtpAuth := smtp.PlainAuth("", SmtpUser, SmtpPass, smtpHost)

url := fmt.Sprintf("%s:%d", smtpHost, smtpPort)
err = smtp.SendMail(url, smtpAuth, from, []string{to}, emailBytes)
if err != nil {
log.Fatal("Error sending email:", err)
}

log.Println("Email sent successfully with DKIM signature!")
}