diff --git a/aes/src/aes.rs b/aes/src/aes.rs index db5e3b7..ad182cc 100644 --- a/aes/src/aes.rs +++ b/aes/src/aes.rs @@ -1,9 +1,12 @@ use crate::{ Block128, - key::{Key, Subkey, Subkeys}, - sbox::SboxLookup, + key::{Key, Subkeys}, + operations::{ + add_round_key, inv_mix_columns, inv_shift_rows, inv_sub_bytes, mix_columns, shift_rows, + sub_bytes, + }, }; -use cipher_core::{BlockCipher, CipherError}; +use cipher_core::{BlockCipher, CipherAction, CipherError}; pub struct Aes { subkeys: Subkeys, @@ -16,20 +19,27 @@ impl Aes { } } + #[cfg(test)] + #[inline] + #[must_use] + pub const fn subkeys(&self) -> &Subkeys { + &self.subkeys + } + fn encryot_block(&self, mut state: Block128) -> Block128 { let mut keys = self.subkeys.chunks(); state = add_round_key(state, keys.next().expect("Round key 0")); for _ in 1..10 { - state = state.sub_bytes(); - state = state.shift_rows(); - state = state.mix_columns(); + state = sub_bytes(state); + state = shift_rows(state); + state = mix_columns(state); state = add_round_key(state, keys.next().expect("Round key")); } // Final round: SubBytes, ShiftRows, AddRoundKey (no MixColumns) - state = state.sub_bytes(); - state = state.shift_rows(); + state = sub_bytes(state); + state = shift_rows(state); state = add_round_key(state, keys.next().expect("Final Round key")); state @@ -40,15 +50,15 @@ impl Aes { state = add_round_key(state, keys.next().expect("Final round key")); for _ in 1..10 { - state = state.inv_shift_rows(); - state = state.inv_sub_bytes(); + state = inv_shift_rows(state); + state = inv_sub_bytes(state); state = add_round_key(state, keys.next().expect("Round key")); - state = state.inv_mix_columns(); + state = inv_mix_columns(state); } // Final round: SubBytes, ShiftRows, AddRoundKey (no MixColumns) - state = state.inv_shift_rows(); - state = state.inv_sub_bytes(); + state = inv_shift_rows(state); + state = inv_sub_bytes(state); state = add_round_key(state, keys.next().expect("Round key 0")); state @@ -73,50 +83,10 @@ impl BlockCipher for Aes { let block128 = Block128::from_be_bytes(block_arr); let result = match action { - cipher_core::CipherAction::Encrypt => self.encryot_block(block128), - cipher_core::CipherAction::Decrypt => self.decryot_block(block128), + CipherAction::Encrypt => self.encryot_block(block128), + CipherAction::Decrypt => self.decryot_block(block128), }; Ok(result.into()) } } - -const fn add_round_key(state: Block128, subkeys: &[Subkey; 4]) -> Block128 { - let k0 = subkeys[0].as_u128(); - let k1 = subkeys[1].as_u128(); - let k2 = subkeys[2].as_u128(); - let k3 = subkeys[3].as_u128(); - let key_block = (k0 << 96) | (k1 << 64) | (k2 << 32) | k3; - Block128::new(state.as_u128() ^ key_block) -} - -#[cfg(test)] -mod tests { - use super::*; - use rstest::rstest; - - const TEST_KEY: u128 = 0x2B7E_1516_28AE_D2A6_ABF7_1588_09CF_4F3C; - - #[rstest] - #[case(0x0000_0000_0000_0000_0000_0000_0000_0000)] - #[case(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF)] - #[case(0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0)] - fn add_round_key_roundtrip(#[case] plaintext: u128) { - let aes = Aes::new(TEST_KEY); - let state = Block128::new(plaintext); - - // Get first round key - let mut keys = aes.subkeys.chunks(); - let first_key = keys.next().expect("First round key"); - - // AddRoundKey twice should return to original - let xored_once = add_round_key(state, first_key); - let xored_twice = add_round_key(xored_once, first_key); - - assert_eq!( - xored_twice.as_u128(), - plaintext, - "AddRoundKey should be self-inverse (double XOR returns to original)" - ); - } -} diff --git a/aes/src/block/block128.rs b/aes/src/block/block128.rs index 3abf48b..d9535b1 100644 --- a/aes/src/block/block128.rs +++ b/aes/src/block/block128.rs @@ -4,7 +4,7 @@ use crate::{ }; use cipher_core::{BlockError, InputBlock}; use std::{ - slice::{ChunksExact, from_raw_parts, from_raw_parts_mut}, + slice::{from_raw_parts, from_raw_parts_mut}, str::FromStr, }; @@ -62,163 +62,6 @@ impl Block128 { Block32::from_u32(val as u32), ] } - - #[must_use] - pub const fn shift_rows(self) -> Self { - let b = self.to_be_bytes(); - let mut out = [0u8; 16]; - - // Row 0: No shift (Indices 0, 4, 8, 12) - out[0] = b[0]; - out[4] = b[4]; - out[8] = b[8]; - out[12] = b[12]; - - // Row 1: Shift left 1 (Indices 1, 5, 9, 13 -> 5, 9, 13, 1) - out[1] = b[5]; - out[5] = b[9]; - out[9] = b[13]; - out[13] = b[1]; - - // Row 2: Shift left 2 (Indices 2, 6, 10, 14 -> 10, 14, 2, 6) - out[2] = b[10]; - out[6] = b[14]; - out[10] = b[2]; - out[14] = b[6]; - - // Row 3: Shift left 3 (Indices 3, 7, 11, 15 -> 15, 3, 7, 11) - out[3] = b[15]; - out[7] = b[3]; - out[11] = b[7]; - out[15] = b[11]; - - Self::from_be_bytes(out) - } - - #[must_use] - pub fn mix_columns(self) -> Self { - let mut bytes = self.to_be_bytes(); - - for col in 0..4 { - let offset = col * 4; - - let c0 = bytes[offset]; - let c1 = bytes[offset + 1]; - let c2 = bytes[offset + 2]; - let c3 = bytes[offset + 3]; - - // Matrix multiplication over GF(2^8): - // [d0] [2 3 1 1] [c0] - // [d1] = [1 2 3 1] [c1] - // [d2] [1 1 2 3] [c2] - // [d3] [3 1 1 2] [c3] - - bytes[offset] = gmul(c0, 2) ^ gmul(c1, 3) ^ c2 ^ c3; - bytes[offset + 1] = c0 ^ gmul(c1, 2) ^ gmul(c2, 3) ^ c3; - bytes[offset + 2] = c0 ^ c1 ^ gmul(c2, 2) ^ gmul(c3, 3); - bytes[offset + 3] = gmul(c0, 3) ^ c1 ^ c2 ^ gmul(c3, 2); - } - - Self::from_be_bytes(bytes) - } - - #[must_use] - pub const fn inv_shift_rows(self) -> Self { - let b = self.to_be_bytes(); - let mut out = [0u8; 16]; - - // Row 0 (Indices 0, 4, 8, 12): No shift - out[0] = b[0]; - out[4] = b[4]; - out[8] = b[8]; - out[12] = b[12]; - - // Row 1 (Indices 1, 5, 9, 13): Shift right 1 -> (13, 1, 5, 9) - out[1] = b[13]; - out[5] = b[1]; - out[9] = b[5]; - out[13] = b[9]; - - // Row 2 (Indices 2, 6, 10, 14): Shift right 2 -> (10, 14, 2, 6) - out[2] = b[10]; - out[6] = b[14]; - out[10] = b[2]; - out[14] = b[6]; - - // Row 3 (Indices 3, 7, 11, 15): Shift right 3 -> (7, 11, 15, 3) - out[3] = b[7]; - out[7] = b[11]; - out[11] = b[15]; - out[15] = b[3]; - - Self::from_be_bytes(out) - } - - #[must_use] - pub fn inv_mix_columns(self) -> Self { - let mut bytes = self.to_be_bytes(); - - // Process 4 columns independently - for col in 0..4 { - let offset = col * 4; - let c0 = bytes[offset]; - let c1 = bytes[offset + 1]; - let c2 = bytes[offset + 2]; - let c3 = bytes[offset + 3]; - - // Inverse matrix multiplication: - // [14 11 13 9] - // [ 9 14 11 13] - // [13 9 14 11] - // [11 13 9 14] - - bytes[offset] = gmul(c0, 14) ^ gmul(c1, 11) ^ gmul(c2, 13) ^ gmul(c3, 9); - bytes[offset + 1] = gmul(c0, 9) ^ gmul(c1, 14) ^ gmul(c2, 11) ^ gmul(c3, 13); - bytes[offset + 2] = gmul(c0, 13) ^ gmul(c1, 9) ^ gmul(c2, 14) ^ gmul(c3, 11); - bytes[offset + 3] = gmul(c0, 11) ^ gmul(c1, 13) ^ gmul(c2, 9) ^ gmul(c3, 14); - } - - Self::from_be_bytes(bytes) - } - - #[inline] - #[must_use] - pub fn sub_bytes(self) -> Self { - Self(self.0.sbox_lookup()) - } - - #[inline] - #[must_use] - pub fn inv_sub_bytes(self) -> Self { - Self(self.0.inv_sbox_lookup()) - } -} - -/// Galois Field multiplication by 2 (xtime). -/// If the high bit is set, XOR with the irreducible polynomial 0x1B. -const fn xtime(x: u8) -> u8 { - if x & 0x80 != 0 { - return (x << 1) ^ 0x1b; - } - x << 1 -} - -/// General Galois Field multiplication. -/// Implemented using "peasant's algorithm" (shift and add). -const fn gmul(mut a: u8, mut b: u8) -> u8 { - let mut p = 0; - let mut i = 0; - - // Unrolled loop for const context - while i < 8 { - if (b & 1) != 0 { - p ^= a; - } - a = xtime(a); - b >>= 1; - i += 1; - } - p } impl FromStr for Block128 { @@ -304,83 +147,3 @@ impl From<&Block128> for Vec { value.to_be_bytes().to_vec() } } - -#[cfg(test)] -mod tests { - use super::*; - use rstest::rstest; - - #[rstest] - #[case( - 0x63CA_B704_0953_D051_CD60_E0E7_BA70_E18C, - 0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7 - )] - fn shift_rows(#[case] input: u128, #[case] expected: u128) { - let block = Block128::new(input); - let result = block.shift_rows().as_u128(); - assert_eq!( - result, expected, - "Shift Rows failed. Expected 0x{expected:032X}, got 0x{result:032X}", - ); - } - - #[rstest] - #[case( - 0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7, - 0x5F72_6415_57F5_BC92_F7BE_3B29_1DB9_F91A - )] - #[case( - 0xD4BF_5D30_D4BF_5D30_D4BF_5D30_D4BF_5D30, - 0x0466_81E5_0466_81E5_0466_81E5_0466_81E5 - )] - fn mix_columns(#[case] input: u128, #[case] expected: u128) { - let block = Block128::new(input); - let result = block.mix_columns().as_u128(); - assert_eq!( - result, expected, - "Mix Columns failed. Expected 0x{expected:032X}, got 0x{result:032X}", - ); - } - - #[rstest] - #[case(0x63CA_B704_0953_D051_CD60_E0E7_BA70_E18C)] - #[case(0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7)] - #[case(0xD4BF_5D30_D4BF_5D30_D4BF_5D30_D4BF_5D30)] - fn inv_shift_rows_is_inverse(#[case] input: u128) { - let block = Block128::new(input); - let shifted = block.shift_rows(); - let unshifted = shifted.inv_shift_rows().as_u128(); - - assert_eq!( - unshifted, input, - "InvShiftRows(ShiftRows(x)) != x. Expected 0x{input:032X}, got 0x{unshifted:032X}", - ); - } - - #[rstest] - #[case(0x63CA_B704_0953_D051_CD60_E0E7_BA70_E18C)] - #[case(0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7)] - #[case(0xD4BF_5D30_D4BF_5D30_D4BF_5D30_D4BF_5D30)] - fn inv_mix_columns_is_inverse(#[case] input: u128) { - let block = Block128::new(input); - let mixed = block.mix_columns(); - let unmixed = mixed.inv_mix_columns().as_u128(); - - assert_eq!( - unmixed, input, - "InvMixColumns(MixColumns(x)) != x. Expected 0x{input:032X}, got 0x{unmixed:032X}", - ); - } - - #[rstest] - #[case(0x57, 0x13, 0xFE)] // Example from FIPS-197 4.2.1 - #[case(0x57, 0x01, 0x57)] // Identity - #[case(0x57, 0x02, 0xAE)] // x2 (xtime) - #[case(0x57, 0x04, 0x47)] // x4 - #[case(0x57, 0x08, 0x8E)] // x8 - #[case(0x57, 0x10, 0x07)] // x16 - fn galois_multiplication(#[case] a: u8, #[case] b: u8, #[case] expected: u8) { - let res = gmul(a, b); - assert_eq!(res, expected, "gmul({a:02x}, {b:02x}) failed"); - } -} diff --git a/aes/src/key/mod.rs b/aes/src/key/mod.rs index c3a36e9..9bbdd55 100644 --- a/aes/src/key/mod.rs +++ b/aes/src/key/mod.rs @@ -4,8 +4,4 @@ mod subkey; mod subkeys; use crate::secret_key; -pub use { - aes_key::Key, - subkey::Subkey, - subkeys::{SubkeyChunks, SubkeyChunksRev, Subkeys}, -}; +pub use {aes_key::Key, subkey::Subkey, subkeys::Subkeys}; diff --git a/aes/src/lib.rs b/aes/src/lib.rs index ed31ae7..e12bf30 100644 --- a/aes/src/lib.rs +++ b/aes/src/lib.rs @@ -2,6 +2,7 @@ mod aes; mod block; mod constants; mod key; +mod operations; mod sbox; mod utils; diff --git a/aes/src/operations/column_mix.rs b/aes/src/operations/column_mix.rs new file mode 100644 index 0000000..e0c064b --- /dev/null +++ b/aes/src/operations/column_mix.rs @@ -0,0 +1,158 @@ +use crate::Block128; + +#[must_use] +pub fn mix_columns(block: Block128) -> Block128 { + let mut bytes = block.to_be_bytes(); + + for col in 0..4 { + let offset = col * 4; + let [c0, c1, c2, c3] = [ + bytes[offset], + bytes[offset + 1], + bytes[offset + 2], + bytes[offset + 3], + ]; + + // Matrix multiplication over GF(2^8): + // [d0] [2 3 1 1] [c0] + // [d1] = [1 2 3 1] [c1] + // [d2] [1 1 2 3] [c2] + // [d3] [3 1 1 2] [c3] + + bytes[offset] = MIX_2[c0 as usize] ^ MIX_3[c1 as usize] ^ c2 ^ c3; + bytes[offset + 1] = c0 ^ MIX_2[c1 as usize] ^ MIX_3[c2 as usize] ^ c3; + bytes[offset + 2] = c0 ^ c1 ^ MIX_2[c2 as usize] ^ MIX_3[c3 as usize]; + bytes[offset + 3] = MIX_3[c0 as usize] ^ c1 ^ c2 ^ MIX_2[c3 as usize]; + } + + Block128::from_be_bytes(bytes) +} + +#[must_use] +pub fn inv_mix_columns(block: Block128) -> Block128 { + let mut bytes = block.to_be_bytes(); + + // Process 4 columns independently + for col in 0..4 { + let offset = col * 4; + let [c0, c1, c2, c3] = [ + bytes[offset], + bytes[offset + 1], + bytes[offset + 2], + bytes[offset + 3], + ]; + + // Inverse matrix multiplication: + // [14 11 13 9] + // [ 9 14 11 13] + // [13 9 14 11] + // [11 13 9 14] + + bytes[offset] = + MIX_14[c0 as usize] ^ MIX_11[c1 as usize] ^ MIX_13[c2 as usize] ^ MIX_9[c3 as usize]; + bytes[offset + 1] = + MIX_9[c0 as usize] ^ MIX_14[c1 as usize] ^ MIX_11[c2 as usize] ^ MIX_13[c3 as usize]; + bytes[offset + 2] = + MIX_13[c0 as usize] ^ MIX_9[c1 as usize] ^ MIX_14[c2 as usize] ^ MIX_11[c3 as usize]; + bytes[offset + 3] = + MIX_11[c0 as usize] ^ MIX_13[c1 as usize] ^ MIX_9[c2 as usize] ^ MIX_14[c3 as usize]; + } + + Block128::from_be_bytes(bytes) +} + +/// Galois Field multiplication by 2 (xtime). +/// If the high bit is set, XOR with the irreducible polynomial 0x1B. +const fn xtime(x: u8) -> u8 { + if x & 0x80 != 0 { + return (x << 1) ^ 0x1b; + } + x << 1 +} + +/// General Galois Field multiplication. +/// Implemented using "peasant's algorithm" (shift and add). +const fn gmul(mut a: u8, mut b: u8) -> u8 { + let mut p = 0; + let mut i = 0; + + while i < 8 { + if (b & 1) != 0 { + p ^= a; + } + a = xtime(a); + b >>= 1; + i += 1; + } + p +} + +#[must_use] +#[allow(clippy::cast_possible_truncation)] +const fn make_gmul_table(factor: u8) -> [u8; 256] { + let mut table = [0; 256]; + let mut i = 0; + while i < 256 { + table[i] = gmul(i as u8, factor); + i += 1; + } + table +} + +const MIX_2: [u8; 256] = make_gmul_table(2); +const MIX_3: [u8; 256] = make_gmul_table(3); +const MIX_9: [u8; 256] = make_gmul_table(9); +const MIX_11: [u8; 256] = make_gmul_table(11); +const MIX_13: [u8; 256] = make_gmul_table(13); +const MIX_14: [u8; 256] = make_gmul_table(14); + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[case( + 0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7, + 0x5F72_6415_57F5_BC92_F7BE_3B29_1DB9_F91A + )] + #[case( + 0xD4BF_5D30_D4BF_5D30_D4BF_5D30_D4BF_5D30, + 0x0466_81E5_0466_81E5_0466_81E5_0466_81E5 + )] + fn columns_mix(#[case] input: u128, #[case] expected: u128) { + let block = Block128::new(input); + let result = mix_columns(block).as_u128(); + assert_eq!( + result, expected, + "Mix Columns failed. Expected 0x{expected:032X}, got 0x{result:032X}", + ); + } + + #[rstest] + #[case(0x63CA_B704_0953_D051_CD60_E0E7_BA70_E18C)] + #[case(0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7)] + #[case(0xD4BF_5D30_D4BF_5D30_D4BF_5D30_D4BF_5D30)] + fn inv_mix_columns_is_inverse(#[case] input: u128) { + let block = Block128::new(input); + let mixed = mix_columns(block); + let unmixed = inv_mix_columns(mixed).as_u128(); + + assert_eq!( + unmixed, input, + "InvMixColumns(MixColumns(x)) != x. Expected 0x{input:032X}, got 0x{unmixed:032X}", + ); + } + + #[rstest] + #[case(0x57, 0x13, 0xFE)] // Example from FIPS-197 4.2.1 + #[case(0x57, 0x01, 0x57)] // Identity + #[case(0x57, 0x02, 0xAE)] // x2 (xtime) + #[case(0x57, 0x04, 0x47)] // x4 + #[case(0x57, 0x08, 0x8E)] // x8 + #[case(0x57, 0x10, 0x07)] // x16 + fn galois_multiplication(#[case] a: u8, #[case] b: u8, #[case] expected: u8) { + let res = gmul(a, b); + assert_eq!(res, expected, "gmul({a:02x}, {b:02x}) failed"); + } +} diff --git a/aes/src/operations/mod.rs b/aes/src/operations/mod.rs new file mode 100644 index 0000000..b8434a9 --- /dev/null +++ b/aes/src/operations/mod.rs @@ -0,0 +1,11 @@ +mod column_mix; +mod round_key; +mod row_shift; +mod sbox_lookup; + +pub use { + column_mix::{inv_mix_columns, mix_columns}, + round_key::add_round_key, + row_shift::{inv_shift_rows, shift_rows}, + sbox_lookup::{inv_sub_bytes, sub_bytes}, +}; diff --git a/aes/src/operations/round_key.rs b/aes/src/operations/round_key.rs new file mode 100644 index 0000000..ccbc6ca --- /dev/null +++ b/aes/src/operations/round_key.rs @@ -0,0 +1,43 @@ +use crate::{Block128, key::Subkey}; + +pub const fn add_round_key(state: Block128, subkeys: &[Subkey; 4]) -> Block128 { + let k0 = subkeys[0].as_u128(); + let k1 = subkeys[1].as_u128(); + let k2 = subkeys[2].as_u128(); + let k3 = subkeys[3].as_u128(); + let key_block = (k0 << 96) | (k1 << 64) | (k2 << 32) | k3; + Block128::new(state.as_u128() ^ key_block) +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + const TEST_KEY: u128 = 0x0F15_71C9_47D9_E859_1CB7_ADD6_AF7F_6798; + + #[rstest] + #[case(0x0000_0000_0000_0000_0000_0000_0000_0000)] + #[case(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF)] + #[case(0x1234_5678_9ABC_DEF0_1234_5678_9ABC_DEF0)] + fn add_round_key_roundtrip(#[case] plaintext: u128) { + use crate::Aes; + + let aes = Aes::new(TEST_KEY); + let state = Block128::new(plaintext); + + // Get first round key + let mut keys = aes.subkeys().chunks(); + let first_key = keys.next().expect("First round key"); + + // AddRoundKey twice should return to original + let xored_once = add_round_key(state, first_key); + let xored_twice = add_round_key(xored_once, first_key); + + assert_eq!( + xored_twice.as_u128(), + plaintext, + "AddRoundKey should be self-inverse (double XOR returns to original)" + ); + } +} diff --git a/aes/src/operations/row_shift.rs b/aes/src/operations/row_shift.rs new file mode 100644 index 0000000..6db8282 --- /dev/null +++ b/aes/src/operations/row_shift.rs @@ -0,0 +1,100 @@ +use crate::Block128; + +#[must_use] +pub const fn shift_rows(block: Block128) -> Block128 { + let b = block.to_be_bytes(); + let mut out = [0u8; 16]; + + // Row 0: No shift (Indices 0, 4, 8, 12) + out[0] = b[0]; + out[4] = b[4]; + out[8] = b[8]; + out[12] = b[12]; + + // Row 1: Shift left 1 (Indices 1, 5, 9, 13 -> 5, 9, 13, 1) + out[1] = b[5]; + out[5] = b[9]; + out[9] = b[13]; + out[13] = b[1]; + + // Row 2: Shift left 2 (Indices 2, 6, 10, 14 -> 10, 14, 2, 6) + out[2] = b[10]; + out[6] = b[14]; + out[10] = b[2]; + out[14] = b[6]; + + // Row 3: Shift left 3 (Indices 3, 7, 11, 15 -> 15, 3, 7, 11) + out[3] = b[15]; + out[7] = b[3]; + out[11] = b[7]; + out[15] = b[11]; + + Block128::from_be_bytes(out) +} + +#[must_use] +pub const fn inv_shift_rows(block: Block128) -> Block128 { + let b = block.to_be_bytes(); + let mut out = [0u8; 16]; + + // Row 0 (Indices 0, 4, 8, 12): No shift + out[0] = b[0]; + out[4] = b[4]; + out[8] = b[8]; + out[12] = b[12]; + + // Row 1 (Indices 1, 5, 9, 13): Shift right 1 -> (13, 1, 5, 9) + out[1] = b[13]; + out[5] = b[1]; + out[9] = b[5]; + out[13] = b[9]; + + // Row 2 (Indices 2, 6, 10, 14): Shift right 2 -> (10, 14, 2, 6) + out[2] = b[10]; + out[6] = b[14]; + out[10] = b[2]; + out[14] = b[6]; + + // Row 3 (Indices 3, 7, 11, 15): Shift right 3 -> (7, 11, 15, 3) + out[3] = b[7]; + out[7] = b[11]; + out[11] = b[15]; + out[15] = b[3]; + + Block128::from_be_bytes(out) +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + + #[rstest] + #[case( + 0x63CA_B704_0953_D051_CD60_E0E7_BA70_E18C, + 0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7 + )] + fn row_shift(#[case] input: u128, #[case] expected: u128) { + let block = Block128::new(input); + let result = shift_rows(block).as_u128(); + assert_eq!( + result, expected, + "Shift Rows failed. Expected 0x{expected:032X}, got 0x{result:032X}", + ); + } + + #[rstest] + #[case(0x63CA_B704_0953_D051_CD60_E0E7_BA70_E18C)] + #[case(0x6353_E08C_0960_E104_CD70_B751_BACA_D0E7)] + #[case(0xD4BF_5D30_D4BF_5D30_D4BF_5D30_D4BF_5D30)] + fn inv_shift_rows_is_inverse(#[case] input: u128) { + let block = Block128::new(input); + let shifted = shift_rows(block); + let unshifted = inv_shift_rows(shifted).as_u128(); + + assert_eq!( + unshifted, input, + "InvShiftRows(ShiftRows(x)) != x. Expected 0x{input:032X}, got 0x{unshifted:032X}", + ); + } +} diff --git a/aes/src/operations/sbox_lookup.rs b/aes/src/operations/sbox_lookup.rs new file mode 100644 index 0000000..8ab44d8 --- /dev/null +++ b/aes/src/operations/sbox_lookup.rs @@ -0,0 +1,13 @@ +use crate::{Block128, sbox::SboxLookup}; + +#[inline] +#[must_use] +pub fn sub_bytes(block: Block128) -> Block128 { + block.sbox_lookup() +} + +#[inline] +#[must_use] +pub fn inv_sub_bytes(block: Block128) -> Block128 { + block.inv_sbox_lookup() +} diff --git a/aes/tests/aes.rs b/aes/tests/aes.rs index 6ded83c..b36ea00 100644 --- a/aes/tests/aes.rs +++ b/aes/tests/aes.rs @@ -1,35 +1,33 @@ +use aes::Aes; use cipher_core::BlockCipher; use rstest::rstest; const TEST_KEY: u128 = 0x2B7E_1516_28AE_D2A6_ABF7_1588_09CF_4F3C; const TEST_PLAINTEXT: u128 = 0x6BC1_BEE2_2E40_9F96_E93D_7E11_7393_1728; -const TEST_CIPHERTEXT: u128 = 0x3AD7_7BB4_0D7A_3660_A89E_CAF3_2466_EF97; +const TEST_CIPHERTEXT: u128 = 0x79BD_98A6_CB0F_D3AE_3D7D_C1A3_3CD3_6E2F; // #[rstest] // #[case(TEST_PLAINTEXT, TEST_CIPHERTEXT, TEST_KEY)] -// // NIST SP 800-38A ECB mode test vectors // #[case( // 0xAE2D_8A57_1E03_AC9C_9EB7_6FAC_45AF_8E51, -// 0xF5D3_D585_03B9_699D_E785_895A_96FD_BAAF, -// 0x2B7E_1516_28AE_D2A6_ABF7_1588_09CF_4F3C +// 0xBC14_003D_01C9_B46C_AC63_D481_5210_E80B, +// 0xF5D3_D585_03B9_699D_E785_895A_96FD_BAAF // )] // #[case( // 0x30C8_1C46_A35C_E411_E5FB_C119_1A0A_52EF, -// 0x43B1_CD7F_598E_CE23_881B_00E3_ED03_0688, -// 0x2B7E_1516_28AE_D2A6_ABF7_1588_09CF_4F3C +// 0xB9AF_FEE2_98CD_0F4A_6708_44A6_D6CE_EF87, +// 0x43B1_CD7F_598E_CE23_881B_00E3_ED03_0688 // )] // #[case( // 0xF69F_2445_DF4F_9B17_AD2B_417B_E66C_3710, -// 0x7B0C_785E_27E8_AD3F_8223_2071_0472_5DD4, -// 0x2B7E_1516_28AE_D2A6_ABF7_1588_09CF_4F3C +// 0xA279_FA71_A91B_9FA9_213C_E13E_659D_5C3B, +// 0x7B0C_785E_27E8_AD3F_8223_2071_0472_5DD4 // )] // fn encrypt_decrypt_roundtrip( // #[case] plaintext: u128, // #[case] expected_ciphertext: u128, // #[case] key: u128, // ) { -// use aes::Aes; -// // let aes = Aes::new(key); // let pt_bytes = plaintext.to_be_bytes(); // @@ -39,7 +37,7 @@ const TEST_CIPHERTEXT: u128 = 0x3AD7_7BB4_0D7A_3660_A89E_CAF3_2466_EF97; // // assert_eq!( // ciphertext_u128, expected_ciphertext, -// "Encryption mismatch.\nExpected: 0x{expected_ciphertext:032X}\nGot: 0x{ciphertext_u128:032X}" +// "Encryption mismatch. Expected 0x{expected_ciphertext:032X}, got 0x{ciphertext_u128:032X}" // ); // // // Test Decrypt @@ -48,6 +46,6 @@ const TEST_CIPHERTEXT: u128 = 0x3AD7_7BB4_0D7A_3660_A89E_CAF3_2466_EF97; // // assert_eq!( // decrypted_u128, plaintext, -// "Decryption mismatch.\nExpected: 0x{plaintext:032X}\nGot: 0x{decrypted_u128:032X}" +// "Decryption mismatch. Expected 0x{plaintext:032X}, got 0x{decrypted_u128:032X}" // ); // }