dcrypt_algorithms/stream/chacha/chacha20/
mod.rs1use crate::types::nonce::ChaCha20Compatible;
6use crate::types::Nonce;
7use byteorder::{ByteOrder, LittleEndian};
8use dcrypt_common::security::{EphemeralSecret, SecretBuffer};
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11pub const CHACHA20_KEY_SIZE: usize = 32;
13pub const CHACHA20_NONCE_SIZE: usize = 12;
15pub const CHACHA20_BLOCK_SIZE: usize = 64;
17
18#[derive(Clone, Zeroize, ZeroizeOnDrop)]
20pub struct ChaCha20 {
21 state: [u32; 16],
23 buffer: [u8; CHACHA20_BLOCK_SIZE],
25 position: usize,
27 counter: u32,
29}
30
31impl ChaCha20 {
32 pub fn new<const N: usize>(key: &[u8; CHACHA20_KEY_SIZE], nonce: &Nonce<N>) -> Self
34 where
35 Nonce<N>: ChaCha20Compatible,
36 {
37 let key_buf = SecretBuffer::new(*key);
39 Self::with_counter_secure(&key_buf, nonce, 0)
40 }
41
42 pub fn with_counter<const N: usize>(
44 key: &[u8; CHACHA20_KEY_SIZE],
45 nonce: &Nonce<N>,
46 counter: u32,
47 ) -> Self
48 where
49 Nonce<N>: ChaCha20Compatible,
50 {
51 let key_buf = SecretBuffer::new(*key);
53 Self::with_counter_secure(&key_buf, nonce, counter)
54 }
55
56 fn with_counter_secure<const N: usize>(
58 key: &SecretBuffer<CHACHA20_KEY_SIZE>,
59 nonce: &Nonce<N>,
60 counter: u32,
61 ) -> Self
62 where
63 Nonce<N>: ChaCha20Compatible,
64 {
65 let mut state = [0u32; 16];
67
68 state[0] = 0x61707865;
70 state[1] = 0x3320646e;
71 state[2] = 0x79622d32;
72 state[3] = 0x6b206574;
73
74 let key_bytes = key.as_ref();
76 for i in 0..8 {
77 state[4 + i] = LittleEndian::read_u32(&key_bytes[i * 4..]);
78 }
79
80 state[12] = counter;
82
83 let nonce_bytes = nonce.as_ref();
85 state[13] = LittleEndian::read_u32(&nonce_bytes[0..4]);
86 state[14] = LittleEndian::read_u32(&nonce_bytes[4..8]);
87 state[15] = LittleEndian::read_u32(&nonce_bytes[8..12]);
88
89 Self {
90 state,
91 buffer: [0; CHACHA20_BLOCK_SIZE],
92 position: CHACHA20_BLOCK_SIZE, counter,
94 }
95 }
96
97 #[inline]
99 fn quarter_round(state: &mut [u32], a: usize, b: usize, c: usize, d: usize) {
100 state[a] = state[a].wrapping_add(state[b]);
101 state[d] ^= state[a];
102 state[d] = state[d].rotate_left(16);
103
104 state[c] = state[c].wrapping_add(state[d]);
105 state[b] ^= state[c];
106 state[b] = state[b].rotate_left(12);
107
108 state[a] = state[a].wrapping_add(state[b]);
109 state[d] ^= state[a];
110 state[d] = state[d].rotate_left(8);
111
112 state[c] = state[c].wrapping_add(state[d]);
113 state[b] ^= state[c];
114 state[b] = state[b].rotate_left(7);
115 }
116
117 fn generate_keystream(&mut self) {
119 let mut working_state = self.state;
121
122 working_state[12] = self.counter;
124
125 for _ in 0..10 {
127 Self::quarter_round(&mut working_state, 0, 4, 8, 12);
129 Self::quarter_round(&mut working_state, 1, 5, 9, 13);
130 Self::quarter_round(&mut working_state, 2, 6, 10, 14);
131 Self::quarter_round(&mut working_state, 3, 7, 11, 15);
132
133 Self::quarter_round(&mut working_state, 0, 5, 10, 15);
135 Self::quarter_round(&mut working_state, 1, 6, 11, 12);
136 Self::quarter_round(&mut working_state, 2, 7, 8, 13);
137 Self::quarter_round(&mut working_state, 3, 4, 9, 14);
138 }
139
140 let mut output_state = EphemeralSecret::new([0u32; 16]);
143 for i in 0..16 {
144 let original_val = if i == 12 { self.counter } else { self.state[i] };
145 output_state[i] = working_state[i].wrapping_add(original_val);
146 }
147
148 for i in 0..16 {
150 LittleEndian::write_u32(&mut self.buffer[i * 4..], output_state[i]);
151 }
152
153 self.position = 0;
155 self.counter = self.counter.wrapping_add(1);
156 }
157
158 pub fn process(&mut self, data: &mut [u8]) {
160 for byte in data.iter_mut() {
161 if self.position >= CHACHA20_BLOCK_SIZE {
163 self.generate_keystream();
164 }
165
166 *byte ^= self.buffer[self.position];
168 self.position += 1;
169 }
170 }
171
172 pub fn encrypt(&mut self, data: &mut [u8]) {
174 self.process(data);
175 }
176
177 pub fn decrypt(&mut self, data: &mut [u8]) {
179 self.process(data);
180 }
181
182 pub fn keystream(&mut self, output: &mut [u8]) {
184 for byte in output.iter_mut() {
186 *byte = 0;
187 }
188
189 self.position = CHACHA20_BLOCK_SIZE;
191
192 self.process(output);
194 }
195
196 pub fn seek(&mut self, block_offset: u32) {
201 self.counter = block_offset.wrapping_add(1);
203
204 self.position = CHACHA20_BLOCK_SIZE;
206
207 self.buffer.zeroize();
209 }
210
211 pub fn reset(&mut self) {
213 self.counter = self.state[12]; self.position = CHACHA20_BLOCK_SIZE; self.buffer.zeroize(); }
217}
218
219#[cfg(test)]
220mod tests;