diff --git a/aes/src/iv.rs b/aes/src/iv.rs new file mode 100644 index 0000000..03b6235 --- /dev/null +++ b/aes/src/iv.rs @@ -0,0 +1,113 @@ +//! Initialization Vector (IV) for AES block cipher modes. +//! +//! The IV provides randomness to ensure identical plaintexts produce +//! different ciphertexts. For CBC mode, the IV must be unpredictable +//! and should never be reused with the same key. + +use crate::Block128; +use cipher_core::{BlockError, parse_block_int, secret_block}; +use std::{fmt, str::FromStr}; + +secret_block! { + /// 128-bit Initialization Vector for AES cipher modes. + /// + /// Used in CBC mode to XOR with the first plaintext block before encryption. + /// Each subsequent block is XORed with the previous ciphertext block. + /// + /// # Security + /// + /// - IVs must be unpredictable (use a cryptographically secure RNG) + /// - Never reuse an IV with the same key + /// - The IV does not need to be secret, but must be unique + pub struct Iv(u128, 128, 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF); +} + +impl Iv { + /// Creates an IV from big-endian bytes. + #[inline] + #[must_use] + pub const fn from_be_bytes(bytes: [u8; 16]) -> Self { + Self(u128::from_be_bytes(bytes)) + } + + /// Returns the IV as big-endian bytes. + #[inline] + #[must_use] + pub const fn to_be_bytes(self) -> [u8; 16] { + self.0.to_be_bytes() + } + + /// Converts this IV to a Block128 for XOR operations. + #[inline] + #[must_use] + pub const fn to_block(self) -> Block128 { + Block128::new(self.0) + } +} + +impl fmt::Display for Iv { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:032X}", self.0) + } +} + +impl FromStr for Iv { + type Err = BlockError; + fn from_str(s: &str) -> Result { + Ok(Self(parse_block_int::(s)?)) + } +} + +impl From<[u8; 16]> for Iv { + fn from(bytes: [u8; 16]) -> Self { + Self::from_be_bytes(bytes) + } +} + +impl From for Iv { + fn from(block: Block128) -> Self { + Self(block.as_u128()) + } +} + +impl From for Block128 { + fn from(iv: Iv) -> Self { + Self::new(iv.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use claims::assert_ok; + + #[test] + fn iv_from_hex_string() { + let iv = assert_ok!("0x000102030405060708090A0B0C0D0E0F".parse::()); + assert_eq!(iv.as_u128(), 0x0001_0203_0405_0607_0809_0A0B_0C0D_0E0F); + } + + #[test] + fn iv_to_block_conversion() { + let iv = Iv::new(0x0011_2233_4455_6677_8899_AABB_CCDD_EEFF); + let block = Block128::from(iv); + assert_eq!(block.as_u128(), 0x0011_2233_4455_6677_8899_AABB_CCDD_EEFF); + } + + #[test] + fn iv_from_bytes() { + let bytes = [ + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, + ]; + let iv = Iv::from_be_bytes(bytes); + assert_eq!(iv.as_u128(), 0x0011_2233_4455_6677_8899_AABB_CCDD_EEFF); + } + + #[test] + fn iv_display_format() { + let iv = Iv::new(0x0011_2233_4455_6677_8899_AABB_CCDD_EEFF); + assert_eq!(format!("{iv}"), "00112233445566778899AABBCCDDEEFF"); + assert_eq!(format!("{iv:x}"), "00112233445566778899aabbccddeeff"); + } +} diff --git a/aes/src/lib.rs b/aes/src/lib.rs index 9c9d2fd..74c070e 100644 --- a/aes/src/lib.rs +++ b/aes/src/lib.rs @@ -14,8 +14,9 @@ mod aes; mod block; mod constants; +mod iv; mod key; mod operations; mod sbox; -pub use {aes::Aes, block::Block128, block::Block32}; +pub use {aes::Aes, block::Block128, block::Block32, iv::Iv}; diff --git a/cipher-core/Cargo.toml b/cipher-core/Cargo.toml index 485f605..232cee9 100644 --- a/cipher-core/Cargo.toml +++ b/cipher-core/Cargo.toml @@ -8,5 +8,8 @@ edition.workspace = true thiserror.workspace = true zeroize.workspace = true +[dev-dependencies] +claims.workspace = true + [lints] workspace = true