feat(cipher-factory): add AesCbc algorithm variant

Add AES-CBC support to Algorithm enum:
    - Add AesCbc variant with clap name "aes-cbc"
    - Add requires_iv() method to check if algorithm needs IV
    - Add new_cbc_cipher() for creating AesCbc instances
    - Add encrypt_cbc() and decrypt_cbc() helper methods
    - Update parse_text() and Display for AesCbc
This commit is contained in:
Kristofers Solo 2025-12-31 01:01:47 +02:00
parent 454d1d6011
commit 6c5cd9b78a
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED

View File

@ -1,7 +1,7 @@
use std::fmt::Display;
use aes::{Aes, Block128};
use cipher_core::{BlockCipher, BlockError, CipherError};
use aes::{Aes, AesCbc, Block128, Iv};
use cipher_core::{BlockCipher, BlockError, CipherError, CipherResult};
use des::{Block64, Des};
use std::str::FromStr;
@ -10,24 +10,27 @@ use std::str::FromStr;
pub enum Algorithm {
Des,
Aes,
#[cfg_attr(feature = "clap", clap(name = "aes-cbc"))]
AesCbc,
}
impl Algorithm {
/// Creates a new cipher instance for the specified algorithm.
/// Returns whether this algorithm requires an IV (Initialization Vector).
#[must_use]
pub const fn requires_iv(&self) -> bool {
matches!(self, Self::AesCbc)
}
/// Creates a new ECB-mode cipher instance for the specified algorithm.
///
/// Parses the key string and instantiates either DES or AES based on the algorithm choice.
/// The key format depends on the algorithm:
/// - DES: 64-bit key (hex string, e.g., "0x1334577999bcdff1")
/// - AES: 128-bit key (hex string, e.g., "0x2b7e151628aed2a6abf7158809cf4f3c")
///
/// # Returns
///
/// A boxed cipher instance implementing `BlockCipher`, or a `CipherError` if parsing fails.
///
/// # Errors
///
/// Returns `CipherError` if the key cannot be parsed (invalid format, wrong length, etc.).
///
/// Returns `CipherError` if the key cannot be parsed or if called with a CBC algorithm.
pub fn new_cipher(&self, key: &str) -> Result<Box<dyn BlockCipher>, CipherError> {
match self {
Self::Des => {
@ -40,9 +43,50 @@ impl Algorithm {
let cipher = Aes::from_key(key);
Ok(Box::new(cipher))
}
Self::AesCbc => Err(CipherError::InvalidPadding(
"AES-CBC requires an IV; use new_cbc_cipher instead".into(),
)),
}
}
/// Creates a new AES-CBC cipher instance with the given key and IV.
///
/// # Errors
///
/// Returns `CipherError` if the key or IV cannot be parsed.
pub fn new_cbc_cipher(&self, key: &str, iv: &str) -> Result<AesCbc, CipherError> {
match self {
Self::AesCbc => {
let key = Block128::from_str(key)?;
let iv = Iv::from_str(iv)?;
Ok(AesCbc::new(key, iv))
}
_ => Err(CipherError::InvalidPadding(format!(
"{self} does not support CBC mode"
))),
}
}
/// Encrypts data using CBC mode with PKCS#7 padding.
///
/// # Errors
///
/// Returns `CipherError` if encryption fails.
pub fn encrypt_cbc(&self, key: &str, iv: &str, plaintext: &[u8]) -> CipherResult<Vec<u8>> {
let cipher = self.new_cbc_cipher(key, iv)?;
cipher.encrypt(plaintext)
}
/// Decrypts data using CBC mode and removes PKCS#7 padding.
///
/// # Errors
///
/// Returns `CipherError` if decryption fails or padding is invalid.
pub fn decrypt_cbc(&self, key: &str, iv: &str, ciphertext: &[u8]) -> CipherResult<Vec<u8>> {
let cipher = self.new_cbc_cipher(key, iv)?;
cipher.decrypt(ciphertext)
}
/// Parses plaintext or ciphertext according to the specified algorithm's block size.
///
/// Converts a text string into a byte vector using the appropriate block size:
@ -63,10 +107,13 @@ impl Algorithm {
/// - The text length doesn't match the block size
/// - The text contains invalid characters for the given format
///
/// Parses text for ECB-mode algorithms (single block).
///
/// For CBC mode, use raw bytes directly instead of this method.
pub fn parse_text(&self, text: &str) -> Result<Vec<u8>, BlockError> {
match self {
Self::Des => Ok(Block64::from_str(text)?.to_be_bytes().to_vec()),
Self::Aes => Ok(Block128::from_str(text)?.to_be_bytes().to_vec()),
Self::Aes | Self::AesCbc => Ok(Block128::from_str(text)?.to_be_bytes().to_vec()),
}
}
}
@ -76,6 +123,7 @@ impl Display for Algorithm {
let s = match self {
Self::Des => "DES",
Self::Aes => "AES",
Self::AesCbc => "AES-CBC",
};
f.write_str(s)
}