1mod core;
2pub use core::{permute, xor_bytes, Block, CHACHA20_NONCE_SIZE, CONSTANTS, STATE_WORDS};
3
4use crate::EncryptionAlgorithm;
5
6use super::{AlgorithmKeyIVInit, AlgorithmProcess, AlgorithmProcessInPlace};
7
8use rayon::prelude::*;
9
10#[derive(Clone)]
12pub struct ChaCha20 {
13 state: Block,
14}
15
16impl ChaCha20 {
17 pub fn new() -> Self {
24 Self {
25 state: [0u32; STATE_WORDS],
26 }
27 }
28
29 pub fn next_keystream(&mut self) -> [u8; 64] {
58 assert!(self.state[12] != 0, "ChaCha20 counter overflow");
60
61 let mut keystream = [0u8; 64];
63
64 let block = permute(&self.state);
66
67 self.state[12] = self.state[12].wrapping_add(1);
69
70 for (bytes, word) in keystream.chunks_exact_mut(4).zip(block) {
72 bytes.copy_from_slice(&word.to_le_bytes());
73 }
74
75 keystream
77 }
78
79 pub fn keystream_at(&self, counter: u32) -> [u8; 64] {
80 let mut keystream = [0u8; 64];
82
83 let mut state = self.state;
85 state[12] = counter;
86
87 let block = permute(&state);
89
90 for (bytes, word) in keystream.chunks_exact_mut(4).zip(block) {
92 bytes.copy_from_slice(&word.to_le_bytes());
93 }
94
95 keystream
97 }
98}
99
100impl AlgorithmKeyIVInit for ChaCha20 {
101 fn init(&mut self, key: &[u8], iv: &[u8]) {
110 assert!(key.len() == 32);
112 assert!(iv.len() == CHACHA20_NONCE_SIZE);
113
114 self.state[0..4].copy_from_slice(&CONSTANTS);
117
118 let key_chunks = key.chunks_exact(4);
122 for (val, chunk) in self.state[4..12].iter_mut().zip(key_chunks) {
123 *val = u32::from_le_bytes(chunk.try_into().unwrap());
124 }
125
126 self.state[12] = 1;
129
130 let iv_chunks = iv.chunks_exact(4);
132 for (val, chunk) in self.state[13..16].iter_mut().zip(iv_chunks) {
133 *val = u32::from_le_bytes(chunk.try_into().unwrap());
134 }
135 }
136}
137
138impl AlgorithmProcess for ChaCha20 {
139 fn process(&mut self, bytes_in: &[u8]) -> Vec<u8> {
169 let mut out = bytes_in.to_owned();
171
172 out
174 .par_chunks_mut(64 * 100)
175 .enumerate()
176 .for_each(|(i, par_chunk)| {
177 par_chunk.chunks_mut(64).enumerate().for_each(|(j, chunk)| {
178 let keystream = self.keystream_at(((i * 100) + j + 1) as u32);
180 xor_bytes(chunk, &keystream);
182 });
183 });
184
185 out.to_vec()
187 }
188}
189
190impl AlgorithmProcessInPlace for ChaCha20 {
191 fn process_in_place(&self, bytes: &mut [u8]) {
199 bytes
201 .par_chunks_mut(64 * 100)
202 .enumerate()
203 .for_each(|(i, par_chunk)| {
204 par_chunk.chunks_mut(64).enumerate().for_each(|(j, chunk)| {
206 let keystream = self.keystream_at(((i * 100) + j + 1) as u32);
208 xor_bytes(chunk, &keystream);
210 });
211 });
212 }
213}
214
215impl EncryptionAlgorithm for ChaCha20 {}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 const PLAINTEXT: [u8; 114] = [
222 0x4c, 0x61, 0x64, 0x69, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x47, 0x65, 0x6e, 0x74, 0x6c,
223 0x65, 0x6d, 0x65, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73,
224 0x73, 0x20, 0x6f, 0x66, 0x20, 0x27, 0x39, 0x39, 0x3a, 0x20, 0x49, 0x66, 0x20, 0x49, 0x20, 0x63,
225 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f,
226 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x74, 0x69, 0x70, 0x20, 0x66, 0x6f, 0x72, 0x20,
227 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, 0x2c, 0x20, 0x73, 0x75, 0x6e, 0x73,
228 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69,
229 0x74, 0x2e,
230 ];
231 const KEY: [u8; 32] = [
232 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
233 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
234 ];
235 const CIPHERTEXT: [u8; 114] = [
236 0x6e, 0x2e, 0x35, 0x9a, 0x25, 0x68, 0xf9, 0x80, 0x41, 0xba, 0x07, 0x28, 0xdd, 0x0d, 0x69, 0x81,
237 0xe9, 0x7e, 0x7a, 0xec, 0x1d, 0x43, 0x60, 0xc2, 0x0a, 0x27, 0xaf, 0xcc, 0xfd, 0x9f, 0xae, 0x0b,
238 0xf9, 0x1b, 0x65, 0xc5, 0x52, 0x47, 0x33, 0xab, 0x8f, 0x59, 0x3d, 0xab, 0xcd, 0x62, 0xb3, 0x57,
239 0x16, 0x39, 0xd6, 0x24, 0xe6, 0x51, 0x52, 0xab, 0x8f, 0x53, 0x0c, 0x35, 0x9f, 0x08, 0x61, 0xd8,
240 0x07, 0xca, 0x0d, 0xbf, 0x50, 0x0d, 0x6a, 0x61, 0x56, 0xa3, 0x8e, 0x08, 0x8a, 0x22, 0xb6, 0x5e,
241 0x52, 0xbc, 0x51, 0x4d, 0x16, 0xcc, 0xf8, 0x06, 0x81, 0x8c, 0xe9, 0x1a, 0xb7, 0x79, 0x37, 0x36,
242 0x5a, 0xf9, 0x0b, 0xbf, 0x74, 0xa3, 0x5b, 0xe6, 0xb4, 0x0b, 0x8e, 0xed, 0xf2, 0x78, 0x5e, 0x42,
243 0x87, 0x4d,
244 ];
245 const IV: [u8; CHACHA20_NONCE_SIZE] = [
246 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
247 ];
248
249 #[test]
250 fn it_correctly_inits_the_chacha20_state() {
251 let mut chacha20 = ChaCha20::new();
252 chacha20.init(&KEY, &IV);
253
254 assert_eq!(
255 chacha20.state,
256 [
257 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, 0x03020100, 0x07060504, 0x0b0a0908,
258 0x0f0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x00000001, 0x09000000,
259 0x4a000000, 0x00000000
260 ]
261 );
262 }
263
264 #[test]
265 fn it_gets_the_first_keystream() {
266 let mut chacha20 = ChaCha20::new();
267 chacha20.init(&KEY, &IV);
268
269 let block = chacha20.next_keystream();
270
271 assert_eq!(
272 block,
273 [
274 0x10, 0xf1, 0xe7, 0xe4, 0xd1, 0x3b, 0x59, 0x15, 0x50, 0x0f, 0xdd, 0x1f, 0xa3, 0x20, 0x71,
275 0xc4, 0xc7, 0xd1, 0xf4, 0xc7, 0x33, 0xc0, 0x68, 0x03, 0x04, 0x22, 0xaa, 0x9a, 0xc3, 0xd4,
276 0x6c, 0x4e, 0xd2, 0x82, 0x64, 0x46, 0x07, 0x9f, 0xaa, 0x09, 0x14, 0xc2, 0xd7, 0x05, 0xd9,
277 0x8b, 0x02, 0xa2, 0xb5, 0x12, 0x9c, 0xd1, 0xde, 0x16, 0x4e, 0xb9, 0xcb, 0xd0, 0x83, 0xe8,
278 0xa2, 0x50, 0x3c, 0x4e,
279 ]
280 );
281 }
282
283 #[test]
284 fn it_encrypts_data() {
285 let mut chacha20 = ChaCha20::new();
286 chacha20.init(
287 &KEY,
288 &[
289 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x00,
290 ],
291 );
292
293 let encrypted_data = chacha20.process(&PLAINTEXT);
294
295 assert_eq!(encrypted_data, CIPHERTEXT);
296 }
297
298 #[test]
299 fn it_can_reverse_encryption() {
300 let mut chacha20 = ChaCha20::new();
301 chacha20.init(&[1u8; 32], &[2u8; CHACHA20_NONCE_SIZE]);
302 let data = [0u8; 64];
303
304 let encrypted_data = chacha20.process(&data);
305 let decrypted_data = chacha20.process(&encrypted_data);
306
307 assert_eq!(decrypted_data, data);
308 }
309
310 #[test]
311 fn it_can_reverse_encryption_for_data_smaller_than_a_chunk() {
312 let mut chacha20 = ChaCha20::new();
313 chacha20.init(&[1u8; 32], &[2u8; CHACHA20_NONCE_SIZE]);
314 let data = [0u8; 1];
315
316 let encrypted_data = chacha20.process(&data);
317 let decrypted_data = chacha20.process(&encrypted_data);
318
319 assert_eq!(decrypted_data, data);
320 }
321
322 #[test]
323 fn it_process_in_place_as_expected() {
324 let mut chacha20_1 = ChaCha20::new();
325 chacha20_1.init(&[0u8; 32], &[0u8; CHACHA20_NONCE_SIZE]);
326 let mut chacha20_2 = ChaCha20::new();
327 chacha20_2.init(&[0u8; 32], &[0u8; CHACHA20_NONCE_SIZE]);
328 let mut data = [0u8; 64 * 1000];
329 let data2 = data.clone();
330
331 chacha20_1.process_in_place(&mut data);
332 let encrypted_sync = chacha20_2.process(&data2);
333
334 assert_eq!(data.to_vec(), encrypted_sync);
335 }
336}