#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use zeroize::{Zeroize, ZeroizeOnDrop};
use super::super::{BlockCipher, CipherAlgorithm};
use crate::error::{validate, Error, Result};
use crate::types::Nonce;
pub trait CbcCompatible: crate::types::sealed::Sealed {}
impl<const N: usize> CbcCompatible for Nonce<N> {}
#[derive(Clone, Zeroize, ZeroizeOnDrop)]
pub struct Cbc<B: BlockCipher + Zeroize + ZeroizeOnDrop> {
cipher: B,
iv: Vec<u8>,
}
impl<B: BlockCipher + CipherAlgorithm + Zeroize + ZeroizeOnDrop> Cbc<B> {
pub fn new<const N: usize>(cipher: B, iv: &Nonce<N>) -> Result<Self>
where
Nonce<N>: CbcCompatible,
{
validate::length("CBC initialization vector", N, B::block_size())?;
Ok(Self {
cipher,
iv: iv.as_ref().to_vec(),
})
}
pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
let block_size = B::block_size();
if plaintext.len() % block_size != 0 {
let expected_len = ((plaintext.len() / block_size) + 1) * block_size;
return Err(Error::Length {
context: "CBC plaintext",
expected: expected_len,
actual: plaintext.len(),
});
}
let mut ciphertext = Vec::with_capacity(plaintext.len());
let mut prev_block = self.iv.clone();
for chunk in plaintext.chunks(block_size) {
let mut block = [0u8; 16]; block[..chunk.len()].copy_from_slice(chunk);
for i in 0..block_size {
block[i] ^= prev_block[i];
}
self.cipher.encrypt_block(&mut block)?;
ciphertext.extend_from_slice(&block);
prev_block = block.to_vec();
}
Ok(ciphertext)
}
pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
let block_size = B::block_size();
if ciphertext.len() % block_size != 0 {
let expected_len = ((ciphertext.len() / block_size) + 1) * block_size;
return Err(Error::Length {
context: "CBC ciphertext",
expected: expected_len,
actual: ciphertext.len(),
});
}
let mut plaintext = Vec::with_capacity(ciphertext.len());
let mut prev_block = self.iv.clone();
for chunk in ciphertext.chunks(block_size) {
let mut block = [0u8; 16]; block[..chunk.len()].copy_from_slice(chunk);
let current_block = block;
self.cipher.decrypt_block(&mut block)?;
for i in 0..block_size {
block[i] ^= prev_block[i];
}
plaintext.extend_from_slice(&block);
prev_block = current_block.to_vec();
}
Ok(plaintext)
}
}
#[cfg(test)]
mod tests;