dcrypt_algorithms/stream/chacha/chacha20/
mod.rs

1//! ChaCha20 stream cipher implementation
2//!
3//! This module implements the ChaCha20 stream cipher as defined in RFC 8439.
4
5use crate::types::nonce::ChaCha20Compatible;
6use crate::types::Nonce;
7use byteorder::{ByteOrder, LittleEndian};
8use dcrypt_common::security::{EphemeralSecret, SecretBuffer};
9use zeroize::{Zeroize, ZeroizeOnDrop};
10
11/// Size of ChaCha20 key in bytes
12pub const CHACHA20_KEY_SIZE: usize = 32;
13/// Size of ChaCha20 nonce in bytes
14pub const CHACHA20_NONCE_SIZE: usize = 12;
15/// Size of ChaCha20 block in bytes
16pub const CHACHA20_BLOCK_SIZE: usize = 64;
17
18/// ChaCha20 stream cipher
19#[derive(Clone, Zeroize, ZeroizeOnDrop)]
20pub struct ChaCha20 {
21    /// The key schedule
22    state: [u32; 16],
23    /// Keystream buffer
24    buffer: [u8; CHACHA20_BLOCK_SIZE],
25    /// Current position in the buffer
26    position: usize,
27    /// Current block counter
28    counter: u32,
29}
30
31impl ChaCha20 {
32    /// Creates a new ChaCha20 instance with the specified key and nonce
33    pub fn new<const N: usize>(key: &[u8; CHACHA20_KEY_SIZE], nonce: &Nonce<N>) -> Self
34    where
35        Nonce<N>: ChaCha20Compatible,
36    {
37        // Wrap key in SecretBuffer for secure handling
38        let key_buf = SecretBuffer::new(*key);
39        Self::with_counter_secure(&key_buf, nonce, 0)
40    }
41
42    /// Creates a new ChaCha20 instance with the specified key, nonce, and counter
43    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        // Wrap key in SecretBuffer for secure handling
52        let key_buf = SecretBuffer::new(*key);
53        Self::with_counter_secure(&key_buf, nonce, counter)
54    }
55
56    /// Internal method that works with SecretBuffer for secure key handling
57    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        // Initialize state with constants and key
66        let mut state = [0u32; 16];
67
68        // "expand 32-byte k" in little-endian
69        state[0] = 0x61707865;
70        state[1] = 0x3320646e;
71        state[2] = 0x79622d32;
72        state[3] = 0x6b206574;
73
74        // Key (8 words) - use secure key access
75        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        // Counter (1 word)
81        state[12] = counter;
82
83        // Nonce (3 words)
84        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, // Force initial keystream generation
93            counter,
94        }
95    }
96
97    /// The ChaCha20 quarter round function
98    #[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    /// Generate a block of keystream
118    fn generate_keystream(&mut self) {
119        // Create a working copy of the state
120        let mut working_state = self.state;
121
122        // Ensure the current counter is set in the working state
123        working_state[12] = self.counter;
124
125        // 20 rounds of ChaCha20: 10 column rounds, 10 diagonal rounds
126        for _ in 0..10 {
127            // Column rounds
128            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            // Diagonal rounds
134            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        // Create output by adding the working state to the original state
141        // Use EphemeralSecret to ensure intermediate values are zeroized
142        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        // Convert to bytes (little-endian)
149        for i in 0..16 {
150            LittleEndian::write_u32(&mut self.buffer[i * 4..], output_state[i]);
151        }
152
153        // Reset position and increment counter for next block
154        self.position = 0;
155        self.counter = self.counter.wrapping_add(1);
156    }
157
158    /// Encrypt or decrypt data in place using the ChaCha20 stream cipher
159    pub fn process(&mut self, data: &mut [u8]) {
160        for byte in data.iter_mut() {
161            // Generate new keystream block if needed
162            if self.position >= CHACHA20_BLOCK_SIZE {
163                self.generate_keystream();
164            }
165
166            // XOR data with keystream
167            *byte ^= self.buffer[self.position];
168            self.position += 1;
169        }
170    }
171
172    /// Encrypt data in place
173    pub fn encrypt(&mut self, data: &mut [u8]) {
174        self.process(data);
175    }
176
177    /// Decrypt data in place
178    pub fn decrypt(&mut self, data: &mut [u8]) {
179        self.process(data);
180    }
181
182    /// Generate keystream directly into an output buffer
183    pub fn keystream(&mut self, output: &mut [u8]) {
184        // Zero the output buffer
185        for byte in output.iter_mut() {
186            *byte = 0;
187        }
188
189        // Force generation from a block boundary (ignore any leftover position)
190        self.position = CHACHA20_BLOCK_SIZE;
191
192        // Then run the encryption pass to copy the keystream
193        self.process(output);
194    }
195
196    /// Seek to a specific block position
197    ///
198    /// `block_offset` is the number of full blocks that have been consumed;
199    /// after seeking, the next generated block will be at `block_offset + 1`.
200    pub fn seek(&mut self, block_offset: u32) {
201        // Set counter so that generate_keystream() yields the next block
202        self.counter = block_offset.wrapping_add(1);
203
204        // Force regeneration on next use
205        self.position = CHACHA20_BLOCK_SIZE;
206
207        // Clear any old keystream
208        self.buffer.zeroize();
209    }
210
211    /// Reset to initial state with the same key
212    pub fn reset(&mut self) {
213        self.counter = self.state[12]; // Restore original counter
214        self.position = CHACHA20_BLOCK_SIZE; // Force keystream regeneration
215        self.buffer.zeroize(); // Clear keystream buffer
216    }
217}
218
219#[cfg(test)]
220mod tests;