Skip to main content

dcrypt_algorithms/block/modes/ctr/
mod.rs

1//! Counter (CTR) mode with proper error propagation and secure memory handling
2//!
3//! Counter mode turns a block cipher into a stream cipher by encrypting
4//! successive values of a counter and XORing the result with the plaintext.
5//!
6//! This implementation follows NIST SP 800-38A recommendations for CTR mode,
7//! using a flexible nonce-counter format with secure memory handling.
8
9#[cfg(not(feature = "std"))]
10use alloc::vec::Vec;
11use dcrypt_internal::zeroing::{
12    boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
13};
14
15use super::super::BlockCipher;
16use crate::error::{validate, Result};
17use crate::types::nonce::AesCtrCompatible;
18use crate::types::Nonce;
19
20// Import security types for memory safety
21use dcrypt_common::security::barrier;
22
23/// Counter position within the counter block
24#[derive(Debug, Clone, Copy, PartialEq)]
25pub enum CounterPosition {
26    /// Counter is placed at the beginning of the block (bytes 0 to counter_size-1)
27    /// This is common in some implementations, especially with 8-byte counters
28    Prefix,
29
30    /// Counter is placed at the end of the block (last counter_size bytes)
31    /// This is the most common arrangement for AES-CTR
32    Postfix,
33
34    /// Counter is placed at a specific offset within the block
35    /// Allows for custom layouts
36    Custom(usize),
37}
38
39/// Counter mode implementation with secure memory handling
40#[derive(Clone)]
41pub struct Ctr<B: BlockCipher + Zeroize> {
42    cipher: B,
43    counter_block: ZeroizingBytes,
44    counter_position: usize,
45    counter_size: usize,
46    keystream: ZeroizingBytes,
47    keystream_pos: usize,
48}
49
50impl<B: BlockCipher + Zeroize> Zeroize for Ctr<B> {
51    fn zeroize(&mut self) {
52        self.cipher.zeroize();
53        self.counter_block.zeroize();
54        self.counter_position.zeroize();
55        self.counter_size.zeroize();
56        self.keystream.zeroize();
57        self.keystream_pos.zeroize();
58    }
59}
60
61impl<B: BlockCipher + Zeroize> Drop for Ctr<B> {
62    fn drop(&mut self) {
63        self.zeroize();
64    }
65}
66
67impl<B: BlockCipher + Zeroize> ZeroizeOnDrop for Ctr<B> {}
68
69impl<B: BlockCipher + Zeroize> Ctr<B> {
70    /// Creates a new CTR mode instance with the default configuration
71    ///
72    /// * `cipher` - The block cipher to use
73    /// * `nonce` - The nonce (must be compatible with CTR mode)
74    ///
75    /// This creates a standard CTR mode with the counter in the last 4 bytes
76    /// and the nonce filling the beginning of the counter block.
77    pub fn new<const N: usize>(cipher: B, nonce: &Nonce<N>) -> Result<Self>
78    where
79        Nonce<N>: AesCtrCompatible,
80    {
81        // Standard CTR mode with 4-byte counter at the end
82        Self::with_counter_params(cipher, nonce, CounterPosition::Postfix, 4)
83    }
84
85    /// Creates a new CTR mode instance with custom counter parameters
86    ///
87    /// * `cipher` - The block cipher to use
88    /// * `nonce` - The nonce (must be compatible with CTR mode)
89    /// * `counter_pos` - Position of the counter within the counter block
90    /// * `counter_size` - Size of the counter in bytes (1-8)
91    ///
92    /// This allows for flexible counter block layouts to match different standards
93    /// and implementations.
94    pub fn with_counter_params<const N: usize>(
95        cipher: B,
96        nonce: &Nonce<N>,
97        counter_pos: CounterPosition,
98        counter_size: usize,
99    ) -> Result<Self>
100    where
101        Nonce<N>: AesCtrCompatible,
102    {
103        let block_size = B::block_size();
104
105        // Validate counter size (1-8 bytes for u64 counter)
106        validate::parameter(
107            counter_size > 0 && counter_size <= 8,
108            "counter_size",
109            "Counter size must be between 1 and 8 bytes",
110        )?;
111
112        // Determine the counter position
113        let position = match counter_pos {
114            CounterPosition::Prefix => 0,
115            CounterPosition::Postfix => block_size - counter_size,
116            CounterPosition::Custom(offset) => {
117                validate::parameter(
118                    offset + counter_size <= block_size,
119                    "counter_position",
120                    "Counter with specified size doesn't fit at offset in block",
121                )?;
122                offset
123            }
124        };
125
126        // Create and initialize the counter block with Zeroizing
127        let mut counter_block = Zeroizing::new(boxed_bytes_zeroed(block_size));
128
129        // Handle nonce according to its size
130        let max_nonce_size = block_size - counter_size;
131
132        // If nonce is too large, truncate it
133        let effective_nonce = if N > max_nonce_size {
134            &nonce.as_ref()[0..max_nonce_size]
135        } else {
136            nonce.as_ref()
137        };
138
139        // Fill in the nonce
140        if position == 0 {
141            // Counter is at the beginning, place nonce after it
142            counter_block[counter_size..counter_size + effective_nonce.len()]
143                .copy_from_slice(effective_nonce);
144        } else {
145            // Counter is elsewhere, place nonce at the beginning by default
146            counter_block[0..effective_nonce.len()].copy_from_slice(effective_nonce);
147        }
148
149        Ok(Self {
150            cipher,
151            counter_block,
152            counter_position: position,
153            counter_size,
154            keystream: Zeroizing::new(boxed_bytes_zeroed(0)),
155            keystream_pos: 0,
156        })
157    }
158
159    /// Generate keystream for CTR mode with secure memory handling
160    fn generate_keystream(&mut self) -> Result<()> {
161        let block_size = B::block_size();
162
163        // Create a new zeroizing keystream buffer
164        self.keystream = Zeroizing::new(boxed_bytes_zeroed(block_size));
165
166        // Use memory barrier to prevent optimization
167        barrier::compiler_fence_seq_cst();
168
169        // Copy current counter block to keystream
170        self.keystream.copy_from_slice(&self.counter_block);
171
172        // Encrypt the counter value
173        self.cipher.encrypt_block(&mut self.keystream)?;
174
175        // Increment the counter based on its size
176        self.increment_counter();
177
178        self.keystream_pos = 0;
179
180        // Use memory barrier after operation
181        barrier::compiler_fence_seq_cst();
182
183        Ok(())
184    }
185
186    /// Increment the counter in the counter block
187    fn increment_counter(&mut self) {
188        match self.counter_size {
189            8 => {
190                let mut counter = [0u8; 8];
191                counter.copy_from_slice(
192                    &self.counter_block[self.counter_position..self.counter_position + 8],
193                );
194                let value = u64::from_be_bytes(counter);
195                counter.copy_from_slice(&value.wrapping_add(1).to_be_bytes());
196                self.counter_block[self.counter_position..self.counter_position + 8]
197                    .copy_from_slice(&counter);
198
199                // Zeroize the temporary counter array
200                counter.zeroize();
201            }
202            4 => {
203                let mut counter = [0u8; 4];
204                counter.copy_from_slice(
205                    &self.counter_block[self.counter_position..self.counter_position + 4],
206                );
207                let value = u32::from_be_bytes(counter);
208                counter.copy_from_slice(&value.wrapping_add(1).to_be_bytes());
209                self.counter_block[self.counter_position..self.counter_position + 4]
210                    .copy_from_slice(&counter);
211
212                // Zeroize the temporary counter array
213                counter.zeroize();
214            }
215            // For other counter sizes, we'll read/write the appropriate number of bytes
216            size => {
217                let mut value: u64 = 0;
218
219                // Read counter value (big-endian)
220                for i in 0..size {
221                    value = (value << 8) | (self.counter_block[self.counter_position + i] as u64);
222                }
223
224                // Increment counter
225                value = value.wrapping_add(1);
226
227                // Write counter value back (big-endian)
228                for i in 0..size {
229                    self.counter_block[self.counter_position + size - 1 - i] = (value & 0xff) as u8;
230                    value >>= 8;
231                }
232            }
233        }
234    }
235
236    /// Encrypts a message using CTR mode
237    pub fn encrypt(&mut self, plaintext: &[u8]) -> Result<Vec<u8>> {
238        let mut ciphertext = Zeroizing::new(boxed_bytes_zeroed(plaintext.len()));
239
240        // Use memory barrier before sensitive operations
241        barrier::compiler_fence_seq_cst();
242
243        for (output, &byte) in ciphertext.iter_mut().zip(plaintext) {
244            if self.keystream_pos >= self.keystream.len() {
245                self.generate_keystream()?;
246            }
247
248            *output = byte ^ self.keystream[self.keystream_pos];
249            self.keystream_pos += 1;
250        }
251
252        // Use memory barrier after sensitive operations
253        barrier::compiler_fence_seq_cst();
254
255        Ok(ciphertext.into_inner().into_vec())
256    }
257
258    /// Decrypts a message using CTR mode
259    /// In CTR mode, encryption and decryption are the same operation
260    pub fn decrypt(&mut self, ciphertext: &[u8]) -> Result<Vec<u8>> {
261        self.encrypt(ciphertext)
262    }
263
264    /// Process data in place (encrypt or decrypt)
265    pub fn process(&mut self, data: &mut [u8]) -> Result<()> {
266        // Use memory barrier before sensitive operations
267        barrier::compiler_fence_seq_cst();
268
269        for byte in data.iter_mut() {
270            // Generate new keystream block if needed
271            if self.keystream_pos >= self.keystream.len() {
272                self.generate_keystream()?;
273            }
274
275            // XOR data with keystream
276            *byte ^= self.keystream[self.keystream_pos];
277            self.keystream_pos += 1;
278        }
279
280        // Use memory barrier after sensitive operations
281        barrier::compiler_fence_seq_cst();
282
283        Ok(())
284    }
285
286    /// Generate keystream directly into an output buffer
287    pub fn keystream(&mut self, output: &mut [u8]) -> Result<()> {
288        // Zero the output buffer
289        for byte in output.iter_mut() {
290            *byte = 0;
291        }
292
293        // Force generation from a block boundary (ignore any leftover position)
294        self.keystream_pos = self.keystream.len();
295
296        // Then run the encryption pass to copy the keystream
297        self.process(output)
298    }
299
300    /// Seek to a specific block position
301    ///
302    /// `block_offset` is the number of full blocks that have been consumed;
303    /// after seeking, the next generated block will be at `block_offset + 1`.
304    pub fn seek(&mut self, block_offset: u32) {
305        // Calculate the counter value based on the offset
306        let mut counter_value = [0u8; 8];
307        counter_value[4..].copy_from_slice(&block_offset.wrapping_add(1).to_be_bytes());
308
309        // Update counter in the counter block
310        for i in 0..self.counter_size {
311            let idx = self.counter_position + self.counter_size - 1 - i;
312            self.counter_block[idx] = counter_value[7 - i];
313        }
314
315        // Force regeneration on next use
316        self.keystream_pos = self.keystream.len();
317
318        // Clear any old keystream with Zeroizing
319        self.keystream = Zeroizing::new(boxed_bytes_zeroed(0));
320
321        // Zeroize the temporary counter value
322        counter_value.zeroize();
323    }
324
325    /// Set the counter value directly
326    ///
327    /// This allows for manual control of the counter, which can be useful for
328    /// seeking to specific positions in the stream.
329    ///
330    /// # Arguments
331    /// * `counter` - The new counter value
332    pub fn set_counter(&mut self, counter: u32) {
333        // Update counter in the counter block
334        let counter_pos = self.counter_position;
335
336        // Write the counter value in big-endian format
337        // This handles various counter sizes (1-8 bytes)
338        let counter_bytes = counter.to_be_bytes();
339        let start_idx = 4 - self.counter_size;
340
341        for i in 0..self.counter_size {
342            if start_idx + i < 4 {
343                // Only copy if within counter_bytes bounds
344                self.counter_block[counter_pos + i] = counter_bytes[start_idx + i];
345            }
346        }
347
348        // Force regeneration of keystream on next use
349        self.keystream_pos = self.keystream.len();
350    }
351
352    /// Reset to initial state with the same key and nonce
353    ///
354    /// This resets the counter to 0 and clears any buffered keystream.
355    ///
356    /// # Arguments
357    /// * `nonce` - Optional new nonce to use (if not provided, keeps the current nonce)
358    /// * `counter` - Optional initial counter value (defaults to 0)
359    pub fn reset<const N: usize>(&mut self, nonce: Option<&Nonce<N>>, counter: u32) -> Result<()>
360    where
361        Nonce<N>: AesCtrCompatible,
362    {
363        // Use memory barrier before sensitive operations
364        barrier::compiler_fence_seq_cst();
365
366        // Update nonce if provided
367        if let Some(new_nonce) = nonce {
368            let block_size = B::block_size();
369            let max_nonce_size = block_size - self.counter_size;
370
371            // If nonce is too large, truncate it
372            let effective_nonce = if N > max_nonce_size {
373                &new_nonce.as_ref()[0..max_nonce_size]
374            } else {
375                new_nonce.as_ref()
376            };
377
378            // Clear the counter block
379            for b in &mut *self.counter_block {
380                *b = 0;
381            }
382
383            // Fill in the nonce
384            let counter_pos = match self.counter_position {
385                0 => self.counter_size, // Counter is at beginning, nonce follows
386                _ => 0,                 // Otherwise nonce is at beginning
387            };
388
389            // Copy the new nonce
390            self.counter_block[counter_pos..counter_pos + effective_nonce.len()]
391                .copy_from_slice(effective_nonce);
392        }
393
394        // Set the counter value
395        self.set_counter(counter);
396
397        // Clear keystream
398        self.keystream = Zeroizing::new(boxed_bytes_zeroed(0));
399        self.keystream_pos = 0;
400
401        // Use memory barrier after sensitive operations
402        barrier::compiler_fence_seq_cst();
403
404        Ok(())
405    }
406}
407
408#[cfg(test)]
409mod tests;