dcrypt_algorithms/stream/chacha/chacha20/
mod.rs1use crate::error::{Error, Result};
6use crate::types::nonce::ChaCha20Compatible;
7use crate::types::Nonce;
8use byteorder::{ByteOrder, LittleEndian};
9use dcrypt_common::security::{EphemeralSecret, SecretBuffer};
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12pub const CHACHA20_KEY_SIZE: usize = 32;
14pub const CHACHA20_NONCE_SIZE: usize = 12;
16pub const CHACHA20_BLOCK_SIZE: usize = 64;
18
19#[derive(Clone, Zeroize, ZeroizeOnDrop)]
21pub struct ChaCha20 {
22 state: [u32; 16],
24 buffer: [u8; CHACHA20_BLOCK_SIZE],
26 position: usize,
28 counter: u32,
30 exhausted: bool,
32}
33
34impl ChaCha20 {
35 pub fn new<const N: usize>(key: &[u8; CHACHA20_KEY_SIZE], nonce: &Nonce<N>) -> Self
37 where
38 Nonce<N>: ChaCha20Compatible,
39 {
40 let key_buf = SecretBuffer::new(*key);
42 Self::with_counter_secure(&key_buf, nonce, 0)
43 }
44
45 pub fn with_counter<const N: usize>(
47 key: &[u8; CHACHA20_KEY_SIZE],
48 nonce: &Nonce<N>,
49 counter: u32,
50 ) -> Self
51 where
52 Nonce<N>: ChaCha20Compatible,
53 {
54 let key_buf = SecretBuffer::new(*key);
56 Self::with_counter_secure(&key_buf, nonce, counter)
57 }
58
59 fn with_counter_secure<const N: usize>(
61 key: &SecretBuffer<CHACHA20_KEY_SIZE>,
62 nonce: &Nonce<N>,
63 counter: u32,
64 ) -> Self
65 where
66 Nonce<N>: ChaCha20Compatible,
67 {
68 let mut state = [0u32; 16];
70
71 state[0] = 0x61707865;
73 state[1] = 0x3320646e;
74 state[2] = 0x79622d32;
75 state[3] = 0x6b206574;
76
77 let key_bytes = key.as_ref();
79 for i in 0..8 {
80 state[4 + i] = LittleEndian::read_u32(&key_bytes[i * 4..]);
81 }
82
83 state[12] = counter;
85
86 let nonce_bytes = nonce.as_ref();
88 state[13] = LittleEndian::read_u32(&nonce_bytes[0..4]);
89 state[14] = LittleEndian::read_u32(&nonce_bytes[4..8]);
90 state[15] = LittleEndian::read_u32(&nonce_bytes[8..12]);
91
92 let instance = Self {
93 state,
94 buffer: [0; CHACHA20_BLOCK_SIZE],
95 position: CHACHA20_BLOCK_SIZE, counter,
97 exhausted: false,
98 };
99 state.zeroize();
100 instance
101 }
102
103 #[inline]
105 fn quarter_round(state: &mut [u32], a: usize, b: usize, c: usize, d: usize) {
106 state[a] = state[a].wrapping_add(state[b]);
107 state[d] ^= state[a];
108 state[d] = state[d].rotate_left(16);
109
110 state[c] = state[c].wrapping_add(state[d]);
111 state[b] ^= state[c];
112 state[b] = state[b].rotate_left(12);
113
114 state[a] = state[a].wrapping_add(state[b]);
115 state[d] ^= state[a];
116 state[d] = state[d].rotate_left(8);
117
118 state[c] = state[c].wrapping_add(state[d]);
119 state[b] ^= state[c];
120 state[b] = state[b].rotate_left(7);
121 }
122
123 fn generate_keystream(&mut self) -> Result<()> {
125 if self.exhausted {
126 return Err(Error::Processing {
127 operation: "ChaCha20",
128 details: "block counter exhausted",
129 });
130 }
131
132 let mut working_state = self.state;
134
135 working_state[12] = self.counter;
137
138 for _ in 0..10 {
140 Self::quarter_round(&mut working_state, 0, 4, 8, 12);
142 Self::quarter_round(&mut working_state, 1, 5, 9, 13);
143 Self::quarter_round(&mut working_state, 2, 6, 10, 14);
144 Self::quarter_round(&mut working_state, 3, 7, 11, 15);
145
146 Self::quarter_round(&mut working_state, 0, 5, 10, 15);
148 Self::quarter_round(&mut working_state, 1, 6, 11, 12);
149 Self::quarter_round(&mut working_state, 2, 7, 8, 13);
150 Self::quarter_round(&mut working_state, 3, 4, 9, 14);
151 }
152
153 let mut output_state = EphemeralSecret::new([0u32; 16]);
156 for i in 0..16 {
157 let original_val = if i == 12 { self.counter } else { self.state[i] };
158 output_state[i] = working_state[i].wrapping_add(original_val);
159 }
160
161 for i in 0..16 {
163 LittleEndian::write_u32(&mut self.buffer[i * 4..], output_state[i]);
164 }
165 working_state.zeroize();
166
167 self.position = 0;
169 if self.counter == u32::MAX {
170 self.exhausted = true;
171 } else {
172 self.counter += 1;
173 }
174 Ok(())
175 }
176
177 fn ensure_capacity(&self, data_len: usize) -> Result<()> {
178 let buffered = if self.position < CHACHA20_BLOCK_SIZE {
179 CHACHA20_BLOCK_SIZE - self.position
180 } else {
181 0
182 };
183 let bytes_requiring_blocks = data_len.saturating_sub(buffered);
184 let blocks_required = bytes_requiring_blocks.div_ceil(CHACHA20_BLOCK_SIZE) as u64;
185 let blocks_available = if self.exhausted {
186 0
187 } else {
188 u64::from(u32::MAX - self.counter) + 1
189 };
190
191 if blocks_required > blocks_available {
192 return Err(Error::Processing {
193 operation: "ChaCha20",
194 details: "message would wrap the block counter",
195 });
196 }
197 Ok(())
198 }
199
200 pub fn process(&mut self, data: &mut [u8]) -> Result<()> {
202 self.ensure_capacity(data.len())?;
203 for byte in data.iter_mut() {
204 if self.position >= CHACHA20_BLOCK_SIZE {
206 self.generate_keystream()?;
207 }
208
209 *byte ^= self.buffer[self.position];
211 self.position += 1;
212 }
213 Ok(())
214 }
215
216 pub fn encrypt(&mut self, data: &mut [u8]) -> Result<()> {
218 self.process(data)
219 }
220
221 pub fn decrypt(&mut self, data: &mut [u8]) -> Result<()> {
223 self.process(data)
224 }
225
226 pub fn keystream(&mut self, output: &mut [u8]) -> Result<()> {
228 for byte in output.iter_mut() {
230 *byte = 0;
231 }
232
233 self.position = CHACHA20_BLOCK_SIZE;
235
236 self.process(output)
238 }
239
240 pub fn seek(&mut self, block_offset: u32) -> Result<()> {
245 self.counter = block_offset.checked_add(1).ok_or(Error::Processing {
247 operation: "ChaCha20 seek",
248 details: "block offset would wrap the counter",
249 })?;
250 self.exhausted = false;
251
252 self.position = CHACHA20_BLOCK_SIZE;
254
255 self.buffer.zeroize();
257 Ok(())
258 }
259
260 pub fn reset(&mut self) {
262 self.counter = self.state[12]; self.exhausted = false;
264 self.position = CHACHA20_BLOCK_SIZE; self.buffer.zeroize(); }
267}
268
269#[cfg(test)]
274pub(crate) fn hchacha20(
275 key: &[u8; CHACHA20_KEY_SIZE],
276 nonce: &[u8; 16],
277) -> [u8; CHACHA20_KEY_SIZE] {
278 let mut state = [0u32; 16];
279 state[0] = 0x6170_7865;
280 state[1] = 0x3320_646e;
281 state[2] = 0x7962_2d32;
282 state[3] = 0x6b20_6574;
283 for i in 0..8 {
284 state[4 + i] = LittleEndian::read_u32(&key[i * 4..i * 4 + 4]);
285 }
286 for i in 0..4 {
287 state[12 + i] = LittleEndian::read_u32(&nonce[i * 4..i * 4 + 4]);
288 }
289
290 for _ in 0..10 {
291 ChaCha20::quarter_round(&mut state, 0, 4, 8, 12);
292 ChaCha20::quarter_round(&mut state, 1, 5, 9, 13);
293 ChaCha20::quarter_round(&mut state, 2, 6, 10, 14);
294 ChaCha20::quarter_round(&mut state, 3, 7, 11, 15);
295 ChaCha20::quarter_round(&mut state, 0, 5, 10, 15);
296 ChaCha20::quarter_round(&mut state, 1, 6, 11, 12);
297 ChaCha20::quarter_round(&mut state, 2, 7, 8, 13);
298 ChaCha20::quarter_round(&mut state, 3, 4, 9, 14);
299 }
300
301 let words = [
302 state[0], state[1], state[2], state[3], state[12], state[13], state[14], state[15],
303 ];
304 let mut out = [0u8; CHACHA20_KEY_SIZE];
305 for (chunk, word) in out.chunks_exact_mut(4).zip(words) {
306 LittleEndian::write_u32(chunk, word);
307 }
308 state.zeroize();
309 out
310}
311
312#[cfg(test)]
313mod tests;