diff --git a/cipher-factory/src/algorithm.rs b/cipher-factory/src/algorithm.rs index 903edf2..c2c2156 100644 --- a/cipher-factory/src/algorithm.rs +++ b/cipher-factory/src/algorithm.rs @@ -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, 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 { + 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> { + 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> { + 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, 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) }