ChaCha20

简介

ChaCha20是由Salsa20修改来的一种加密算法,两者同属于流加密算法,在密码学安全性上是RC4算法的替代,其只有三种操作,分别为32位下的模加,循环左移和异或,通过这三种操作实现了一个伪随机函数,接受一个256位密钥和一个96位nonce每轮输出512位的流密码,ChaCha20Salsa20的区别是前者的循环函数进行了修改以增加扩散性

nonce在密码学中指的是一次性随机数,用于添加在状态矩阵中确保每次会话生成的密码都是不同的

特征

ChaCha20采用一个由4*4个uint32_t组成的状态矩阵来生成流密码,其按照轮输出流密码,每输出完512位的流密码后其会重新打乱整个状态矩阵来生成下一轮的流密码
ChaCha20的轮函数非常简单,接受4个32位整数并通过运算来混合它们,这个轮函数因为每次对矩阵里的四分之一的数进行混合被称为四分之一轮,一次完整的混合总共会调用该函数80次

实现

主体部分就是先初始化状态矩阵,然后每加密64字节的数据就重新生成新的流密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ChaCha20_enc(uint8_t *input, size_t len, uint8_t *key, uint8_t *nonce,
uint32_t count = 0) {
int idx = 0;
ChaCha20_init(key, nonce, count);
for (size_t i = 0; i < len; i++) {
if (idx % 64 == 0) {
ChaCha20_gen(state, keyStream);
state[12]++;
idx = 0;
}
// 应用流密码
input[i] ^= ((uint8_t *)keyStream)[idx];
idx++;
}
}

首先是对状态矩阵进行填充

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
char ChaCha20_Const[128] = "expand 32-byte k";
void ChaCha20_init(uint8_t key[32], uint8_t *nonce, uint32_t count) {
// 填充128位常量
state[0] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const);
state[1] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const + 4);
state[2] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const + 8);
state[3] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const + 12);
// 填充256位密钥
state[4] = convert_uint8Arr_to_uint32(key + 0);
state[5] = convert_uint8Arr_to_uint32(key + 4);
state[6] = convert_uint8Arr_to_uint32(key + 8);
state[7] = convert_uint8Arr_to_uint32(key + 12);
state[8] = convert_uint8Arr_to_uint32(key + 16);
state[9] = convert_uint8Arr_to_uint32(key + 20);
state[10] = convert_uint8Arr_to_uint32(key + 24);
state[11] = convert_uint8Arr_to_uint32(key + 28);
// 填充轮计数器,用于确保一次会话中的每个数据块生成的流密码均不同
state[12] = count;
// 填充nonce,用于确保每个会话生成的流密码不同
state[13] = convert_uint8Arr_to_uint32(nonce + 0);
state[14] = convert_uint8Arr_to_uint32(nonce + 4);
state[15] = convert_uint8Arr_to_uint32(nonce + 8);
}

这里的128位常量可以任意修改,标准实现为expand 32-byte k

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ChaCha20_gen(uint32_t *in, uint32_t *out) {
for (int i = 0; i < 16; i++)
out[i] = in[i];
// 十轮混合函数,前4个是列混合,后四个是对角线混合
for (int i = 0; i < 10; i++) {
QR(out[0], out[4], out[8], out[12]);
QR(out[1], out[5], out[9], out[13]);
QR(out[2], out[6], out[10], out[14]);
QR(out[3], out[7], out[11], out[15]);
QR(out[0], out[5], out[10], out[15]);
QR(out[1], out[6], out[11], out[12]);
QR(out[2], out[7], out[8], out[13]);
QR(out[3], out[4], out[9], out[14]);
}
// 将打乱前的矩阵叠加到打乱后的矩阵上,防止差分攻击
for (int i = 0; i < 16; i++)
out[i] += in[i];
}

生成流密码方式如上,可以发现明显的10轮特征,轮数根据变种不同可能会减少,混合完后将原矩阵加到新矩阵上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define QR(a, b, c, d) \
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)

四分之一轮函数如上,这四个左移长度常量是具有密码学意义的(也就是说从密码学角度最好别乱动他),这个函数确保了算法的扩散性

解密

