feat(factory): add cipher/algorithm helper functions

This commit is contained in:
Kristofers Solo 2025-11-24 12:02:22 +02:00
parent 051bba33a8
commit 46a47102b9
Signed by: kristoferssolo
GPG Key ID: 74FF8144483D82C8
8 changed files with 120 additions and 41 deletions

View File

@ -19,6 +19,12 @@ impl Aes {
} }
} }
#[inline]
#[must_use]
pub fn from_key(key: impl Into<Key>) -> Self {
Self::new(key)
}
#[cfg(test)] #[cfg(test)]
#[inline] #[inline]
#[must_use] #[must_use]
@ -66,9 +72,8 @@ impl Aes {
} }
impl BlockCipher for Aes { impl BlockCipher for Aes {
const BLOCK_SIZE: usize = 16; fn block_size(&self) -> usize {
fn from_key(key: &[u8]) -> Self { 16
Self::new(key)
} }
fn transform_impl( fn transform_impl(
@ -76,9 +81,10 @@ impl BlockCipher for Aes {
block: &[u8], block: &[u8],
action: cipher_core::CipherAction, action: cipher_core::CipherAction,
) -> cipher_core::CipherResult<cipher_core::Output> { ) -> cipher_core::CipherResult<cipher_core::Output> {
let block_arr: [u8; Self::BLOCK_SIZE] = block let block_size = self.block_size();
let block_arr: [u8; 16] = block
.try_into() .try_into()
.map_err(|_| CipherError::invalid_block_size(Self::BLOCK_SIZE, block.len()))?; .map_err(|_| CipherError::invalid_block_size(block_size, block.len()))?;
let block128 = Block128::from_be_bytes(block_arr); let block128 = Block128::from_be_bytes(block_arr);

View File

@ -1,6 +1,8 @@
use std::fmt::Debug; use std::fmt::Debug;
use zeroize::ZeroizeOnDrop; use zeroize::ZeroizeOnDrop;
use crate::Block128;
/// 128-bit Key for AES /// 128-bit Key for AES
#[derive(ZeroizeOnDrop)] #[derive(ZeroizeOnDrop)]
pub struct Key([u8; 16]); pub struct Key([u8; 16]);
@ -67,6 +69,12 @@ impl From<u128> for Key {
} }
} }
impl From<Block128> for Key {
fn from(key: Block128) -> Self {
key.to_be_bytes().into()
}
}
impl From<Key> for [u8; 16] { impl From<Key> for [u8; 16] {
fn from(key: Key) -> Self { fn from(key: Key) -> Self {
key.0 key.0

View File

@ -10,6 +10,10 @@ pub enum CipherError {
/// Input data doesn't match the cipher's block size /// Input data doesn't match the cipher's block size
#[error("Invalid block size: expected {expected} bytes, got {actual}")] #[error("Invalid block size: expected {expected} bytes, got {actual}")]
InvalidBlockSize { expected: usize, actual: usize }, InvalidBlockSize { expected: usize, actual: usize },
/// Error parsing block from string
#[error("Error parsing block from string: {0}")]
BlockParseError(#[from] BlockError),
} }
impl CipherError { impl CipherError {

View File

@ -6,9 +6,7 @@ use crate::{CipherAction, CipherError, CipherResult, Output};
/// Implementers define `transform_impl` to handle the core algorithm, /// Implementers define `transform_impl` to handle the core algorithm,
/// while `transform` provides validation and convenience wrappers. /// while `transform` provides validation and convenience wrappers.
pub trait BlockCipher { pub trait BlockCipher {
const BLOCK_SIZE: usize; fn block_size(&self) -> usize;
fn from_key(key: &[u8]) -> Self;
/// Core cipher transformation (must be implemented by concrete types). /// Core cipher transformation (must be implemented by concrete types).
/// ///
@ -24,13 +22,11 @@ pub trait BlockCipher {
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `CipherError::InvalidBlockSize` if `block.len() != BLOCK_SIZE`. /// Returns `CipherError::InvalidBlockSize` if `block.len() != self.block_size()`.
fn transform(&self, block: &[u8], action: CipherAction) -> CipherResult<Output> { fn transform(&self, block: &[u8], action: CipherAction) -> CipherResult<Output> {
if block.len() != Self::BLOCK_SIZE { let block_size = self.block_size();
return Err(CipherError::invalid_block_size( if block.len() != block_size {
Self::BLOCK_SIZE, return Err(CipherError::invalid_block_size(block_size, block.len()));
block.len(),
));
} }
self.transform_impl(block, action) self.transform_impl(block, action)
} }

View File

@ -1,5 +1,10 @@
use std::fmt::Display; use std::fmt::Display;
use aes::{Aes, Block128};
use cipher_core::{BlockCipher, BlockError, CipherError};
use des::{Block64, Des};
use std::str::FromStr;
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Algorithm { pub enum Algorithm {
@ -7,6 +12,65 @@ pub enum Algorithm {
Aes, Aes,
} }
impl Algorithm {
/// Creates a new 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.).
///
pub fn new_cipher(&self, key: &str) -> Result<Box<dyn BlockCipher>, CipherError> {
match self {
Self::Des => {
let key = Block64::from_str(key)?;
let cipher = Des::from_key(key);
Ok(Box::new(cipher))
}
Self::Aes => {
let key = Block128::from_str(key)?;
let cipher = Aes::from_key(key);
Ok(Box::new(cipher))
}
}
}
/// 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:
/// - DES: 64-bit blocks (8 bytes)
/// - AES: 128-bit blocks (16 bytes)
///
/// The input can be provided in various formats (hex, binary, ASCII, etc.) as supported
/// by the block type's `FromStr` implementation.
///
/// # Returns
///
/// A byte vector representing the parsed text, or a `BlockError` if parsing fails.
///
/// # Errors
///
/// Returns `BlockError` if:
/// - The text format is invalid
/// - The text length doesn't match the block size
/// - The text contains invalid characters for the given format
///
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()),
}
}
}
impl Display for Algorithm { impl Display for Algorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self { let s = match self {

View File

@ -1,13 +1,10 @@
mod args; mod args;
use crate::args::Args; use crate::args::Args;
use aes::{Aes, Block128};
use cipher_core::BlockCipher; use cipher_core::BlockCipher;
use cipher_factory::{Algorithm, OperationChoice, OutputFormat}; use cipher_factory::{self, OperationChoice, OutputFormat};
use clap::Parser; use clap::Parser;
use color_eyre::eyre::{Ok, Result}; use color_eyre::eyre::{Ok, Result};
use des::{Block64, Des};
use std::str::FromStr;
fn main() -> Result<()> { fn main() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
@ -19,26 +16,17 @@ fn main() -> Result<()> {
output_format, output_format,
} = Args::parse(); } = Args::parse();
match algorithm { let text_bytes = algorithm.parse_text(&text)?;
Algorithm::Des => { let cipher = algorithm.new_cipher(&key)?;
let key = Block64::from_str(&key)?;
let text = Block64::from_str(&text)?; execute_cipher(operation, cipher.as_ref(), &text_bytes, output_format)?;
let cipher = Des::from_key(&key.to_be_bytes());
execute_cipher(operation, &cipher, &text.to_be_bytes(), output_format)?;
}
Algorithm::Aes => {
let key = Block128::from_str(&key)?;
let text = Block128::from_str(&text)?;
let cipher = Aes::from_key(&key.to_be_bytes());
execute_cipher(operation, &cipher, &text.to_be_bytes(), output_format)?;
}
}
Ok(()) Ok(())
} }
fn execute_cipher( fn execute_cipher(
operation: OperationChoice, operation: OperationChoice,
cipher: &impl BlockCipher, cipher: &dyn BlockCipher,
text_bytes: &[u8], text_bytes: &[u8],
output_format: Option<OutputFormat>, output_format: Option<OutputFormat>,
) -> Result<()> { ) -> Result<()> {

View File

@ -32,13 +32,17 @@ impl Des {
let subkeys = Subkeys::from_key(&key.into()); let subkeys = Subkeys::from_key(&key.into());
Self { subkeys } Self { subkeys }
} }
#[inline]
#[must_use]
pub fn from_key(key: impl Into<Key>) -> Self {
Self::new(key)
}
} }
impl BlockCipher for Des { impl BlockCipher for Des {
const BLOCK_SIZE: usize = 8; fn block_size(&self) -> usize {
8
fn from_key(key: &[u8]) -> Self {
Self::new(key)
} }
fn transform_impl( fn transform_impl(
@ -46,9 +50,10 @@ impl BlockCipher for Des {
block: &[u8], block: &[u8],
action: cipher_core::CipherAction, action: cipher_core::CipherAction,
) -> cipher_core::CipherResult<cipher_core::Output> { ) -> cipher_core::CipherResult<cipher_core::Output> {
let block_arr: [u8; Self::BLOCK_SIZE] = block let block_size = self.block_size();
let block_arr: [u8; 8] = block
.try_into() .try_into()
.map_err(|_| CipherError::invalid_block_size(Self::BLOCK_SIZE, block.len()))?; .map_err(|_| CipherError::invalid_block_size(block_size, block.len()))?;
let block64 = Block64::from_be_bytes(block_arr); let block64 = Block64::from_be_bytes(block_arr);
let permutated_block = ip(block64); let permutated_block = ip(block64);

View File

@ -1,6 +1,8 @@
use std::fmt::Debug; use std::fmt::Debug;
use zeroize::ZeroizeOnDrop; use zeroize::ZeroizeOnDrop;
use crate::Block64;
/// 64-bit Key for DES /// 64-bit Key for DES
#[derive(ZeroizeOnDrop)] #[derive(ZeroizeOnDrop)]
pub struct Key([u8; 8]); pub struct Key([u8; 8]);
@ -36,13 +38,19 @@ impl From<&[u8]> for Key {
let mut bytes = [0; 8]; let mut bytes = [0; 8];
let len = value.len().min(8); let len = value.len().min(8);
bytes[..len].copy_from_slice(&value[..len]); bytes[..len].copy_from_slice(&value[..len]);
Self(bytes) bytes.into()
} }
} }
impl From<u64> for Key { impl From<u64> for Key {
fn from(key: u64) -> Self { fn from(key: u64) -> Self {
Self(key.to_be_bytes()) key.to_be_bytes().into()
}
}
impl From<Block64> for Key {
fn from(key: Block64) -> Self {
key.to_be_bytes().into()
} }
} }