mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-20 11:04:38 +00:00
refactor(aes): move to operations module
This commit is contained in:
parent
505cc8b08e
commit
37f8a97a11
@ -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)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<u8> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -2,6 +2,7 @@ mod aes;
|
||||
mod block;
|
||||
mod constants;
|
||||
mod key;
|
||||
mod operations;
|
||||
mod sbox;
|
||||
mod utils;
|
||||
|
||||
|
||||
158
aes/src/operations/column_mix.rs
Normal file
158
aes/src/operations/column_mix.rs
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
11
aes/src/operations/mod.rs
Normal file
11
aes/src/operations/mod.rs
Normal file
@ -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},
|
||||
};
|
||||
43
aes/src/operations/round_key.rs
Normal file
43
aes/src/operations/round_key.rs
Normal file
@ -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)"
|
||||
);
|
||||
}
|
||||
}
|
||||
100
aes/src/operations/row_shift.rs
Normal file
100
aes/src/operations/row_shift.rs
Normal file
@ -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}",
|
||||
);
|
||||
}
|
||||
}
|
||||
13
aes/src/operations/sbox_lookup.rs
Normal file
13
aes/src/operations/sbox_lookup.rs
Normal file
@ -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()
|
||||
}
|
||||
@ -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}"
|
||||
// );
|
||||
// }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user