use aes::Aes128;
use fpe::ff1::{BinaryNumeralString, FF1};
use openmls_traits::types::CryptoError;
const RADIX: u32 = 2;
pub(crate) fn encrypt(key: &[u8; 16], plaintext: u32) -> Result<u32, CryptoError> {
let ff1 = FF1::<Aes128>::new(key, RADIX).map_err(|_| CryptoError::CryptoLibraryError)?;
let input = BinaryNumeralString::from_bytes_le(&plaintext.to_be_bytes());
let output = ff1
.encrypt(&[], &input)
.map_err(|_| CryptoError::CryptoLibraryError)?;
numeral_string_to_u32(output)
}
pub(crate) fn decrypt(key: &[u8; 16], ciphertext: u32) -> Result<u32, CryptoError> {
let ff1 = FF1::<Aes128>::new(key, RADIX).map_err(|_| CryptoError::CryptoLibraryError)?;
let input = BinaryNumeralString::from_bytes_le(&ciphertext.to_be_bytes());
let output = ff1
.decrypt(&[], &input)
.map_err(|_| CryptoError::CryptoLibraryError)?;
numeral_string_to_u32(output)
}
fn numeral_string_to_u32(numeral_string: BinaryNumeralString) -> Result<u32, CryptoError> {
let bytes: [u8; 4] = numeral_string
.to_bytes_le()
.try_into()
.map_err(|_| CryptoError::CryptoLibraryError)?;
Ok(u32::from_be_bytes(bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let key = [
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
0xee, 0xff,
];
for &x in &[
0u32,
1,
0xff,
0x100,
0xdead_beef,
0x8000_0000,
u32::MAX - 1,
u32::MAX,
] {
let permuted = encrypt(&key, x).expect("encrypt");
let back = decrypt(&key, permuted).expect("decrypt");
assert_eq!(back, x, "round-trip failed for {x:#010x}");
}
}
#[test]
fn deterministic() {
let key = [0xa5u8; 16];
let a = encrypt(&key, 0x1234_5678).expect("encrypt a");
let b = encrypt(&key, 0x1234_5678).expect("encrypt b");
assert_eq!(a, b);
}
#[test]
fn fixed_vector() {
let key = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
0x0e, 0x0f,
];
let x: u32 = 0x0123_4567;
let permuted = encrypt(&key, x).expect("encrypt");
assert_eq!(decrypt(&key, permuted).expect("decrypt"), x);
assert_eq!(permuted, 0xa1ba_5e30, "got {permuted:#010x}");
}
}