From 8b80e17f822cfc1c3c2c42a0357fa28a5a633c31 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Mon, 10 Nov 2025 09:56:20 +0200 Subject: [PATCH] feat(aes): add 128 bit key and block --- aes/src/aes.rs | 17 +++ aes/src/block/block128.rs | 118 +++++++++++++++++++++ aes/src/block/mod.rs | 5 + aes/src/block/secret_block.rs | 188 ++++++++++++++++++++++++++++++++++ aes/src/constants.rs | 1 + aes/src/key/aes_key.rs | 92 +++++++++++++++++ aes/src/key/mod.rs | 3 + aes/src/lib.rs | 6 ++ 8 files changed, 430 insertions(+) create mode 100644 aes/src/aes.rs create mode 100644 aes/src/block/block128.rs create mode 100644 aes/src/block/mod.rs create mode 100644 aes/src/block/secret_block.rs create mode 100644 aes/src/constants.rs create mode 100644 aes/src/key/aes_key.rs create mode 100644 aes/src/key/mod.rs diff --git a/aes/src/aes.rs b/aes/src/aes.rs new file mode 100644 index 0000000..b732e36 --- /dev/null +++ b/aes/src/aes.rs @@ -0,0 +1,17 @@ +use crate::key::Key; + +pub struct Aes {} + +impl Aes { + pub fn new(_key: impl Into) -> Self { + todo!() + } +} + +impl Aes { + const BLOCK_SIZE: usize = 16; + + fn from_key(key: &[u8]) -> Self { + Self::new(key) + } +} diff --git a/aes/src/block/block128.rs b/aes/src/block/block128.rs new file mode 100644 index 0000000..6114917 --- /dev/null +++ b/aes/src/block/block128.rs @@ -0,0 +1,118 @@ +use crate::block::secret_block; +use cipher_core::{BlockError, InputBlock}; +use std::{ + slice::{from_raw_parts, from_raw_parts_mut}, + str::FromStr, +}; + +secret_block! { + pub struct Block128(u128, 128, 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF); +} + +impl InputBlock for Block128 { + const BLOCK_SIZE: usize = 128; + fn as_bytes(&self) -> &[u8] { + unsafe { from_raw_parts((&raw const self.0).cast::().cast::(), 16) } + } + fn as_bytes_mut(&mut self) -> &mut [u8] { + unsafe { from_raw_parts_mut((&raw mut self.0).cast::().cast::(), 16) } + } +} + +impl Block128 { + #[inline] + #[must_use] + pub const fn from_be_bytes(bytes: [u8; 16]) -> Self { + Self(u128::from_be_bytes(bytes)) + } + + #[inline] + #[must_use] + pub const fn to_be_bytes(self) -> [u8; 16] { + self.0.to_be_bytes() + } + + #[inline] + #[must_use] + pub const fn to_le_bytes(self) -> [u8; 16] { + self.0.to_le_bytes() + } +} + +impl FromStr for Block128 { + type Err = BlockError; + fn from_str(s: &str) -> Result { + Ok(Self(parse_string_to_u128(s)?)) + } +} + +fn parse_string_to_u128(s: &str) -> Result { + let trimmed = s.trim(); + + if trimmed.is_empty() { + return Err(BlockError::EmptyBlock); + } + + // Hexadecimal with 0x/0X prefix + if let Some(hex_str) = trimmed + .strip_prefix("0x") + .or_else(|| trimmed.strip_prefix("0X")) + { + return parse_radix(hex_str, 16); + } + // Binary with 0b/0B prefix + if let Some(bin_str) = trimmed + .strip_prefix("0b") + .or_else(|| trimmed.strip_prefix("0B")) + { + return parse_radix(bin_str, 2); + } + + ascii_string_to_u128(trimmed) +} + +fn parse_radix(s: &str, radix: u32) -> Result { + let trimmed = s.trim_start_matches('0'); + if trimmed.is_empty() { + return Ok(0); + } + + u128::from_str_radix(trimmed, radix).map_err(BlockError::from) +} + +fn ascii_string_to_u128(s: &str) -> Result { + if s.len() > 8 { + return Err(BlockError::InvalidByteStringLength(s.len())); + } + + if !s.is_ascii() { + return Err(BlockError::conversion_error( + "u64", + "String contains non-ASCII characters", + )); + } + + let mut bytes = [0u8; 16]; + let offset = 16 - s.len(); + bytes[offset..].copy_from_slice(s.as_bytes()); + + Ok(u128::from_be_bytes(bytes)) +} + +impl From<[u8; 16]> for Block128 { + fn from(bytes: [u8; 16]) -> Self { + Self::from_be_bytes(bytes) + } +} + +impl From for Vec { + fn from(value: Block128) -> Self { + value.0.to_be_bytes().to_vec() + } +} + +impl From<&Block128> for Vec { + fn from(value: &Block128) -> Self { + value.0.to_be_bytes().to_vec() + } +} diff --git a/aes/src/block/mod.rs b/aes/src/block/mod.rs new file mode 100644 index 0000000..f98778f --- /dev/null +++ b/aes/src/block/mod.rs @@ -0,0 +1,5 @@ +mod block128; +mod secret_block; + +use crate::secret_block; +pub use block128::Block128; diff --git a/aes/src/block/secret_block.rs b/aes/src/block/secret_block.rs new file mode 100644 index 0000000..f70f0bb --- /dev/null +++ b/aes/src/block/secret_block.rs @@ -0,0 +1,188 @@ +/// Macro to generate a masked, zeroizable integer wrapper type. +/// +/// Usage: +/// ``` +/// use des::secret_block; +/// secret_block! { +/// /// docs... +/// pub struct Block48(u64, 48, 0x0000_FFFF_FFFF_FFFFu64); +/// } +/// ``` +#[macro_export] +macro_rules! secret_block { + ( + $(#[$meta:meta])* + $vis:vis struct $name:ident ( $int:tt, $bits:expr, $mask:expr ); +) => { + $(#[$meta])* + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + $vis struct $name($int); + + impl $name { + /// Mask to restrict the underlying integer to valid bits. + pub const MASK: $int = $mask; + + /// Calculate the number of hex digits needed for the bit width + const fn hex_width() -> usize { + ($bits as usize).div_ceil(4) + } + + /// Calculate the number of octal digits needed for the bit width + const fn octal_width() -> usize { + ($bits as usize).div_ceil(3) + } + + #[inline] + #[must_use] + pub const fn new(value: $int) -> Self { + Self(value & Self::MASK) + } + + secret_block!(@conversions_as $int); + secret_block!(@conversions_from $int $int); + } + + impl From<$int> for $name { + fn from(v: $int) -> Self { + Self(v & Self::MASK) + } + } + + impl From<$name> for $int { + fn from(value: $name) -> $int { + value.0 + } + } + + impl std::fmt::UpperHex for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:0width$X}", self.0, width = Self::hex_width()) + } + } + + impl std::fmt::LowerHex for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:0width$x}", self.0, width = Self::hex_width()) + } + } + + impl std::fmt::Octal for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:0width$o}", self.0, width = Self::octal_width()) + } + } + + impl std::fmt::Binary for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:0width$b}", self.0, width = $bits) + } + } + + }; + // Helper: generate conversions_as based on type + (@conversions_as u8) => { + /// Return value as u8 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn as_u8(&self) -> u8 { + self.0 as u8 + } + + secret_block!(@conversions_as u16); + }; + (@conversions_as u16) => { + /// Return value as u16 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn as_u16(&self) -> u16 { + self.0 as u16 + } + + secret_block!(@conversions_as u32); + }; + (@conversions_as u32) => { + /// Return value as u32 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn as_u32(&self) -> u32 { + self.0 as u32 + } + + secret_block!(@conversions_as u64); + }; + (@conversions_as u64) => { + /// Return value as u64 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn as_u64(&self) -> u64 { + self.0 as u64 + } + }; + (@conversions_as u128) => { + /// Return value as u64 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn as_u128(&self) -> u128 { + self.0 as u128 + } + }; + // Helper: generate conversions_from based on type + (@conversions_from u8 $int:tt) => { + /// Create value from u8 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn from_u8(key: u8) -> Self { + Self(key as $int & Self::MASK) + } + }; + (@conversions_from u16 $int:tt) => { + /// Create value from u16 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn from_u16(key: u16) -> Self { + Self(key as $int & Self::MASK) + } + + secret_block!(@conversions_from u8 $int); + }; + (@conversions_from u32 $int:tt) => { + /// Create value from u32 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn from_u32(key: u32) -> Self { + Self(key as $int & Self::MASK) + } + + secret_block!(@conversions_from u16 $int); + }; + (@conversions_from u64 $int:tt) => { + /// Create value from u64 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn from_u64(key: u64) -> Self { + Self(key as $int & Self::MASK) + } + + secret_block!(@conversions_from u32 $int); + }; + (@conversions_from u128 $int:tt) => { + /// Create value from u64 + #[allow(dead_code)] + #[inline] + #[must_use] + pub const fn from_u128(key: u128) -> Self { + Self(key & Self::MASK) + } + + secret_block!(@conversions_from u64 $int); + } +} diff --git a/aes/src/constants.rs b/aes/src/constants.rs new file mode 100644 index 0000000..7ac3054 --- /dev/null +++ b/aes/src/constants.rs @@ -0,0 +1 @@ +// AES Constants diff --git a/aes/src/key/aes_key.rs b/aes/src/key/aes_key.rs new file mode 100644 index 0000000..d84a3a5 --- /dev/null +++ b/aes/src/key/aes_key.rs @@ -0,0 +1,92 @@ +use std::fmt::Debug; +use zeroize::ZeroizeOnDrop; + +/// 128-bit Key for AES +#[derive(ZeroizeOnDrop)] +pub struct Key([u8; 16]); + +impl Key { + #[inline] + #[must_use] + pub const fn from_array(bytes: [u8; 16]) -> Self { + Self(bytes) + } + + #[inline] + #[must_use] + pub const fn as_array(&self) -> &[u8; 16] { + &self.0 + } + + #[inline] + #[must_use] + pub const fn as_2d(&self) -> [[u8; 4]; 4] { + [ + [self.0[0], self.0[1], self.0[2], self.0[3]], + [self.0[4], self.0[5], self.0[6], self.0[7]], + [self.0[8], self.0[9], self.0[10], self.0[11]], + [self.0[12], self.0[13], self.0[14], self.0[15]], + ] + } + + #[inline] + #[must_use] + pub const fn as_u128(&self) -> u128 { + u128::from_be_bytes(self.0) + } +} + +impl From<[[u8; 4]; 4]> for Key { + fn from(matrix: [[u8; 4]; 4]) -> Self { + let mut bytes = [0; 16]; + for (idx, row) in matrix.iter().enumerate() { + bytes[idx * 4..(idx - 1) * 4].copy_from_slice(row); + } + Self(bytes) + } +} + +impl From<[u8; 16]> for Key { + fn from(bytes: [u8; 16]) -> Self { + Self(bytes) + } +} + +impl From<&[u8]> for Key { + fn from(value: &[u8]) -> Self { + let mut bytes = [0; 16]; + let len = value.len().min(16); + bytes[..len].copy_from_slice(&value[..len]); + bytes.into() + } +} + +impl From for Key { + fn from(key: u128) -> Self { + key.to_be_bytes().into() + } +} + +impl From for [u8; 16] { + fn from(key: Key) -> Self { + key.0 + } +} + +impl From for [[u8; 4]; 4] { + fn from(key: Key) -> Self { + key.as_2d() + } +} + +impl AsRef<[u8]> for Key { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Debug for Key { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Key([REDACTED])") + } +} diff --git a/aes/src/key/mod.rs b/aes/src/key/mod.rs new file mode 100644 index 0000000..148a4ca --- /dev/null +++ b/aes/src/key/mod.rs @@ -0,0 +1,3 @@ +mod aes_key; + +pub use aes_key::Key; diff --git a/aes/src/lib.rs b/aes/src/lib.rs index e69de29..7730c8e 100644 --- a/aes/src/lib.rs +++ b/aes/src/lib.rs @@ -0,0 +1,6 @@ +mod aes; +mod block; +mod constants; +mod key; + +pub use {aes::Aes, block::Block128};