1. 引言:为什么需要 ChaCha20?
在密码学领域,AES(高级加密标准)长期占据主流地位,但它有一个 “软肋”—— 在无硬件加速(如 AES-NI 指令集)的设备(如物联网传感器、低端手机、嵌入式系统)上性能拉胯。此外,AES 的查表操作容易受到 “侧信道攻击”(通过功耗、时序等物理信息泄露密钥)。
为解决这些问题,著名密码学家丹尼尔・伯恩斯坦(Daniel J. Bernstein)设计了ChaCha20—— 一种轻量级、高安全性、高性能的流密码。2015 年,IETF 发布RFC7539(《ChaCha20 and Poly1305 for IETF Protocols》),将 ChaCha20 标准化,使其成为 TLS、WireGuard 等协议的核心加密算法。
本文将从原理出发,结合 RFC7539 标准和 openHiTLS 的开源实现,带您彻底搞懂 ChaCha20。
2. ChaCha20 核心原理:从 RFC7539 说起
ChaCha20 的核心是通过 “轮次变换” 生成伪随机密钥流,再与明文 / 密文进行异或操作实现加解密(流密码的典型逻辑)。RFC7539 详细定义了其状态结构、轮次规则和密钥流生成流程,我们逐一拆解。
2.1 状态矩阵:ChaCha20 的 “骨架”
ChaCha20 的所有操作都围绕一个16×32 位的状态矩阵(共 512 位)展开,矩阵分为 4 个部分,严格遵循 RFC7539-2.3 的定义:
| 位置(0-15) | 内容 | 来源与说明 | 长度(32 位整数) |
|---|---|---|---|
| 0-3 | 固定常数 | 对应字符串 “expand 32-byte k” 的小端字节序 | 4 个 |
| 4-11 | 256 位密钥 | 密钥按小端字节序拆分为 8 个 32 位整数 | 8 个 |
| 12 | 块计数器(Counter) | 32 位整数,每生成 64 字节密钥流自增 1 | 1 个 |
| 13-15 | 96 位随机数(Nonce) | Nonce 按小端字节序拆分为 3 个 32 位整数 | 3 个 |
关键注意点(RFC7539 强制要求):
- Nonce 必须 “一次性使用”:同一密钥下,Nonce 重复会导致密钥流重复,攻击者可通过异或密文还原明文;
- 计数器初始值通常为 1(RFC7539-2.4):0 号块预留用于生成 Poly1305 的认证密钥(ChaCha20-Poly1305 AEAD 方案)。
2.2 轮次变换:密钥流的 “发动机”
ChaCha20 通过20 轮变换(10 次迭代,每次迭代含 “列混淆” 和 “对角线混淆”)打乱状态矩阵,生成伪随机数据。核心操作是 “四分之一轮”(QUARTERROUND),RFC7539-2.1 定义其逻辑为:
a += b:32 位无符号加法(溢出忽略);d ^= a:异或操作;d = ROTL32(d, 16):d 循环左移 16 位;- 重复类似步骤操作 c、b(左移 12 位、8 位、7 位)。
2.2.1 完整轮次流程(RFC7539-2.3)
1 次迭代 = 列混淆 + 对角线混淆:
- 列混淆:对矩阵 4 列分别执行 QUARTERROUND(列 0:0→4→8→12;列 1:1→5→9→13;列 2:2→6→10→14;列 3:3→7→11→15);
- 对角线混淆:对 4 条对角线分别执行 QUARTERROUND(对角线 0:0→5→10→15;对角线 1:1→6→11→12;对角线 2:2→7→8→13;对角线 3:3→4→9→14);
- 重复 10 次迭代,共 20 轮变换。
用 mermaid 流程图直观展示:

