forked from TheAlgorithms/Rust
-
Notifications
You must be signed in to change notification settings - Fork 1
/
chacha.rs
149 lines (142 loc) · 5.51 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
/*
* 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.
*/
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` 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.
*
* +----+----+----+----+
* | 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.
*
* +------+------+------+------+
* | | | | |
* | 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"
)
);
}
}