dcrypt_algorithms/stream/chacha/chacha20/
mod.rs1use crate::error::{Error, Result};
6use crate::types::nonce::ChaCha20Compatible;
7use crate::types::Nonce;
8use dcrypt_common::security::{EphemeralSecret, SecretBuffer};
9use dcrypt_internal::zeroing::{Zeroize, ZeroizeOnDrop, Zeroizing};
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)]
20pub struct ChaCha20 {
21 state: [u32; 16],
23 buffer: [u8; CHACHA20_BLOCK_SIZE],
25 position: usize,
27 counter: u32,
29 exhausted: bool,
31}
32
33impl Zeroize for ChaCha20 {
34 fn zeroize(&mut self) {
35 self.state.zeroize();
36 self.buffer.zeroize();
37 self.position.zeroize();
38 self.counter.zeroize();
39 self.exhausted.zeroize();
40 }
41}
42
43impl Drop for ChaCha20 {
44 fn drop(&mut self) {
45 self.zeroize();
46 }
47}
48
49impl ZeroizeOnDrop for ChaCha20 {}
50
51impl ChaCha20 {
52 pub fn new<const N: usize>(key: &[u8; CHACHA20_KEY_SIZE], nonce: &Nonce<N>) -> Self
54 where
55 Nonce<N>: ChaCha20Compatible,
56 {
57 let key_buf = SecretBuffer::new(*key);
59 Self::with_counter_secure(&key_buf, nonce, 0)
60 }
61
62 pub fn with_counter<const N: usize>(
64 key: &[u8; CHACHA20_KEY_SIZE],
65 nonce: &Nonce<N>,
66 counter: u32,
67 ) -> Self
68 where
69 Nonce<N>: ChaCha20Compatible,
70 {
71 let key_buf = SecretBuffer::new(*key);
73 Self::with_counter_secure(&key_buf, nonce, counter)
74 }
75
76 fn with_counter_secure<const N: usize>(
78 key: &SecretBuffer<CHACHA20_KEY_SIZE>,
79 nonce: &Nonce<N>,
80 counter: u32,
81 ) -> Self
82 where
83 Nonce<N>: ChaCha20Compatible,
84 {
85 let mut state = Zeroizing::new([0u32; 16]);
87
88 state[0] = 0x61707865;
90 state[1] = 0x3320646e;
91 state[2] = 0x79622d32;
92 state[3] = 0x6b206574;
93
94 let key_bytes = key.as_ref();
96 for i in 0..8 {
97 state[4 + i] =
98 u32::from_le_bytes(key_bytes[i * 4..i * 4 + 4].try_into().expect("four bytes"));
99 }
100
101 state[12] = counter;
103
104 let nonce_bytes = nonce.as_ref();
106 state[13] = u32::from_le_bytes(nonce_bytes[0..4].try_into().expect("four bytes"));
107 state[14] = u32::from_le_bytes(nonce_bytes[4..8].try_into().expect("four bytes"));
108 state[15] = u32::from_le_bytes(nonce_bytes[8..12].try_into().expect("four bytes"));
109
110 let instance = Self {
111 state: state.into_inner(),
112 buffer: [0; CHACHA20_BLOCK_SIZE],
113 position: CHACHA20_BLOCK_SIZE, counter,
115 exhausted: false,
116 };
117 instance
118 }
119
120 #[inline]
122 fn quarter_round(state: &mut [u32], a: usize, b: usize, c: usize, d: usize) {
123 state[a] = state[a].wrapping_add(state[b]);
124 state[d] ^= state[a];
125 state[d] = state[d].rotate_left(16);
126
127 state[c] = state[c].wrapping_add(state[d]);
128 state[b] ^= state[c];
129 state[b] = state[b].rotate_left(12);
130
131 state[a] = state[a].wrapping_add(state[b]);
132 state[d] ^= state[a];
133 state[d] = state[d].rotate_left(8);
134
135 state[c] = state[c].wrapping_add(state[d]);
136 state[b] ^= state[c];
137 state[b] = state[b].rotate_left(7);
138 }
139
140 fn generate_keystream(&mut self) -> Result<()> {
142 if self.exhausted {
143 return Err(Error::Processing {
144 operation: "ChaCha20",
145 details: "block counter exhausted",
146 });
147 }
148
149 let mut working_state = self.state;
151
152 working_state[12] = self.counter;
154
155 for _ in 0..10 {
157 Self::quarter_round(&mut working_state, 0, 4, 8, 12);
159 Self::quarter_round(&mut working_state, 1, 5, 9, 13);
160 Self::quarter_round(&mut working_state, 2, 6, 10, 14);
161 Self::quarter_round(&mut working_state, 3, 7, 11, 15);
162
163 Self::quarter_round(&mut working_state, 0, 5, 10, 15);
165 Self::quarter_round(&mut working_state, 1, 6, 11, 12);
166 Self::quarter_round(&mut working_state, 2, 7, 8, 13);
167 Self::quarter_round(&mut working_state, 3, 4, 9, 14);
168 }
169
170 let mut output_state = EphemeralSecret::new([0u32; 16]);
173 for i in 0..16 {
174 let original_val = if i == 12 { self.counter } else { self.state[i] };
175 output_state[i] = working_state[i].wrapping_add(original_val);
176 }
177
178 for i in 0..16 {
180 self.buffer[i * 4..i * 4 + 4].copy_from_slice(&output_state[i].to_le_bytes());
181 }
182 working_state.zeroize();
183
184 self.position = 0;
186 if self.counter == u32::MAX {
187 self.exhausted = true;
188 } else {
189 self.counter += 1;
190 }
191 Ok(())
192 }
193
194 fn ensure_capacity(&self, data_len: usize) -> Result<()> {
195 let buffered = if self.position < CHACHA20_BLOCK_SIZE {
196 CHACHA20_BLOCK_SIZE - self.position
197 } else {
198 0
199 };
200 let bytes_requiring_blocks = data_len.saturating_sub(buffered);
201 let blocks_required = bytes_requiring_blocks.div_ceil(CHACHA20_BLOCK_SIZE) as u64;
202 let blocks_available = if self.exhausted {
203 0
204 } else {
205 u64::from(u32::MAX - self.counter) + 1
206 };
207
208 if blocks_required > blocks_available {
209 return Err(Error::Processing {
210 operation: "ChaCha20",
211 details: "message would wrap the block counter",
212 });
213 }
214 Ok(())
215 }
216
217 pub fn process(&mut self, data: &mut [u8]) -> Result<()> {
219 self.ensure_capacity(data.len())?;
220 for byte in data.iter_mut() {
221 if self.position >= CHACHA20_BLOCK_SIZE {
223 self.generate_keystream()?;
224 }
225
226 *byte ^= self.buffer[self.position];
228 self.position += 1;
229 }
230 Ok(())
231 }
232
233 pub fn encrypt(&mut self, data: &mut [u8]) -> Result<()> {
235 self.process(data)
236 }
237
238 pub fn decrypt(&mut self, data: &mut [u8]) -> Result<()> {
240 self.process(data)
241 }
242
243 pub fn keystream(&mut self, output: &mut [u8]) -> Result<()> {
245 for byte in output.iter_mut() {
247 *byte = 0;
248 }
249
250 self.position = CHACHA20_BLOCK_SIZE;
252
253 self.process(output)
255 }
256
257 pub fn seek(&mut self, block_offset: u32) -> Result<()> {
262 self.counter = block_offset.checked_add(1).ok_or(Error::Processing {
264 operation: "ChaCha20 seek",
265 details: "block offset would wrap the counter",
266 })?;
267 self.exhausted = false;
268
269 self.position = CHACHA20_BLOCK_SIZE;
271
272 self.buffer.zeroize();
274 Ok(())
275 }
276
277 pub fn reset(&mut self) {
279 self.counter = self.state[12]; self.exhausted = false;
281 self.position = CHACHA20_BLOCK_SIZE; self.buffer.zeroize(); }
284}
285
286pub(crate) fn hchacha20(
291 key: &[u8; CHACHA20_KEY_SIZE],
292 nonce: &[u8; 16],
293) -> Zeroizing<[u8; CHACHA20_KEY_SIZE]> {
294 let mut state = Zeroizing::new([0u32; 16]);
295 state[0] = 0x6170_7865;
296 state[1] = 0x3320_646e;
297 state[2] = 0x7962_2d32;
298 state[3] = 0x6b20_6574;
299 for i in 0..8 {
300 state[4 + i] = u32::from_le_bytes(key[i * 4..i * 4 + 4].try_into().expect("four bytes"));
301 }
302 for i in 0..4 {
303 state[12 + i] = u32::from_le_bytes(nonce[i * 4..i * 4 + 4].try_into().expect("four bytes"));
304 }
305
306 for _ in 0..10 {
307 ChaCha20::quarter_round(&mut state[..], 0, 4, 8, 12);
308 ChaCha20::quarter_round(&mut state[..], 1, 5, 9, 13);
309 ChaCha20::quarter_round(&mut state[..], 2, 6, 10, 14);
310 ChaCha20::quarter_round(&mut state[..], 3, 7, 11, 15);
311 ChaCha20::quarter_round(&mut state[..], 0, 5, 10, 15);
312 ChaCha20::quarter_round(&mut state[..], 1, 6, 11, 12);
313 ChaCha20::quarter_round(&mut state[..], 2, 7, 8, 13);
314 ChaCha20::quarter_round(&mut state[..], 3, 4, 9, 14);
315 }
316
317 let words = Zeroizing::new([
318 state[0], state[1], state[2], state[3], state[12], state[13], state[14], state[15],
319 ]);
320 let mut out = Zeroizing::new([0u8; CHACHA20_KEY_SIZE]);
321 for (chunk, word) in out.chunks_exact_mut(4).zip(words.iter().copied()) {
322 chunk[0] = word as u8;
323 chunk[1] = (word >> 8) as u8;
324 chunk[2] = (word >> 16) as u8;
325 chunk[3] = (word >> 24) as u8;
326 }
327 out
328}
329
330#[cfg(test)]
331mod tests;