ChaCha20 是一种高性能、高安全性的对称流密码算法,由 Google 的 Dan Bernstein 于 2008 年设计。它是 Salsa20 算法的改进版本,旨在提供比其前辈更高的抗攻击能力和更简洁的实现。ChaCha20 因其卓越的性能和安全性,已成为 TLS 协议中的重要组成部分,特别是在移动设备和低功耗环境中,替代了传统的 AES-GCM。

核心思想:通过一个密钥 (Key) 和一个随机数 (Nonce) 生成一个无限长的伪随机密钥流,然后将密钥流与明文进行异或 (XOR) 操作得到密文。解密时,使用相同的密钥和随机数生成相同的密钥流,再与密文异或即可还原明文。


一、流密码 (Stream Cipher) 简介

流密码是一种对称加密算法,它将明文的每个比特或每个字节与一个伪随机密钥流的对应比特或字节进行组合(通常是异或)来生成密文。

1.1 与分组密码 (Block Cipher) 的区别

特性 流密码 (Stream Cipher) 分组密码 (Block Cipher)
工作方式 逐位/逐字节加密 将明文分成固定大小的块,逐块加密
密钥流 基于密钥和初始化向量生成伪随机密钥流 对密钥进行复杂变换,直接加密数据块
错误传播 一般不会扩散 (比特错只影响一个比特) 会扩散,一个比特错误可能导致整个块解密失败
性能 通常更快,软件实现效率高 比流密码慢,硬件实现效率高
应用 实时通信、资源受限设备 大量数据存储、通用加密
典型算法 ChaCha20, RC4 AES, DES

1.2 流密码的安全性要求

  • 密钥流的随机性:密钥流必须具备高度的随机性,难以预测。
  • 长周期:密钥流的周期必须足够长,不重复或难以重复。
  • 抗统计攻击:密钥流不应存在可被统计分析利用的模式。
  • 抗代数攻击:密钥流生成函数不应存在可被代数方法攻击的弱点。

二、ChaCha20 算法基础

ChaCha20 是基于一个 256 位密钥和 96 位 Nonce(随机数)工作的。它生成 64 字节(512 位)的密钥流块。

2.1 核心组件:状态矩阵

ChaCha20 的内部状态是一个 4x4uint32 矩阵 (16 个 32 位字),共 64 字节。这个状态矩阵由以下部分初始化:

  1. 常数 (Constants):4 个 32 位字 ("expand 32-byte k")。
  2. 密钥 (Key):8 个 32 位字 (256 位密钥)。
  3. 计数器 (Block Counter):1 个 32 位字。每次生成 64 字节密钥流块时递增 1。
  4. 随机数/初始化向量 (Nonce/IV):3 个 32 位字 (96 位 Nonce)。

状态矩阵结构

1
2
3
4
5
6
7
8
9
+----+----+----+----+
| c0 | c1 | c2 | c3 | <- Constants
+----+----+----+----+
| k0 | k1 | k2 | k3 | <- Key (part 1)
+----+----+----+----+
| k4 | k5 | k6 | k7 | <- Key (part 2)
+----+----+----+----+
| b0 | n0 | n1 | n2 | <- Counter + Nonce
+----+----+----+----+

2.2 核心运算:QR 函数 (Quarter-Round)

ChaCha20 的核心是 Quarter-Round (QR) 函数,它对状态矩阵中的 4 个字进行一次置乱操作。这个函数保证了扩散 (diffusion) 和混淆 (confusion)。

QR 函数的输入:4 个 32 位字 a, b, c, d
QR 函数的操作 (都是 modulo 2^32 运算):

  1. a += b
  2. d ^= a
  3. d = (d <<< 16) (d 左旋 16 位)
  4. c += d
  5. b ^= c
  6. b = (b <<< 12) (b 左旋 12 位)
  7. a += b
  8. d ^= a
  9. d = (d <<< 8) (d 左旋 8 位)
  10. c += d
  11. b ^= c
  12. b = (b <<< 7) (b 左旋 7 位)

这个 QR 函数是高度并行的,对现代 CPU 的性能非常友好。

2.3 状态更新:12 轮置乱 (Rounds)

ChaCha20 在生成一个密钥流块时,会对初始化的状态矩阵进行 20 轮 (rounds) 置乱(Salsa20 是 20 轮,ChaCha20 的名称中的 20 就来源于此)。每轮包含 4 次“列操作”和 4 次“对角线操作”。

一轮操作流程

  1. 列操作 (Column Rounds):对状态矩阵的每列执行一次 QR 函数。
    • QR(s[0], s[4], s[8], s[12])
    • QR(s[1], s[5], s[9], s[13])
    • QR(s[2], s[6], s[10], s[14])
    • QR(s[3], s[7], s[11], s[15])
  2. 对角线操作 (Diagonal Rounds):对状态矩阵的每“主对角线”执行一次 QR 函数。
    • QR(s[0], s[5], s[10], s[15])
    • QR(s[1], s[6], s[11], s[12])
    • QR(s[2], s[7], s[8], s[13])
    • QR(s[3], s[4], s[9], s[14])

