Files
KAT/src/protocols/aut64.rs
T
2026-02-07 17:35:27 -05:00

291 lines
10 KiB
Rust

//! AUT64 block cipher implementation
//!
//! Ported from protopirate's aut64.c
//!
//! AUT64 algorithm: 12 rounds, 8-byte block/key size
//! Based on: Reference AUT64 implementation
//! See: https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf
pub const AUT64_NUM_ROUNDS: usize = 12;
pub const AUT64_BLOCK_SIZE: usize = 8;
pub const AUT64_KEY_SIZE: usize = 8;
pub const AUT64_PBOX_SIZE: usize = 8;
pub const AUT64_SBOX_SIZE: usize = 16;
#[allow(dead_code)]
pub const AUT64_KEY_STRUCT_PACKED_SIZE: usize = 16;
/// AUT64 key structure
#[derive(Debug, Clone)]
pub struct Aut64Key {
pub index: u8,
pub key: [u8; AUT64_KEY_SIZE],
pub pbox: [u8; AUT64_PBOX_SIZE],
pub sbox: [u8; AUT64_SBOX_SIZE],
}
impl Default for Aut64Key {
fn default() -> Self {
Self {
index: 0,
key: [0u8; AUT64_KEY_SIZE],
pbox: [0u8; AUT64_PBOX_SIZE],
sbox: [0u8; AUT64_SBOX_SIZE],
}
}
}
/// Round-dependent upper-nibble lookup table
static TABLE_LN: [[u8; 8]; AUT64_NUM_ROUNDS] = [
[0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 0
[0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 1
[0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 2
[0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 3
[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 4
[0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 5
[0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 6
[0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 7
[0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 8
[0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 9
[0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 10
[0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 11
];
/// Round-dependent lower-nibble lookup table
static TABLE_UN: [[u8; 8]; AUT64_NUM_ROUNDS] = [
[0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 0
[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 1
[0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 2
[0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 3
[0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 4
[0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 5
[0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 6
[0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 7
[0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 8
[0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 9
[0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 10
[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 11
];
/// GF(2^4) multiplication table (nibble offset table)
#[rustfmt::skip]
static TABLE_OFFSET: [u8; 256] = [
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 0
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, // 1
0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE, 0x3, 0x1, 0x7, 0x5, 0xB, 0x9, 0xF, 0xD, // 2
0x0, 0x3, 0x6, 0x5, 0xC, 0xF, 0xA, 0x9, 0xB, 0x8, 0xD, 0xE, 0x7, 0x4, 0x1, 0x2, // 3
0x0, 0x4, 0x8, 0xC, 0x3, 0x7, 0xB, 0xF, 0x6, 0x2, 0xE, 0xA, 0x5, 0x1, 0xD, 0x9, // 4
0x0, 0x5, 0xA, 0xF, 0x7, 0x2, 0xD, 0x8, 0xE, 0xB, 0x4, 0x1, 0x9, 0xC, 0x3, 0x6, // 5
0x0, 0x6, 0xC, 0xA, 0xB, 0xD, 0x7, 0x1, 0x5, 0x3, 0x9, 0xF, 0xE, 0x8, 0x2, 0x4, // 6
0x0, 0x7, 0xE, 0x9, 0xF, 0x8, 0x1, 0x6, 0xD, 0xA, 0x3, 0x4, 0x2, 0x5, 0xC, 0xB, // 7
0x0, 0x8, 0x3, 0xB, 0x6, 0xE, 0x5, 0xD, 0xC, 0x4, 0xF, 0x7, 0xA, 0x2, 0x9, 0x1, // 8
0x0, 0x9, 0x1, 0x8, 0x2, 0xB, 0x3, 0xA, 0x4, 0xD, 0x5, 0xC, 0x6, 0xF, 0x7, 0xE, // 9
0x0, 0xA, 0x7, 0xD, 0xE, 0x4, 0x9, 0x3, 0xF, 0x5, 0x8, 0x2, 0x1, 0xB, 0x6, 0xC, // A
0x0, 0xB, 0x5, 0xE, 0xA, 0x1, 0xF, 0x4, 0x7, 0xC, 0x2, 0x9, 0xD, 0x6, 0x8, 0x3, // B
0x0, 0xC, 0xB, 0x7, 0x5, 0x9, 0xE, 0x2, 0xA, 0x6, 0x1, 0xD, 0xF, 0x3, 0x4, 0x8, // C
0x0, 0xD, 0x9, 0x4, 0x1, 0xC, 0x8, 0x5, 0x2, 0xF, 0xB, 0x6, 0x3, 0xE, 0xA, 0x7, // D
0x0, 0xE, 0xF, 0x1, 0xD, 0x3, 0x2, 0xC, 0x9, 0x7, 0x6, 0x8, 0x4, 0xA, 0xB, 0x5, // E
0x0, 0xF, 0xD, 0x2, 0x9, 0x6, 0x4, 0xB, 0x1, 0xE, 0xC, 0x3, 0x8, 0x7, 0x5, 0xA, // F
];
/// S-box substitution table
static TABLE_SUB: [u8; 16] = [
0x0, 0x1, 0x9, 0xE, 0xD, 0xB, 0x7, 0x6,
0xF, 0x2, 0xC, 0x5, 0xA, 0x4, 0x3, 0x8,
];
/// Key nibble operation: apply key-dependent GF offset
fn key_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8], iteration: usize) -> u8 {
let key_value = key.key[table[iteration] as usize];
let offset = ((key_value as usize) << 4) | (nibble as usize);
TABLE_OFFSET[offset]
}
/// Compute round key from state
fn round_key(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
let mut result_hi: u8 = 0;
let mut result_lo: u8 = 0;
for i in 0..(AUT64_BLOCK_SIZE - 1) {
result_hi ^= key_nibble(key, state[i] >> 4, &TABLE_UN[round_n], i);
result_lo ^= key_nibble(key, state[i] & 0x0F, &TABLE_LN[round_n], i);
}
(result_hi << 4) | result_lo
}
/// Final byte nibble for key schedule
fn final_byte_nibble(key: &Aut64Key, table: &[u8; 8]) -> u8 {
let key_value = key.key[table[AUT64_BLOCK_SIZE - 1] as usize];
TABLE_SUB[key_value as usize] << 4
}
/// Encrypt final byte nibble (inverse S-box lookup through offset table)
fn encrypt_final_byte_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8]) -> u8 {
let offset = final_byte_nibble(key, table) as usize;
for i in 0u8..16 {
if TABLE_OFFSET[offset + i as usize] == nibble {
return i;
}
}
0 // Should not reach here for valid inputs
}
/// Encrypt compress: compute encrypted output byte for a round
fn encrypt_compress(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
let round_k = round_key(key, state, round_n);
let mut result_hi = round_k >> 4;
let mut result_lo = round_k & 0x0F;
result_hi ^= encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] >> 4, &TABLE_UN[round_n]);
result_lo ^= encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] & 0x0F, &TABLE_LN[round_n]);
(result_hi << 4) | result_lo
}
/// Decrypt final byte nibble (forward S-box through offset table)
fn decrypt_final_byte_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8], result: u8) -> u8 {
let offset = final_byte_nibble(key, table) as usize;
TABLE_OFFSET[(result ^ nibble) as usize + offset]
}
/// Decrypt compress: compute decrypted output byte for a round
fn decrypt_compress(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
let round_k = round_key(key, state, round_n);
let result_hi = round_k >> 4;
let result_lo = round_k & 0x0F;
let hi = decrypt_final_byte_nibble(
key,
state[AUT64_BLOCK_SIZE - 1] >> 4,
&TABLE_UN[round_n],
result_hi,
);
let lo = decrypt_final_byte_nibble(
key,
state[AUT64_BLOCK_SIZE - 1] & 0x0F,
&TABLE_LN[round_n],
result_lo,
);
(hi << 4) | lo
}
/// S-box substitution on a full byte (applies S-box to each nibble independently)
fn substitute(key: &Aut64Key, byte: u8) -> u8 {
(key.sbox[(byte >> 4) as usize] << 4) | key.sbox[(byte & 0x0F) as usize]
}
/// Byte-level permutation using P-box
fn permute_bytes(key: &Aut64Key, state: &mut [u8]) {
let mut result = [0u8; AUT64_PBOX_SIZE];
for i in 0..AUT64_PBOX_SIZE {
result[key.pbox[i] as usize] = state[i];
}
state[..AUT64_PBOX_SIZE].copy_from_slice(&result);
}
/// Bit-level permutation using P-box
fn permute_bits(key: &Aut64Key, byte: u8) -> u8 {
let mut result: u8 = 0;
for i in 0..8 {
if byte & (1 << i) != 0 {
result |= 1 << key.pbox[i];
}
}
result
}
/// Compute inverse permutation box
fn reverse_box(box_in: &[u8], len: usize) -> Vec<u8> {
let mut reversed = vec![0u8; len];
for i in 0..len {
for j in 0..len {
if box_in[j] == i as u8 {
reversed[i] = j as u8;
break;
}
}
}
reversed
}
/// AUT64 encrypt: 12 rounds of the cipher
pub fn aut64_encrypt(key: &Aut64Key, message: &mut [u8]) {
// Create reverse key for encryption
let mut reverse_key = key.clone();
let rev_pbox = reverse_box(&key.pbox, AUT64_PBOX_SIZE);
let rev_sbox = reverse_box(&key.sbox, AUT64_SBOX_SIZE);
reverse_key.pbox.copy_from_slice(&rev_pbox);
reverse_key.sbox.copy_from_slice(&rev_sbox);
for i in 0..AUT64_NUM_ROUNDS {
permute_bytes(&reverse_key, message);
message[7] = encrypt_compress(&reverse_key, message, i);
message[7] = substitute(&reverse_key, message[7]);
message[7] = permute_bits(&reverse_key, message[7]);
message[7] = substitute(&reverse_key, message[7]);
}
}
/// AUT64 decrypt: 12 rounds of the cipher (reverse order)
pub fn aut64_decrypt(key: &Aut64Key, message: &mut [u8]) {
for i in (0..AUT64_NUM_ROUNDS).rev() {
message[7] = substitute(key, message[7]);
message[7] = permute_bits(key, message[7]);
message[7] = substitute(key, message[7]);
message[7] = decrypt_compress(key, message, i);
permute_bytes(key, message);
}
}
/// Pack an AUT64 key structure into a 16-byte array
#[allow(dead_code)]
pub fn aut64_pack(src: &Aut64Key) -> [u8; AUT64_KEY_STRUCT_PACKED_SIZE] {
let mut dest = [0u8; AUT64_KEY_STRUCT_PACKED_SIZE];
dest[0] = src.index;
for i in 0..(src.key.len() / 2) {
dest[i + 1] = (src.key[i * 2] << 4) | src.key[i * 2 + 1];
}
let mut pbox: u32 = 0;
for i in 0..src.pbox.len() {
pbox = (pbox << 3) | src.pbox[i] as u32;
}
dest[5] = (pbox >> 16) as u8;
dest[6] = ((pbox >> 8) & 0xFF) as u8;
dest[7] = (pbox & 0xFF) as u8;
for i in 0..(src.sbox.len() / 2) {
dest[i + 8] = (src.sbox[i * 2] << 4) | src.sbox[i * 2 + 1];
}
dest
}
/// Unpack a 16-byte array into an AUT64 key structure
#[allow(dead_code)]
pub fn aut64_unpack(src: &[u8]) -> Aut64Key {
let mut dest = Aut64Key::default();
dest.index = src[0];
for i in 0..(dest.key.len() / 2) {
dest.key[i * 2] = src[i + 1] >> 4;
dest.key[i * 2 + 1] = src[i + 1] & 0xF;
}
let pbox: u32 = ((src[5] as u32) << 16) | ((src[6] as u32) << 8) | src[7] as u32;
for i in (0..dest.pbox.len()).rev() {
dest.pbox[i] = ((pbox >> ((dest.pbox.len() - 1 - i) * 3)) & 0x7) as u8;
}
for i in 0..(dest.sbox.len() / 2) {
dest.sbox[i * 2] = src[i + 8] >> 4;
dest.sbox[i * 2 + 1] = src[i + 8] & 0xF;
}
dest
}