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)]
#[inline]
#[must_use]
@ -66,9 +72,8 @@ impl Aes {
}
impl BlockCipher for Aes {
const BLOCK_SIZE: usize = 16;
fn from_key(key: &[u8]) -> Self {
Self::new(key)
fn block_size(&self) -> usize {
16
}
fn transform_impl(
@ -76,9 +81,10 @@ impl BlockCipher for Aes {
block: &[u8],
action: cipher_core::CipherAction,
) -> 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()
.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);

View File

@ -1,6 +1,8 @@
use std::fmt::Debug;
use zeroize::ZeroizeOnDrop;
use crate::Block128;
/// 128-bit Key for AES
#[derive(ZeroizeOnDrop)]
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] {
fn from(key: Key) -> Self {
key.0

View File

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

View File

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

View File

@ -1,5 +1,10 @@
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))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Algorithm {
@ -7,6 +12,65 @@ pub enum Algorithm {
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 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {

View File

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

View File

@ -32,13 +32,17 @@ impl Des {
let subkeys = Subkeys::from_key(&key.into());
Self { subkeys }
}
#[inline]
#[must_use]
pub fn from_key(key: impl Into<Key>) -> Self {
Self::new(key)
}
}
impl BlockCipher for Des {
const BLOCK_SIZE: usize = 8;
fn from_key(key: &[u8]) -> Self {
Self::new(key)
fn block_size(&self) -> usize {
8
}
fn transform_impl(
@ -46,9 +50,10 @@ impl BlockCipher for Des {
block: &[u8],
action: cipher_core::CipherAction,
) -> 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()
.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 permutated_block = ip(block64);

View File

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