feat(aes): add IV (Initialization Vector) type for CBC mode

Add 128-bit IV type using secret_block! macro with:
    - Parsing from hex/binary/ASCII strings
    - Conversions to/from Block128 for XOR operations
    - Big-endian byte array conversions
This commit is contained in:
Kristofers Solo 2025-12-31 00:37:42 +02:00
parent 220baa09ad
commit f4480ba218
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
3 changed files with 118 additions and 1 deletions

113
aes/src/iv.rs Normal file
View File

@ -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<Self, Self::Err> {
Ok(Self(parse_block_int::<u128>(s)?))
}
}
impl From<[u8; 16]> for Iv {
fn from(bytes: [u8; 16]) -> Self {
Self::from_be_bytes(bytes)
}
}
impl From<Block128> for Iv {
fn from(block: Block128) -> Self {
Self(block.as_u128())
}
}
impl From<Iv> 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::<Iv>());
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");
}
}

View File

@ -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};

View File

@ -8,5 +8,8 @@ edition.workspace = true
thiserror.workspace = true
zeroize.workspace = true
[dev-dependencies]
claims.workspace = true
[lints]
workspace = true