mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-31 13:52:29 +00:00
feat(cipher-factory,cli): add CBC mode support to CipherContext and CLI
Update CipherContext:
- Add optional iv field for CBC mode
- Add process_cbc() for CBC-specific handling
- Add parse_hex() helper for decryption input
- Separate ECB and CBC processing paths
Update CLI:
- Add --iv argument for initialization vector
- Pass IV through to CipherContext
This commit is contained in:
parent
6c5cd9b78a
commit
1ffc0327b3
@ -1,11 +1,12 @@
|
||||
use crate::{Algorithm, OperationMode, OutputFormat};
|
||||
use cipher_core::{BlockCipher, CipherResult};
|
||||
use cipher_core::{BlockCipher, CipherError, CipherResult, Output};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CipherContext {
|
||||
pub algorithm: Algorithm,
|
||||
pub operation: OperationMode,
|
||||
pub key: String,
|
||||
pub iv: Option<String>,
|
||||
pub input_text: String,
|
||||
pub output_format: OutputFormat,
|
||||
}
|
||||
@ -17,6 +18,7 @@ impl CipherContext {
|
||||
algorithm: Algorithm,
|
||||
operation: OperationMode,
|
||||
key: String,
|
||||
iv: Option<String>,
|
||||
input_text: String,
|
||||
output_format: OutputFormat,
|
||||
) -> Self {
|
||||
@ -24,22 +26,57 @@ impl CipherContext {
|
||||
algorithm,
|
||||
operation,
|
||||
key,
|
||||
iv,
|
||||
input_text,
|
||||
output_format,
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the input text using the configured cipher algorithm and operation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if parsing the input text or creating the cipher fails,
|
||||
/// or if the encryption/decryption process encounters an error.
|
||||
/// Returns `Err` if:
|
||||
/// - Parsing the input text or creating the cipher fails
|
||||
/// - The encryption/decryption process encounters an error
|
||||
/// - CBC mode is used without providing an IV
|
||||
pub fn process(&self) -> CipherResult<String> {
|
||||
let text_bytes = self.algorithm.parse_text(&self.input_text)?;
|
||||
let cipher = self.algorithm.new_cipher(&self.key)?;
|
||||
self.execute(cipher.as_ref(), &text_bytes)
|
||||
if self.algorithm.requires_iv() {
|
||||
self.process_cbc()
|
||||
} else {
|
||||
self.process_ecb()
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(&self, cipher: &dyn BlockCipher, text_bytes: &[u8]) -> CipherResult<String> {
|
||||
fn process_ecb(&self) -> CipherResult<String> {
|
||||
let text_bytes = self.algorithm.parse_text(&self.input_text)?;
|
||||
let cipher = self.algorithm.new_cipher(&self.key)?;
|
||||
self.execute_ecb(cipher.as_ref(), &text_bytes)
|
||||
}
|
||||
|
||||
fn process_cbc(&self) -> CipherResult<String> {
|
||||
let iv = self.iv.as_ref().ok_or_else(|| {
|
||||
CipherError::InvalidPadding("CBC mode requires an IV".into())
|
||||
})?;
|
||||
|
||||
let cipher = self.algorithm.new_cbc_cipher(&self.key, iv)?;
|
||||
|
||||
match self.operation {
|
||||
OperationMode::Encrypt => {
|
||||
let ciphertext = cipher.encrypt(self.input_text.as_bytes())?;
|
||||
Ok(format!("{:X}", Output::from(ciphertext)))
|
||||
}
|
||||
OperationMode::Decrypt => {
|
||||
// Parse hex input for decryption
|
||||
let ciphertext = parse_hex(&self.input_text)?;
|
||||
let plaintext = cipher.decrypt(&ciphertext)?;
|
||||
let output = self.output_format.format(&Output::from(plaintext));
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_ecb(&self, cipher: &dyn BlockCipher, text_bytes: &[u8]) -> CipherResult<String> {
|
||||
match self.operation {
|
||||
OperationMode::Encrypt => {
|
||||
let ciphertext = cipher.encrypt(text_bytes)?;
|
||||
@ -53,3 +90,26 @@ impl CipherContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses a hex string into bytes.
|
||||
fn parse_hex(s: &str) -> CipherResult<Vec<u8>> {
|
||||
let trimmed = s.trim();
|
||||
let s = trimmed
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| trimmed.strip_prefix("0X"))
|
||||
.unwrap_or(trimmed);
|
||||
|
||||
if !s.len().is_multiple_of(2) {
|
||||
return Err(CipherError::InvalidPadding(
|
||||
"hex string must have even length".into(),
|
||||
));
|
||||
}
|
||||
|
||||
(0..s.len())
|
||||
.step_by(2)
|
||||
.map(|i| {
|
||||
u8::from_str_radix(&s[i..i + 2], 16)
|
||||
.map_err(|_| CipherError::InvalidPadding(format!("invalid hex at position {i}")))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@ -12,11 +12,15 @@ pub struct Args {
|
||||
#[arg(short, long)]
|
||||
pub algorithm: Algorithm,
|
||||
|
||||
/// Key used for encryption/decryption. Can be a string or a path to a file
|
||||
/// Key used for encryption/decryption (hex string, e.g., 0x2b7e...)
|
||||
#[arg(short, long, required = true)]
|
||||
pub key: String,
|
||||
|
||||
/// The text to encrypt/decrypt. Can be a string or a path to a file
|
||||
/// Initialization vector for CBC mode (hex string, e.g., 0x0001...)
|
||||
#[arg(long)]
|
||||
pub iv: Option<String>,
|
||||
|
||||
/// The text to encrypt/decrypt
|
||||
#[arg(value_name = "TEXT", required = true)]
|
||||
pub text: String,
|
||||
|
||||
@ -31,6 +35,7 @@ impl From<Args> for CipherContext {
|
||||
algorithm: args.algorithm,
|
||||
operation: args.operation,
|
||||
key: args.key,
|
||||
iv: args.iv,
|
||||
input_text: args.text,
|
||||
output_format: args.output_format.unwrap_or_default(),
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user