Skip to main content

dcrypt_algorithms/block/modes/cbc/
mod.rs

1//! Cipher Block Chaining (CBC) mode implementation
2//!
3//! CBC mode is a block cipher mode of operation that provides confidentiality
4//! by XORing each plaintext block with the previous ciphertext block before
5//! encryption. The first block is XORed with an initialization vector (IV).
6//!
7//! This implementation follows NIST SP 800-38A specifications and provides
8//! secure memory handling with automatic zeroization of sensitive data.
9
10#[cfg(not(feature = "std"))]
11use alloc::vec::Vec;
12use dcrypt_internal::zeroing::{boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing};
13
14use super::super::{BlockCipher, CipherAlgorithm};
15use crate::error::{validate, Error, Result};
16use crate::types::Nonce;
17
18/// Marker trait for nonces that are compatible with CBC mode
19pub trait CbcCompatible: crate::types::sealed::Sealed {}
20
21// Implement CbcCompatible trait for Nonce types that match block sizes
22impl<const N: usize> CbcCompatible for Nonce<N> {}
23
24/// CBC mode implementation
25#[derive(Clone)]
26pub struct Cbc<B: BlockCipher + Zeroize + ZeroizeOnDrop> {
27    cipher: B,
28    iv: Vec<u8>,
29}
30
31impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Zeroize for Cbc<B> {
32    fn zeroize(&mut self) {
33        self.cipher.zeroize();
34        self.iv.zeroize();
35    }
36}
37
38impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> Drop for Cbc<B> {
39    fn drop(&mut self) {
40        self.zeroize();
41    }
42}
43
44impl<B: BlockCipher + Zeroize + ZeroizeOnDrop> ZeroizeOnDrop for Cbc<B> {}
45
46impl<B: BlockCipher + CipherAlgorithm + Zeroize + ZeroizeOnDrop> Cbc<B> {
47    /// Creates a new CBC mode instance with the given cipher and IV
48    ///
49    /// The IV (nonce) must be the same size as the block size of the cipher.
50    pub fn new<const N: usize>(cipher: B, iv: &Nonce<N>) -> Result<Self>
51    where
52        Nonce<N>: CbcCompatible,
53    {
54        // Validate that the nonce size matches the block size at runtime
55        validate::length("CBC initialization vector", N, B::block_size())?;
56
57        Ok(Self {
58            cipher,
59            iv: iv.as_ref().to_vec(),
60        })
61    }
62
63    /// Encrypts a message using CBC mode
64    ///
65    /// The plaintext must be a multiple of the block size.
66    /// For plaintext that is not a multiple of the block size,
67    /// padding must be applied before calling this function.
68    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
69        // Validate plaintext length is a multiple of block size
70        let block_size = B::block_size();
71        if plaintext.len() % block_size != 0 {
72            let expected_len = ((plaintext.len() / block_size) + 1) * block_size;
73            return Err(Error::Length {
74                context: "CBC plaintext",
75                expected: expected_len,
76                actual: plaintext.len(),
77            });
78        }
79
80        let mut ciphertext = Vec::with_capacity(plaintext.len());
81        let mut prev_block = self.iv.clone();
82
83        // Process the plaintext in blocks
84        for chunk in plaintext.chunks(block_size) {
85            let mut block = Zeroizing::new([0u8; 16]); // AES block size is 16 bytes
86            block[..chunk.len()].copy_from_slice(chunk);
87
88            // XOR with previous ciphertext block (or IV for the first block)
89            for i in 0..block_size {
90                block[i] ^= prev_block[i];
91            }
92
93            // Encrypt the XORed block
94            self.cipher.encrypt_block(&mut block[..])?;
95
96            // Append to ciphertext and update previous block
97            ciphertext.extend_from_slice(&block[..]);
98            prev_block = block.to_vec();
99        }
100
101        Ok(ciphertext)
102    }
103
104    /// Decrypts a message using CBC mode
105    ///
106    /// The ciphertext must be a multiple of the block size.
107    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
108        // Validate ciphertext length is a multiple of block size
109        let block_size = B::block_size();
110        if ciphertext.len() % block_size != 0 {
111            let expected_len = ((ciphertext.len() / block_size) + 1) * block_size;
112            return Err(Error::Length {
113                context: "CBC ciphertext",
114                expected: expected_len,
115                actual: ciphertext.len(),
116            });
117        }
118
119        let mut plaintext = Zeroizing::new(boxed_bytes_zeroed(ciphertext.len()));
120        let mut prev_block = self.iv.clone();
121        let mut plaintext_offset = 0usize;
122
123        // Process the ciphertext in blocks
124        for chunk in ciphertext.chunks(block_size) {
125            let mut block = Zeroizing::new([0u8; 16]); // AES block size is 16 bytes
126            block[..chunk.len()].copy_from_slice(chunk);
127
128            // Save current ciphertext block
129            let current_block = *block;
130
131            // Decrypt the block
132            self.cipher.decrypt_block(&mut block[..])?;
133
134            // XOR with previous ciphertext block (or IV for the first block)
135            for i in 0..block_size {
136                block[i] ^= prev_block[i];
137            }
138
139            // Append to plaintext and update previous block
140            plaintext[plaintext_offset..plaintext_offset + block_size]
141                .copy_from_slice(&block[..block_size]);
142            plaintext_offset += block_size;
143            prev_block = current_block.to_vec();
144        }
145
146        Ok(plaintext.into_inner().into_vec())
147    }
148}
149
150#[cfg(test)]
151mod tests;