-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
Copy pathchacha.rs
150 lines (144 loc) · 5.62 KB
/
chacha.rs
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
macro_rules! quarter_round {
($a:expr,$b:expr,$c:expr,$d:expr) => {
$a = $a.wrapping_add($b);
$d = ($d ^ $a).rotate_left(16);
$c = $c.wrapping_add($d);
$b = ($b ^ $c).rotate_left(12);
$a = $a.wrapping_add($b);
$d = ($d ^ $a).rotate_left(8);
$c = $c.wrapping_add($d);
$b = ($b ^ $c).rotate_left(7);
};
}
#[allow(dead_code)]
// "expand 32-byte k", written in little-endian order
pub const C: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
/// ChaCha20 implementation based on RFC8439
///
/// ChaCha20 is a stream cipher developed independently by Daniel J. Bernstein.\
/// To use it, the `chacha20` function should be called with appropriate
/// parameters and the output of the function should be XORed with plain text.
///
/// `chacha20` function takes as input an array of 16 32-bit integers (512 bits)
/// of which 128 bits is the constant 'expand 32-byte k', 256 bits is the key,
/// and 128 bits are nonce and counter. According to RFC8439, the nonce should
/// be 96 bits long, which leaves 32 bits for the counter. Given that the block
/// length is 512 bits, this leaves enough counter values to encrypt 256GB of
/// data.
///
/// The 16 input numbers can be thought of as the elements of a 4x4 matrix like
/// the one bellow, on which we do the main operations of the cipher.
///
/// ```text
/// +----+----+----+----+
/// | 00 | 01 | 02 | 03 |
/// +----+----+----+----+
/// | 04 | 05 | 06 | 07 |
/// +----+----+----+----+
/// | 08 | 09 | 10 | 11 |
/// +----+----+----+----+
/// | 12 | 13 | 14 | 15 |
/// +----+----+----+----+
/// ```
///
/// As per the diagram bellow, `input[0, 1, 2, 3]` are the constants mentioned
/// above, `input[4..=11]` is filled with the key, and `input[6..=9]` should be
/// filled with nonce and counter values. The output of the function is stored
/// in `output` variable and can be XORed with the plain text to produce the
/// cipher text.
///
/// ```text
/// +------+------+------+------+
/// | | | | |
/// | C[0] | C[1] | C[2] | C[3] |
/// | | | | |
/// +------+------+------+------+
/// | | | | |
/// | key0 | key1 | key2 | key3 |
/// | | | | |
/// +------+------+------+------+
/// | | | | |
/// | key4 | key5 | key6 | key7 |
/// | | | | |
/// +------+------+------+------+
/// | | | | |
/// | ctr0 | no.0 | no.1 | no.2 |
/// | | | | |
/// +------+------+------+------+
/// ```
///
/// Note that the constants, the key, and the nonce should be written in
/// little-endian order, meaning that for example if the key is 01:02:03:04
/// (in hex), it corresponds to the integer `0x04030201`. It is important to
/// know that the hex value of the counter is meaningless, and only its integer
/// value matters, and it should start with (for example) `0x00000000`, and then
/// `0x00000001` and so on until `0xffffffff`. Keep in mind that as soon as we get
/// from bytes to words, we stop caring about their representation in memory,
/// and we only need the math to be correct.
///
/// The output of the function can be used without any change, as long as the
/// plain text has the same endianness. For example if the plain text is
/// "hello world", and the first word of the output is `0x01020304`, then the
/// first byte of plain text ('h') should be XORed with the least-significant
/// byte of `0x01020304`, which is `0x04`.
pub fn chacha20(input: &[u32; 16], output: &mut [u32; 16]) {
output.copy_from_slice(&input[..]);
for _ in 0..10 {
// Odd round (column round)
quarter_round!(output[0], output[4], output[8], output[12]); // column 1
quarter_round!(output[1], output[5], output[9], output[13]); // column 2
quarter_round!(output[2], output[6], output[10], output[14]); // column 3
quarter_round!(output[3], output[7], output[11], output[15]); // column 4
// Even round (diagonal round)
quarter_round!(output[0], output[5], output[10], output[15]); // diag 1
quarter_round!(output[1], output[6], output[11], output[12]); // diag 2
quarter_round!(output[2], output[7], output[8], output[13]); // diag 3
quarter_round!(output[3], output[4], output[9], output[14]); // diag 4
}
for (a, &b) in output.iter_mut().zip(input.iter()) {
*a = a.wrapping_add(b);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fmt::Write;
fn output_hex(inp: &[u32; 16]) -> String {
let mut res = String::new();
res.reserve(512 / 4);
for &x in inp {
write!(&mut res, "{x:08x}").unwrap();
}
res
}
#[test]
// test vector 1
fn basic_tv1() {
let mut inp = [0u32; 16];
let mut out = [0u32; 16];
inp[0] = C[0];
inp[1] = C[1];
inp[2] = C[2];
inp[3] = C[3];
inp[4] = 0x03020100; // The key is 00:01:02:..:1f (hex)
inp[5] = 0x07060504;
inp[6] = 0x0b0a0908;
inp[7] = 0x0f0e0d0c;
inp[8] = 0x13121110;
inp[9] = 0x17161514;
inp[10] = 0x1b1a1918;
inp[11] = 0x1f1e1d1c;
inp[12] = 0x00000001; // The value of counter is 1 (an integer). Nonce:
inp[13] = 0x09000000; // 00:00:00:09
inp[14] = 0x4a000000; // 00:00:00:4a
inp[15] = 0x00000000; // 00:00:00:00
chacha20(&inp, &mut out);
assert_eq!(
output_hex(&out),
concat!(
"e4e7f11015593bd11fdd0f50c47120a3c7f4d1c70368c0339aaa22044e6cd4c3",
"466482d209aa9f0705d7c214a2028bd9d19c12b5b94e16dee883d0cb4e3c50a2"
)
);
}
}