经过 20 轮(10 次列操作 + 10 次对角线操作)的置乱后,会得到一个最终的工作状态矩阵

2.4 生成密钥流块

  1. 将经过 20 轮置乱后的工作状态矩阵的每个字与最初的初始化状态矩阵的对应字进行模加 (Addition Modulo 2^32)
  2. 将这个结果矩阵扁平化为 64 字节的比特串,这就是一个密钥流块 (Key Stream Block)
  3. 这个密钥流块会与明文块进行异或操作,得到密文块。

流程图 (简化的生成密钥流块流程):

2.5 加密与解密

  • 加密:明文块 P 与密钥流块 K_stream 异或得到密文块 CC = P XOR K_stream
  • 解密:密文块 C 与相同的密钥流块 K_stream 异或得到明文块 PP = C XOR K_stream

由于异或运算的特性 (A XOR B) XOR B = A,所以加解密过程是完全对称的。

三、ChaCha20-Poly1305

ChaCha20 通常与 Poly1305 消息认证码 (MAC) 结合使用,形成 ChaCha20-Poly1305 AEAD (Authenticated Encryption with Associated Data) 模式。

3.1 为什么需要 AEAD 模式?

纯粹的流密码(如原始 ChaCha20)只提供数据机密性,不能保证:

  • 数据完整性 (Integrity):防止密文被篡改。
  • 数据真实性 (Authenticity):确认数据确实来自声称的发送方。

AEAD 模式(如 ChaCha20-Poly1305, AES-GCM)通过引入 MAC 来同时保证机密性、完整性和真实性。

3.2 Poly1305 简介

Poly1305 是一种由 Dan Bernstein 设计的快速消息认证码。它基于一次性密钥 (one-time key) 和多项式乘法计算 MAC 标签。

3.3 ChaCha20-Poly1305 AEAD 工作原理

  1. 使用 ChaCha20 生成密钥流,加密明文得到密文。
  2. 使用 ChaCha20 密钥流中的一部分(称为 Poly1305 密钥)以及 Nonce 和一些额外认证数据 (Associated Data, AD)。
  3. 将密文和 AD 作为输入,利用 Poly1305 计算出一个 MAC 标签。
  4. 发送方将密文、Nonce 和 MAC 标签一同发送给接收方。

接收方在解密前会先用相同的密钥、Nonce 和 AD 重新计算 MAC 标签,并与接收到的标签进行比较。只有当标签匹配且解密成功时,才认为数据是有效的。

ChaCha20-Poly1305 模式因其高性能和安全性,被广泛应用于 HTTPS (TLS 1.2/1.3)、VPN (WireGuard) 和其他网络协议中。

四、ChaCha20 的优缺点与适用场景

4.1 优点

  • 高性能:ChaCha20 的内部操作(加法、异或、循环移位)对现代 CPU 极其友好,易于高效的软件实现。这使得它在没有硬件加速的 CPU 上比 AES 更快。
  • 安全性高:经过广泛的密码学分析,目前没有已知的有效攻击方法。20 轮设计提供了高强度的安全余量。
  • 简洁性:算法设计相对简洁,代码量小,不易引入实现错误。
  • 无需查找表:与 AES 不同,ChaCha20 不需要查找表,这有助于抵抗侧信道攻击,并且在缓存较小的 CPU 上表现更佳。
  • 无专利:完全公开且免版税。
  • 与 Poly1305 完美结合:ChaCha20-Poly1305 提供了强大的 AEAD 功能。

4.2 缺点

  • 相对较新:虽然已经非常成熟,但相较于 AES,其标准化和部署时间较短。
  • 硬件加速程度:目前大多数硬件加速都是针对 AES 的,ChaCha20 在硬件性能上可能不如专门优化的 AES。

4.3 适用场景

  • TLS/SSL:特别是在 Google Chrome 浏览器与服务器之间的通信中广泛使用,尤其是在移动设备上,因为它能提供更好的性能。
  • VPN (WireGuard):下一代安全 VPN 协议 WireGuard 核心加密算法就是 ChaCha20-Poly1305。
  • 移动设备和嵌入式系统:由于其高效的软件实现和对 ARM 等架构的优化,非常适合资源受限或没有 AES 硬件加速的设备。
  • 通用数据加密:任何需要高性能、高安全性对称加密的场景。

五、Go 语言实现 ChaCha20-Poly1305

Go 语言标准库通过 golang.org/x/crypto/chacha20poly1305 包提供了 ChaCha20-Poly1305 AEAD 的实现。

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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package main

import (
"crypto/rand"
"fmt"
"io"
"log"

"golang.org/x/crypto/chacha20poly1305"
)

// generateRandomBytes 生成指定长度的随机字节
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := io.ReadFull(rand.Reader, b)
if err != nil {
return nil, fmt.Errorf("生成随机字节失败: %w", err)
}
return b, nil
}