ChaCha20是流加密,所以解密和RC4一样,获取流密码后根据流密码的使用方式解密即可,标准ChaCha20是异或,所以加密和解密代码相同

完整代码

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
#include <cstddef>
#include <cstdint>
#include <cstdio>
using namespace std;
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
#define QR(a, b, c, d) \
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)

uint32_t convert_uint8Arr_to_uint32(const uint8_t *arr) {
return ((uint32_t)(arr[0] << 0)) | ((uint32_t)(arr[1] << 8)) |
((uint32_t)(arr[2] << 16)) | ((uint32_t)(arr[3] << 24));
}
uint32_t state[16];
uint32_t keyStream[16];
char ChaCha20_Const[128] = "expand 32-byte k";
void ChaCha20_init(uint8_t key[32], uint8_t *nonce, uint32_t count) {
// 填充128位常量
state[0] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const);
state[1] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const + 4);
state[2] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const + 8);
state[3] = convert_uint8Arr_to_uint32((uint8_t *)ChaCha20_Const + 12);
// 填充256位密钥
state[4] = convert_uint8Arr_to_uint32(key + 0);
state[5] = convert_uint8Arr_to_uint32(key + 4);
state[6] = convert_uint8Arr_to_uint32(key + 8);
state[7] = convert_uint8Arr_to_uint32(key + 12);
state[8] = convert_uint8Arr_to_uint32(key + 16);
state[9] = convert_uint8Arr_to_uint32(key + 20);
state[10] = convert_uint8Arr_to_uint32(key + 24);
state[11] = convert_uint8Arr_to_uint32(key + 28);
// 填充轮计数器,用于确保一次会话中的每个数据块生成的流密码均不同
state[12] = count;
// 填充nonce,用于确保每个会话生成的流密码不同
state[13] = convert_uint8Arr_to_uint32(nonce + 0);
state[14] = convert_uint8Arr_to_uint32(nonce + 4);
state[15] = convert_uint8Arr_to_uint32(nonce + 8);
}
void ChaCha20_gen(uint32_t *in, uint32_t *out) {
for (int i = 0; i < 16; i++)
out[i] = in[i];
// 十轮混合函数,前4个是列混合,后四个是对角线混合
for (int i = 0; i < 10; i++) {
QR(out[0], out[4], out[8], out[12]);
QR(out[1], out[5], out[9], out[13]);
QR(out[2], out[6], out[10], out[14]);
QR(out[3], out[7], out[11], out[15]);
QR(out[0], out[5], out[10], out[15]);
QR(out[1], out[6], out[11], out[12]);
QR(out[2], out[7], out[8], out[13]);
QR(out[3], out[4], out[9], out[14]);
}
// 将打乱前的矩阵叠加到打乱后的矩阵上,防止差分攻击
for (int i = 0; i < 16; i++)
out[i] += in[i];
}
void ChaCha20_enc(uint8_t *input, size_t len, uint8_t *key, uint8_t *nonce,
uint32_t count = 0) {
int idx = 0;
ChaCha20_init(key, nonce, count);
for (size_t i = 0; i < len; i++) {
if (idx % 64 == 0) {
ChaCha20_gen(state, keyStream);
state[12]++;
idx = 0;
}
// 应用流密码
input[i] ^= ((uint8_t *)keyStream)[idx];
idx++;
}
}
int main() {
char key[33] = "Eleven11________________________";
uint8_t nonce[12] = {0x12, 0x34, 0x56, 0x78, 0x12, 0x34,
0x56, 0x78, 0x12, 0x34, 0x56, 0x78};
char plaintext[] = "Hello, ChaCha20!";
size_t len = sizeof(plaintext) - 1;

ChaCha20_enc((uint8_t *)plaintext, len, (uint8_t *)key, nonce);
for (size_t i = 0; i < len; i++)
printf("%02X ", (uint8_t)plaintext[i]);
ChaCha20_enc((uint8_t *)plaintext, len, (uint8_t *)key, nonce);
printf("\n%s\n", plaintext);
}

reference

ChaCha20-wikipedia
ChaCha20

对称加密目录

Author

SGSG

Posted on

2025-10-27

Updated on

2025-11-02

Licensed under