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