func main() {
// 1. 生成 256 位 (32 字节) 的密钥
key, err := generateRandomBytes(chacha20poly1305.KeySize)
if err != nil {
log.Fatalf("生成密钥失败: %v", err)
}
fmt.Printf("密钥 (Hex): %x\n", key)

// 2. 创建 ChaCha20-Poly1305 AEAD cipher
aead, err := chacha20poly1305.New(key)
if err != nil {
log.Fatalf("创建 AEAD 密码器失败: %v", err)
}

// 3. 生成 Nonce (必须是随机的,且每个加密操作必须不同,但不需要保密)
// Nonce 的大小通常是 chacha20poly1305.NonceSize (12 字节/96 位)
nonce, err := generateRandomBytes(aead.NonceSize())
if err != nil {
log.Fatalf("生成 Nonce 失败: %v", err)
}
fmt.Printf("Nonce (Hex): %x\n", nonce)

// 4. 定义明文和附加认证数据 (Associated Data, AD)
plaintext := []byte("这是需要加密和验证的绝密消息!Go语言 ChaCha20-Poly1305 示例。")
associatedData := []byte("这是附加认证数据,不加密但需要验证完整性。例如,协议版本号或会话ID。")

fmt.Printf("\n原始明文: %s\n", string(plaintext))
fmt.Printf("附加认证数据 (AD): %s\n", string(associatedData))

// 5. 加密并生成 MAC 标签
// Seal 函数的 out 缓冲区会包含密文和 MAC 标签
// 格式通常是 nonce + ciphertext + tag
ciphertextWithTag := aead.Seal(nil, nonce, plaintext, associatedData)
fmt.Printf("\n加密后的数据 (密文+标签, Hex): %x\n", ciphertextWithTag)

// 6. 解密并验证 MAC 标签
// Open 函数会在解密前验证 MAC 标签,如果验证失败会返回错误
decryptedPlaintext, err := aead.Open(nil, nonce, ciphertextWithTag, associatedData)
if err != nil {
log.Fatalf("解密或MAC验证失败: %v", err)
}
fmt.Printf("解密后的明文: %s\n", string(decryptedPlaintext))

if string(plaintext) == string(decryptedPlaintext) {
fmt.Println("\n加密、解密和MAC验证成功!数据完整且真实。")
} else {
fmt.Println("\n解密后的数据与原始数据不匹配,加密或解密失败。")
}

// 7. 模拟数据篡改,验证 MAC 机制
fmt.Println("\n--- 模拟数据篡改 ---")
// 篡改密文的一个字节
tamperedCiphertextWithTag := make([]byte, len(ciphertextWithTag))
copy(tamperedCiphertextWithTag, ciphertextWithTag)
if len(tamperedCiphertextWithTag) > 0 {
tamperedCiphertextWithTag[0] ^= 0x01 // 修改第一个字节
}

_, err = aead.Open(nil, nonce, tamperedCiphertextWithTag, associatedData)
if err != nil {
fmt.Printf("尝试解密篡改数据 (预期失败): %v\n", err)
} else {
fmt.Println("解密篡改数据成功 (不应该发生)! MAC 验证失效。")
}

// 篡改 Associated Data
fmt.Println("\n--- 模拟 AD 篡改 ---")
tamperedAssociatedData := []byte("篡改过的附加认证数据")
_, err = aead.Open(nil, nonce, ciphertextWithTag, tamperedAssociatedData)
if err != nil {
fmt.Printf("尝试解密原始密文但 AD 篡改 (预期失败): %v\n", err)
} else {
fmt.Println("解密成功但 AD 篡改 (不应该发生)! MAC 验证失效。")
}
}

运行结果示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
密钥 (Hex): 91d36d...
Nonce (Hex): f154e5...

原始明文: 这是需要加密和验证的绝密消息!Go语言 ChaCha20-Poly1305 示例。
附加认证数据 (AD): 这是附加认证数据,不加密但需要验证完整性。例如,协议版本号或会话ID。

加密后的数据 (密文+标签, Hex): 040f7b....
解密后的明文: 这是需要加密和验证的绝密消息!Go语言 ChaCha20-Poly1305 示例。

加密、解密和MAC验证成功!数据完整且真实。

--- 模拟数据篡改 ---
尝试解密篡改数据 (预期失败): chacha20poly1305: message authentication failed

--- 模拟 AD 篡改 ---
尝试解密原始密文但 AD 篡改 (预期失败): chacha20poly1305: message authentication failed

此 Go 语言示例演示了如何使用 chacha20poly1305 包进行密钥生成、Nonce 生成、数据的加密、解密以及 MAC 标签的验证。它还通过模拟篡改密文和附加认证数据来展示 MAC 机制如何保护数据的完整性和真实性。

六、总结

ChaCha20 流密码及其 AEAD 模式 ChaCha20-Poly1305 是现代密码学中的一个重要进展。它以其卓越的性能、高安全性、简洁的实现和免版税的特性,在多种应用场景中展现出强大的优势,尤其是在移动设备和 Web (TLS) 领域,成为 AES-GCM 的有力替代者。理解 ChaCha20 的工作原理和安全特性,对于构建安全、高效的网络通信和数据保护系统至关重要。