2.3 密钥流生成与加解密
ChaCha20 的加解密逻辑完全对称(异或的可逆性):
- 加密:明文字节 × 密钥流字节 = 密文字节;
- 解密:密文字节 × 密钥流字节 = 明文字节。
密钥流生成规则(RFC7539-2.3):
- 每次 20 轮变换生成 64 字节密钥流;
- 密钥流生成后,计数器(state [12])自增 1,准备下一块数据;
- 若明文长度不足 64 字节,仅取密钥流的前 N 字节异或。
3. openHiTLS 开源实现解析:ChaCha20 代码实战
openHiTLS 是华为开源的轻量级密码库,其 ChaCha20 实现严格遵循 RFC7539。我们以chacha20.c(代码链接)为例,解析核心函数的逻辑。
3.1 核心宏定义:QUARTERROUND 的实现
对应 RFC7539-2.1 的四分之一轮操作,openHiTLS 用宏定义封装,确保高效执行:
// RFC7539-2.1 定义的四分之一轮操作
#define QUARTER(a, b, c, d) \
do { \
(a) += (b); (d) ^= (a); (d) = ROTL32((d), 16); \
(c) += (d); (b) ^= (c); (b) = ROTL32((b), 12); \
(a) += (b); (d) ^= (a); (d) = ROTL32((d), 8); \
(c) += (d); (b) ^= (c); (b) = ROTL32((b), 7); \
} while (0)
// 对状态矩阵的指定位置执行QUARTERROUND
#define QUARTERROUND(state, a, b, c, d) QUARTER((state)[(a)], (state)[(b)], (state)[(c)], (state)[(d)])
ROTL32:32 位循环左移函数(openHiTLS 内部实现);- 宏定义采用
do-while(0)结构:避免多语句宏在条件判断中出现逻辑错误。
3.2 设置密钥:CRYPT_CHACHA20_SetKey
对应 RFC7539-2.3 的状态矩阵初始化,函数负责将 256 位密钥填入状态矩阵:
int32_t CRYPT_CHACHA20_SetKey(CRYPT_CHACHA20_Ctx *ctx, const uint8_t *key, uint32_t keyLen) {
// 1. 输入合法性检查(安全编程基础)
if (ctx == NULL || key == NULL || keyLen == 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
// 密钥长度必须为32字节(256位,RFC7539强制要求)
if (keyLen != CHACHA20_KEYLEN) { // CHACHA20_KEYLEN = 32
BSL_ERR_PUSH_ERROR(CRYPT_CHACHA20_KEYLEN_ERROR);
return CRYPT_CHACHA20_KEYLEN_ERROR;
}
// 2. 初始化状态矩阵的“常数部分”(对应“expand 32-byte k”小端)
ctx->state[0] = 0x61707865; // 'expa'(小端:0x65 0x78 0x70 0x61 → 0x61707865)
ctx->state[1] = 0x3320646e; // 'nd 3'
ctx->state[2] = 0x79622d32; // '2-by'
ctx->state[3] = 0x6b206574; // 'te k'
// 3. 填充“密钥部分”(小端字节序,RFC7539要求)
// GET_UINT32_LE:从字节流中取4字节,按小端转换为32位整数
ctx->state[4] = GET_UINT32_LE(key, 0); // 密钥第0-3字节
ctx->state[5] = GET_UINT32_LE(key, 4); // 密钥第4-7字节
ctx->state[6] = GET_UINT32_LE(key, 8); // 密钥第8-11字节
ctx->state[7] = GET_UINT32_LE(key, 12); // 密钥第12-15字节
ctx->state[8] = GET_UINT32_LE(key, 16); // 密钥第16-19字节
ctx->state[9] = GET_UINT32_LE(key, 20); // 密钥第20-23字节
ctx->state[10] = GET_UINT32_LE(key, 24); // 密钥第24-27字节
ctx->state[11] = GET_UINT32_LE(key, 28); // 密钥第28-31字节
// 4. 初始化计数器(RFC7539-2.4:通常设为1,0号块预留)
ctx->state[12] = 1;
// 5. 标记密钥已设置
ctx->set |= KEYSET;
ctx->lastLen = 0;
return CRYPT_SUCCESS;
}
3.3 设置 Nonce:CRYPT_CHACHA20_SetNonce
Nonce 是 96 位(12 字节),函数负责将其填入状态矩阵的 13-15 位置:
static int32_t CRYPT_CHACHA20_SetNonce(CRYPT_CHACHA20_Ctx *ctx, const uint8_t *nonce, uint32_t nonceLen) {
// 1. 输入合法性检查
if (ctx == NULL || nonce == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
// Nonce长度必须为12字节(96位,RFC7539强制要求)
if (nonceLen != CHACHA20_NONCELEN) { // CHACHA20_NONCELEN = 12
BSL_ERR_PUSH_ERROR(CRYPT_CHACHA20_NONCELEN_ERROR);
return CRYPT_CHACHA20_NONCELEN_ERROR;
}
// 2. 填充“Nonce部分”(小端字节序)
ctx->state[13] = GET_UINT32_LE(nonce, 0); // Nonce第0-3字节
ctx->state[14] = GET_UINT32_LE(nonce, 4); // Nonce第4-7字节
ctx->state[15] = GET_UINT32_LE(nonce, 8); // Nonce第8-11字节
// 3. 标记Nonce已设置
ctx->set |= NONCESET;
ctx->lastLen = 0;
return CRYPT_SUCCESS;
}
3.4 生成密钥流块:CHACHA20_Block
函数执行 20 轮变换,生成 64 字节密钥流,是 ChaCha20 的核心计算逻辑:
void CHACHA20_Block(CRYPT_CHACHA20_Ctx *ctx) {
uint32_t i;
// 1. 保存初始状态矩阵(后续需与变换后矩阵相加)
(void)memcpy_s(ctx->last.c, CHACHA20_STATEBYTES, ctx->state, sizeof(ctx->state));
// CHACHA20_STATEBYTES = 64(16×4字节)
// 2. 执行10次迭代(共20轮变换,RFC7539-2.3)
for (i = 0; i < 10; i++) {
// 列混淆
QUARTERROUND(ctx->last.c, 0, 4, 8, 12); // 列0
QUARTERROUND(ctx->last.c, 1, 5, 9, 13); // 列1
QUARTERROUND(ctx->last.c, 2, 6, 10, 14); // 列2
QUARTERROUND(ctx->last.c, 3, 7, 11, 15); // 列3
// 对角线混淆
QUARTERROUND(ctx->last.c, 0, 5, 10, 15); // 对角线0
QUARTERROUND(ctx->last.c, 1, 6, 11, 12); // 对角线1
QUARTERROUND(ctx->last.c, 2, 7, 8, 13); // 对角线2
QUARTERROUND(ctx->last.c, 3, 4, 9, 14); // 对角线3
}
// 3. 与初始状态矩阵相加(RFC7539要求),并转换为小端字节流
for (i = 0; i < CHACHA20_STATESIZE; i++) { // CHACHA20_STATESIZE = 16
ctx->last.c[i] += ctx->state[i];
// CRYPT_HTOLE32:将主机字节序转换为小端字节序
ctx->last.c[i] = CRYPT_HTOLE32(ctx->last.c[i]);
}
// 4. 计数器自增(准备下一块密钥流)
ctx->state[12]++;
}
3.5 数据加解密:CRYPT_CHACHA20_Update
函数将明文 / 密文与密钥流异或,实现核心加解密逻辑:
int32_t CRYPT_CHACHA20_Update(CRYPT_CHACHA20_Ctx *ctx, const uint8_t *in, uint8_t *out, uint32_t len) {
// 1. 输入合法性检查(需先设置密钥和Nonce)
if (ctx == NULL || out == NULL || in == NULL || len == 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
if ((ctx->set & KEYSET) == 0) { // 未设置密钥
BSL_ERR_PUSH_ERROR(CRYPT_CHACHA20_NO_KEYINFO);
return CRYPT_CHACHA20_NO_KEYINFO;
}
if ((ctx->set & NONCESET) == 0) { // 未设置Nonce
BSL_ERR_PUSH_ERROR(CRYPT_CHACHA20_NO_NONCEINFO);
return CRYPT_CHACHA20_NO_NONCEINFO;
}
// (注:省略后续异或逻辑,核心是循环调用CHACHA20_Block生成密钥流,再与in异或写入out)
}
4. ChaCha20 的优势:安全性与性能双在线
4.1 安全性:抗攻击能力拉满
- 密钥强度:256 位密钥,暴力破解几乎不可能;
- 抗侧信道攻击:无 AES 的查表操作,功耗 / 时序泄露风险低;
- 标准化保障:RFC7539 严格定义,避免实现漏洞(如 openHiTLS 的实现完全遵循标准);
- Nonce 防护:RFC7539 明确 Nonce 唯一性要求,避免密钥流重复风险。
4.2 性能:资源受限设备的 “救星”
在无 AES-NI 的设备上,ChaCha20 的吞吐量远超 AES-128:
| 设备类型 | ChaCha20 吞吐量 | AES-128-CBC 吞吐量 |
|---|---|---|
| 低端 ARM 嵌入式 | ~150 MB/s | ~80 MB/s |
| 物联网传感器 | ~50 MB/s | ~20 MB/s |
| 无加速的 PC | ~300 MB/s | ~180 MB/s |
因:ChaCha20 的操作仅含加法、异或、循环左移,无需复杂的 S 盒查表,硬件实现成本低。
5. 应用场景:ChaCha20 在哪里发光?
- TLS 协议:RFC7905 定义了
TLS_CHACHA20_POLY1305_SHA256套件,成为移动浏览器(如 Chrome、Firefox)的默认选择; - WireGuard VPN:全链路使用 ChaCha20-Poly1305,兼顾速度与安全;
- 物联网:传感器、智能设备的端到端加密(如 LoRaWAN 协议);
- 即时通讯:Signal、WhatsApp 等应用用 ChaCha20 加密消息内容;
- 嵌入式系统:路由器、智能家居设备的配置文件加密。
6. 总结与展望
ChaCha20 凭借 “高安全性、高性能、低资源消耗” 的特点,成为 AES 的重要补充,尤其在物联网和移动领域。RFC7539 的标准化使其实现更统一(如 openHiTLS 的开源代码),降低了应用门槛。
未来,随着量子计算的发展,ChaCha20 可能需要与后量子密码结合(如 openHiTLS 正在探索的后量子算法),但在当前场景下,它仍是最可靠的流密码之一。
如果您想深入学习,建议:
- 阅读 RFC7539 原文(链接);
- openHiTLS 开源仓库:chacha20.c
- 丹尼尔・伯恩斯坦:《ChaCha, a variant of Salsa20》
3353

被折叠的 条评论
为什么被折叠?



