use crate::Error;
use digest::KeyInit;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncryptionType {
Aes256CtsHmacSha196 = 18,
Aes128CtsHmacSha196 = 17,
Rc4Hmac = 23,
}
pub fn string_to_key_aes(password: &str, salt: &str, key_size: usize) -> Vec<u8> {
use sha1::Sha1;
assert!(
key_size == 16 || key_size == 32,
"key_size must be 16 or 32"
);
let mut raw_key = vec![0u8; key_size];
pbkdf2::pbkdf2_hmac::<Sha1>(password.as_bytes(), salt.as_bytes(), 4096, &mut raw_key);
dk_derive(&raw_key, b"kerberos")
}
pub fn string_to_key_rc4(password: &str) -> Vec<u8> {
use digest::Digest;
let unicode_password: Vec<u8> = password
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let mut hasher = md4::Md4::new();
hasher.update(&unicode_password);
hasher.finalize().to_vec()
}
pub fn derive_key_aes(base_key: &[u8], usage: &[u8]) -> Vec<u8> {
dk_derive(base_key, usage)
}
pub fn usage_enc(usage: u32) -> [u8; 5] {
let mut out = [0u8; 5];
out[0..4].copy_from_slice(&usage.to_be_bytes());
out[4] = 0xAA;
out
}
pub fn usage_int(usage: u32) -> [u8; 5] {
let mut out = [0u8; 5];
out[0..4].copy_from_slice(&usage.to_be_bytes());
out[4] = 0x55;
out
}
pub fn usage_chk(usage: u32) -> [u8; 5] {
let mut out = [0u8; 5];
out[0..4].copy_from_slice(&usage.to_be_bytes());
out[4] = 0x99;
out
}
pub fn encrypt_aes_cts(key: &[u8], iv: &[u8], plaintext: &[u8]) -> Vec<u8> {
if plaintext.is_empty() {
return Vec::new();
}
let block_size = 16;
if plaintext.len() <= block_size {
let mut padded = [0u8; 16];
padded[..plaintext.len()].copy_from_slice(plaintext);
for i in 0..16 {
padded[i] ^= iv[i];
}
let ct = aes_ecb_encrypt(key, &padded);
return ct.to_vec();
}
let n_blocks = plaintext.len().div_ceil(block_size);
let padded_len = n_blocks * block_size;
let mut padded = vec![0u8; padded_len];
padded[..plaintext.len()].copy_from_slice(plaintext);
let cbc_out = aes_cbc_encrypt(key, iv, &padded);
let mut result = cbc_out;
let second_last_start = (n_blocks - 2) * block_size;
let last_start = (n_blocks - 1) * block_size;
let mut second_last_block = [0u8; 16];
let mut last_block = [0u8; 16];
second_last_block.copy_from_slice(&result[second_last_start..second_last_start + block_size]);
last_block.copy_from_slice(&result[last_start..last_start + block_size]);
result[second_last_start..second_last_start + block_size].copy_from_slice(&last_block);
result[last_start..last_start + block_size].copy_from_slice(&second_last_block);
result.truncate(plaintext.len());
result
}
pub fn decrypt_aes_cts(key: &[u8], iv: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
if ciphertext.is_empty() {
return Ok(Vec::new());
}
let block_size = 16;
if ciphertext.len() <= block_size {
if ciphertext.len() != block_size {
return Err(Error::invalid_data(format!(
"AES-CTS single-block ciphertext must be exactly 16 bytes, got {}",
ciphertext.len()
)));
}
let mut pt = aes_ecb_decrypt(key, ciphertext);
for i in 0..16 {
pt[i] ^= iv[i];
}
return Ok(pt.to_vec());
}
let orig_len = ciphertext.len();
let n_blocks = orig_len.div_ceil(block_size);
let padded_len = n_blocks * block_size;
let mut padded_ct = vec![0u8; padded_len];
padded_ct[..orig_len].copy_from_slice(ciphertext);
let second_last_start = (n_blocks - 2) * block_size;
let last_start = (n_blocks - 1) * block_size;
if orig_len % block_size != 0 {
let tail_len = orig_len - (n_blocks - 1) * block_size;
let mut c_n_minus_1 = [0u8; 16];
c_n_minus_1.copy_from_slice(&padded_ct[second_last_start..second_last_start + block_size]);
let intermediate = aes_ecb_decrypt(key, &c_n_minus_1);
let mut reconstructed_last = [0u8; 16];
reconstructed_last[..tail_len]
.copy_from_slice(&padded_ct[last_start..last_start + tail_len]);
reconstructed_last[tail_len..].copy_from_slice(&intermediate[tail_len..]);
padded_ct[second_last_start..second_last_start + block_size]
.copy_from_slice(&reconstructed_last);
padded_ct[last_start..last_start + block_size].copy_from_slice(&c_n_minus_1);
} else {
let mut second_last_block = [0u8; 16];
let mut last_block = [0u8; 16];
second_last_block
.copy_from_slice(&padded_ct[second_last_start..second_last_start + block_size]);
last_block.copy_from_slice(&padded_ct[last_start..last_start + block_size]);
padded_ct[second_last_start..second_last_start + block_size].copy_from_slice(&last_block);
padded_ct[last_start..last_start + block_size].copy_from_slice(&second_last_block);
}
let plaintext = aes_cbc_decrypt(key, iv, &padded_ct);
Ok(plaintext[..orig_len].to_vec())
}
pub fn encrypt_rc4_hmac(key: &[u8], usage: u32, plaintext: &[u8]) -> Vec<u8> {
use hmac::{Hmac, Mac};
type HmacMd5 = Hmac<md5::Md5>;
let usage_bytes = (usage as i32).to_le_bytes();
let mut mac = HmacMd5::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(&usage_bytes);
let k1 = mac.finalize().into_bytes();
let mut confounder = [0u8; 8];
getrandom::fill(&mut confounder).expect("CSPRNG failed");
let mut payload = Vec::with_capacity(8 + plaintext.len());
payload.extend_from_slice(&confounder);
payload.extend_from_slice(plaintext);
let mut mac = HmacMd5::new_from_slice(&k1).expect("HMAC accepts any key length");
mac.update(&payload);
let checksum = mac.finalize().into_bytes();
let mut mac = HmacMd5::new_from_slice(&k1).expect("HMAC accepts any key length");
mac.update(&checksum);
let k3 = mac.finalize().into_bytes();
let encrypted = rc4_transform(&k3, &payload);
let mut output = Vec::with_capacity(16 + encrypted.len());
output.extend_from_slice(&checksum);
output.extend_from_slice(&encrypted);
output
}
pub fn decrypt_rc4_hmac(key: &[u8], usage: u32, ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
use hmac::{Hmac, Mac};
type HmacMd5 = Hmac<md5::Md5>;
if ciphertext.len() < 24 {
return Err(Error::invalid_data(
"RC4-HMAC ciphertext too short (need at least 16-byte checksum + 8-byte confounder)",
));
}
let checksum = &ciphertext[..16];
let encrypted_data = &ciphertext[16..];
let usage_bytes = (usage as i32).to_le_bytes();
let mut mac = HmacMd5::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(&usage_bytes);
let k1 = mac.finalize().into_bytes();
let mut mac = HmacMd5::new_from_slice(&k1).expect("HMAC accepts any key length");
mac.update(checksum);
let k3 = mac.finalize().into_bytes();
let payload = rc4_transform(&k3, encrypted_data);
let mut mac = HmacMd5::new_from_slice(&k1).expect("HMAC accepts any key length");
mac.update(&payload);
let computed_checksum = mac.finalize().into_bytes();
if computed_checksum.as_slice() != checksum {
return Err(Error::invalid_data("RC4-HMAC checksum verification failed"));
}
if payload.len() < 8 {
return Err(Error::invalid_data("RC4-HMAC decrypted payload too short"));
}
Ok(payload[8..].to_vec())
}
pub fn compute_checksum(key: &[u8], usage: u32, data: &[u8], etype: EncryptionType) -> Vec<u8> {
match etype {
EncryptionType::Aes128CtsHmacSha196 | EncryptionType::Aes256CtsHmacSha196 => {
let kc = derive_key_aes(key, &usage_chk(usage));
hmac_sha1_96(&kc, data)
}
EncryptionType::Rc4Hmac => {
use hmac::{Hmac, Mac};
type HmacMd5 = Hmac<md5::Md5>;
let usage_bytes = (usage as i32).to_le_bytes();
let mut mac = HmacMd5::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(&usage_bytes);
let k1 = mac.finalize().into_bytes();
let mut mac = HmacMd5::new_from_slice(&k1).expect("HMAC accepts any key length");
mac.update(data);
mac.finalize().into_bytes().to_vec()
}
}
}
fn hmac_sha1_96(key: &[u8], data: &[u8]) -> Vec<u8> {
use hmac::{Hmac, Mac};
use sha1::Sha1;
type HmacSha1 = Hmac<Sha1>;
let mut mac = HmacSha1::new_from_slice(key).expect("HMAC accepts any key length");
mac.update(data);
let result = mac.finalize().into_bytes();
result[..12].to_vec()
}
fn dk_derive(base_key: &[u8], constant: &[u8]) -> Vec<u8> {
let block_size = 16; let key_size = base_key.len();
let folded = nfold(constant, block_size);
let mut result = Vec::with_capacity(key_size);
let mut input = [0u8; 16];
input.copy_from_slice(&folded);
while result.len() < key_size {
let encrypted = aes_ecb_encrypt(base_key, &input);
result.extend_from_slice(&encrypted);
input = encrypted;
}
result.truncate(key_size);
result
}
fn nfold(input: &[u8], output_len: usize) -> Vec<u8> {
let in_len = input.len();
let rotated_byte = |rot: usize, byte_idx: usize| -> u8 {
let in_bits = in_len * 8;
let rot_mod = rot % in_bits;
let bit = (byte_idx * 8 + in_bits - rot_mod) % in_bits;
let b = bit / 8;
let s = bit % 8;
if s == 0 {
input[b]
} else {
(((input[b] as u16) << s) | ((input[(b + 1) % in_len] as u16) >> (8 - s))) as u8
}
};
let in_bits = in_len * 8;
let out_bits = output_len * 8;
let lcm_bits = lcm(in_bits, out_bits);
let lcm_bytes = lcm_bits / 8;
let mut result = vec![0u32; output_len];
for i in 0..lcm_bytes {
let copy = i / in_len;
let byte_in_copy = i % in_len;
let rotation = copy * 13;
let val = rotated_byte(rotation, byte_in_copy);
let out_idx = i % output_len;
result[out_idx] += val as u32;
}
loop {
let mut carry = 0u32;
for i in (0..output_len).rev() {
result[i] += carry;
carry = result[i] >> 8;
result[i] &= 0xFF;
}
if carry == 0 {
break;
}
result[output_len - 1] += carry;
}
result.iter().map(|&v| v as u8).collect()
}
fn lcm(a: usize, b: usize) -> usize {
a / gcd(a, b) * b
}
fn gcd(mut a: usize, mut b: usize) -> usize {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
fn aes_ecb_encrypt(key: &[u8], block: &[u8]) -> [u8; 16] {
use aes::cipher::{BlockCipherEncrypt, KeyInit};
let mut output = [0u8; 16];
output.copy_from_slice(block);
match key.len() {
16 => {
let cipher = aes::Aes128::new_from_slice(key).expect("valid key");
cipher.encrypt_block((&mut output).into());
}
32 => {
let cipher = aes::Aes256::new_from_slice(key).expect("valid key");
cipher.encrypt_block((&mut output).into());
}
_ => panic!("AES key must be 16 or 32 bytes, got {}", key.len()),
}
output
}
fn aes_ecb_decrypt(key: &[u8], block: &[u8]) -> [u8; 16] {
use aes::cipher::{BlockCipherDecrypt, KeyInit};
let mut output = [0u8; 16];
output.copy_from_slice(block);
match key.len() {
16 => {
let cipher = aes::Aes128::new_from_slice(key).expect("valid key");
cipher.decrypt_block((&mut output).into());
}
32 => {
let cipher = aes::Aes256::new_from_slice(key).expect("valid key");
cipher.decrypt_block((&mut output).into());
}
_ => panic!("AES key must be 16 or 32 bytes, got {}", key.len()),
}
output
}
fn aes_cbc_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
assert!(
data.len() % 16 == 0,
"AES-CBC input must be a multiple of 16 bytes"
);
let n_blocks = data.len() / 16;
let mut output = vec![0u8; data.len()];
let mut prev = [0u8; 16];
prev.copy_from_slice(iv);
for i in 0..n_blocks {
let start = i * 16;
let mut block = [0u8; 16];
block.copy_from_slice(&data[start..start + 16]);
for j in 0..16 {
block[j] ^= prev[j];
}
let encrypted = aes_ecb_encrypt(key, &block);
output[start..start + 16].copy_from_slice(&encrypted);
prev = encrypted;
}
output
}
fn aes_cbc_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> {
assert!(
data.len() % 16 == 0,
"AES-CBC input must be a multiple of 16 bytes"
);
let n_blocks = data.len() / 16;
let mut output = vec![0u8; data.len()];
let mut prev = [0u8; 16];
prev.copy_from_slice(iv);
for i in 0..n_blocks {
let start = i * 16;
let mut ct_block = [0u8; 16];
ct_block.copy_from_slice(&data[start..start + 16]);
let mut decrypted = aes_ecb_decrypt(key, &ct_block);
for j in 0..16 {
decrypted[j] ^= prev[j];
}
output[start..start + 16].copy_from_slice(&decrypted);
prev = ct_block;
}
output
}
fn rc4_transform(key: &[u8], data: &[u8]) -> Vec<u8> {
let mut s: Vec<u8> = (0..=255).collect();
let mut j: u8 = 0;
for i in 0..256 {
j = j.wrapping_add(s[i]).wrapping_add(key[i % key.len()]);
s.swap(i, j as usize);
}
let mut i: u8 = 0;
j = 0;
data.iter()
.map(|&byte| {
i = i.wrapping_add(1);
j = j.wrapping_add(s[i as usize]);
s.swap(i as usize, j as usize);
byte ^ s[s[i as usize].wrapping_add(s[j as usize]) as usize]
})
.collect()
}
pub(crate) fn kerberos_encrypt(
base_key: &[u8],
usage: u32,
plaintext: &[u8],
etype: EncryptionType,
) -> Vec<u8> {
match etype {
EncryptionType::Aes128CtsHmacSha196 | EncryptionType::Aes256CtsHmacSha196 => {
let ke = derive_key_aes(base_key, &usage_enc(usage));
let ki = derive_key_aes(base_key, &usage_int(usage));
let mut confounder = [0u8; 16];
getrandom::fill(&mut confounder).expect("CSPRNG failed");
let mut full_plain = Vec::with_capacity(16 + plaintext.len());
full_plain.extend_from_slice(&confounder);
full_plain.extend_from_slice(plaintext);
let hmac = hmac_sha1_96(&ki, &full_plain);
let iv = [0u8; 16];
let ciphertext = encrypt_aes_cts(&ke, &iv, &full_plain);
let mut output = ciphertext;
output.extend_from_slice(&hmac);
output
}
EncryptionType::Rc4Hmac => encrypt_rc4_hmac(base_key, usage, plaintext),
}
}
pub(crate) fn kerberos_decrypt(
base_key: &[u8],
usage: u32,
ciphertext: &[u8],
etype: EncryptionType,
) -> Result<Vec<u8>, Error> {
match etype {
EncryptionType::Aes128CtsHmacSha196 | EncryptionType::Aes256CtsHmacSha196 => {
if ciphertext.len() < 12 + 16 {
return Err(Error::invalid_data(
"Kerberos AES ciphertext too short (need at least confounder + HMAC)",
));
}
let hmac_offset = ciphertext.len() - 12;
let enc_data = &ciphertext[..hmac_offset];
let expected_hmac = &ciphertext[hmac_offset..];
let ke = derive_key_aes(base_key, &usage_enc(usage));
let ki = derive_key_aes(base_key, &usage_int(usage));
let iv = [0u8; 16];
let full_plain = decrypt_aes_cts(&ke, &iv, enc_data)?;
let computed_hmac = hmac_sha1_96(&ki, &full_plain);
if computed_hmac != expected_hmac {
return Err(Error::Auth {
message: "Kerberos AES HMAC verification failed".to_string(),
});
}
if full_plain.len() < 16 {
return Err(Error::invalid_data(
"Kerberos AES decrypted data too short for confounder",
));
}
Ok(full_plain[16..].to_vec())
}
EncryptionType::Rc4Hmac => decrypt_rc4_hmac(base_key, usage, ciphertext),
}
}
pub(crate) fn etype_from_i32(val: i32) -> Result<EncryptionType, Error> {
match val {
18 => Ok(EncryptionType::Aes256CtsHmacSha196),
17 => Ok(EncryptionType::Aes128CtsHmacSha196),
23 => Ok(EncryptionType::Rc4Hmac),
_ => Err(Error::Auth {
message: format!("unsupported Kerberos encryption type: {val}"),
}),
}
}
#[cfg(test)]
pub(crate) fn generate_random_key(etype: EncryptionType) -> Vec<u8> {
let key_size = match etype {
EncryptionType::Aes256CtsHmacSha196 => 32,
EncryptionType::Aes128CtsHmacSha196 => 16,
EncryptionType::Rc4Hmac => 16,
};
let mut key = vec![0u8; key_size];
getrandom::fill(&mut key).expect("CSPRNG failed");
key
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encryption_type_values() {
assert_eq!(EncryptionType::Aes256CtsHmacSha196 as u32, 18);
assert_eq!(EncryptionType::Aes128CtsHmacSha196 as u32, 17);
assert_eq!(EncryptionType::Rc4Hmac as u32, 23);
}
#[test]
fn nfold_rfc3961_test_vectors() {
let result = nfold(b"012345", 8);
assert_eq!(result, hex("be072631276b1955"));
let result = nfold(b"password", 7);
assert_eq!(result, hex("78a07b6caf85fa"));
let result = nfold(b"Rough Consensus, and Running Code", 8);
assert_eq!(result, hex("bb6ed30870b7f0e0"));
let result = nfold(b"password", 21);
assert_eq!(result, hex("59e4a8ca7c0385c3c37b3f6d2000247cb6e6bd5b3e"));
let result = nfold(b"kerberos", 16);
assert_eq!(result, hex("6b65726265726f737b9b5b2b93132b93"));
let result = nfold(b"kerberos", 21);
assert_eq!(result, hex("8372c236344e5f1550cd0747e15d62ca7a5a3bcea4"));
let result = nfold(b"kerberos", 32);
assert_eq!(
result,
hex("6b65726265726f737b9b5b2b93132b935c9bdcdad95c9899c4cae4dee6d6cae4")
);
}
#[test]
fn string_to_key_rc4_produces_nt_hash() {
let key = string_to_key_rc4("Password");
assert_eq!(key, hex("a4f49c406510bdcab6824ee7c30fd852"));
}
#[test]
fn string_to_key_rc4_empty_password() {
let key = string_to_key_rc4("");
assert_eq!(key.len(), 16);
assert_eq!(key, hex("31d6cfe0d16ae931b73c59d7e0c089c0"));
}
#[test]
fn string_to_key_aes256_rfc3962_test_vector() {
let key = string_to_key_aes("password", "ATHENA.MIT.EDUraeburn", 32);
assert_eq!(
key,
hex("01b897121d933ab44b47eb5494db15e50eb74530dbdae9b634d65020ff5d88c1")
);
}
#[test]
fn string_to_key_aes128_rfc3962_test_vector() {
let key = string_to_key_aes("password", "ATHENA.MIT.EDUraeburn", 16);
assert_eq!(key, hex("fca822951813fb252154c883f5ee1cf4"));
}
#[test]
fn string_to_key_aes256_produces_32_bytes() {
let key = string_to_key_aes("test", "EXAMPLE.COMtest", 32);
assert_eq!(key.len(), 32);
}
#[test]
fn string_to_key_aes128_produces_16_bytes() {
let key = string_to_key_aes("test", "EXAMPLE.COMtest", 16);
assert_eq!(key.len(), 16);
}
#[test]
fn derive_key_aes_deterministic() {
let base_key = [0xAA; 16];
let usage = usage_enc(7);
let k1 = derive_key_aes(&base_key, &usage);
let k2 = derive_key_aes(&base_key, &usage);
assert_eq!(k1, k2, "same inputs must produce same output");
}
#[test]
fn derive_key_aes_different_usages_produce_different_keys() {
let base_key = [0xBB; 16];
let k_enc = derive_key_aes(&base_key, &usage_enc(7));
let k_int = derive_key_aes(&base_key, &usage_int(7));
assert_ne!(
k_enc, k_int,
"different usage types must produce different keys"
);
}
#[test]
fn derive_key_aes_different_usage_numbers_produce_different_keys() {
let base_key = [0xCC; 32];
let k1 = derive_key_aes(&base_key, &usage_enc(1));
let k7 = derive_key_aes(&base_key, &usage_enc(7));
assert_ne!(
k1, k7,
"different usage numbers must produce different keys"
);
}
#[test]
fn derive_key_aes128_preserves_key_length() {
let base_key = [0xDD; 16];
let derived = derive_key_aes(&base_key, &usage_enc(1));
assert_eq!(derived.len(), 16);
}
#[test]
fn derive_key_aes256_preserves_key_length() {
let base_key = [0xEE; 32];
let derived = derive_key_aes(&base_key, &usage_enc(1));
assert_eq!(derived.len(), 32);
}
#[test]
fn aes_cts_empty_input() {
let key = [0x11; 16];
let iv = [0u8; 16];
let ct = encrypt_aes_cts(&key, &iv, &[]);
assert!(ct.is_empty());
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert!(pt.is_empty());
}
#[test]
fn aes_cts_single_block_roundtrip() {
let key = [0x22; 16];
let iv = [0u8; 16];
let plaintext = b"sixteen bytes!!!";
assert_eq!(plaintext.len(), 16);
let ct = encrypt_aes_cts(&key, &iv, plaintext);
assert_eq!(ct.len(), 16);
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes_cts_two_blocks_roundtrip() {
let key = [0x33; 16];
let iv = [0u8; 16];
let plaintext = [0x42u8; 32];
let ct = encrypt_aes_cts(&key, &iv, &plaintext);
assert_eq!(ct.len(), 32);
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes_cts_non_block_aligned_roundtrip() {
let key = [0x44; 16];
let iv = [0u8; 16];
let plaintext = [0x55u8; 30];
let ct = encrypt_aes_cts(&key, &iv, &plaintext);
assert_eq!(
ct.len(),
30,
"CTS ciphertext length equals plaintext length"
);
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes_cts_three_blocks_roundtrip() {
let key = [0x55; 32]; let iv = [0u8; 16];
let plaintext = [0x66u8; 48];
let ct = encrypt_aes_cts(&key, &iv, &plaintext);
assert_eq!(ct.len(), 48);
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes_cts_non_aligned_aes256_roundtrip() {
let key = [0x77; 32]; let iv = [0u8; 16];
let plaintext: Vec<u8> = (0..50).collect();
let ct = encrypt_aes_cts(&key, &iv, &plaintext);
assert_eq!(ct.len(), 50);
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn aes_cts_sub_block_pads_to_full_block() {
let key = [0x88; 16];
let iv = [0u8; 16];
let plaintext = b"short";
let ct = encrypt_aes_cts(&key, &iv, plaintext);
assert_eq!(ct.len(), 16, "single-block ciphertext is always 16 bytes");
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert_eq!(pt.len(), 16);
assert_eq!(&pt[..5], plaintext.as_slice());
assert_eq!(&pt[5..], &[0u8; 11]); }
#[test]
fn aes_cts_ciphertext_differs_from_plaintext() {
let key = [0x99; 16];
let iv = [0u8; 16];
let plaintext = [0xAA; 32];
let ct = encrypt_aes_cts(&key, &iv, &plaintext);
assert_ne!(ct, plaintext, "ciphertext must differ from plaintext");
}
#[test]
fn rc4_hmac_roundtrip() {
let key = hex("a4f49c406510bdcab6824ee7c30fd852");
let plaintext = b"Hello, Kerberos!";
let usage = 7u32;
let ct = encrypt_rc4_hmac(&key, usage, plaintext);
assert_eq!(ct.len(), 16 + 8 + plaintext.len());
let pt = decrypt_rc4_hmac(&key, usage, &ct).unwrap();
assert_eq!(pt, plaintext);
}
#[test]
fn rc4_hmac_empty_plaintext_roundtrip() {
let key = [0xBB; 16];
let ct = encrypt_rc4_hmac(&key, 1, &[]);
assert_eq!(ct.len(), 24);
let pt = decrypt_rc4_hmac(&key, 1, &ct).unwrap();
assert!(pt.is_empty());
}
#[test]
fn rc4_hmac_wrong_key_fails() {
let key = [0xCC; 16];
let ct = encrypt_rc4_hmac(&key, 1, b"secret data");
let wrong_key = [0xDD; 16];
let result = decrypt_rc4_hmac(&wrong_key, 1, &ct);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("checksum verification failed"));
}
#[test]
fn rc4_hmac_wrong_usage_fails() {
let key = [0xEE; 16];
let ct = encrypt_rc4_hmac(&key, 1, b"usage test");
let result = decrypt_rc4_hmac(&key, 2, &ct);
assert!(result.is_err());
}
#[test]
fn rc4_hmac_ciphertext_too_short() {
let key = [0xFF; 16];
let result = decrypt_rc4_hmac(&key, 1, &[0u8; 23]); assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("too short"));
}
#[test]
fn rc4_hmac_tampered_ciphertext_fails() {
let key = [0x11; 16];
let mut ct = encrypt_rc4_hmac(&key, 1, b"tamper test");
let last = ct.len() - 1;
ct[last] ^= 0xFF;
let result = decrypt_rc4_hmac(&key, 1, &ct);
assert!(result.is_err());
}
#[test]
fn checksum_aes_produces_12_bytes() {
let key = [0x11; 16];
let data = b"checksum test data";
let checksum = compute_checksum(&key, 7, data, EncryptionType::Aes128CtsHmacSha196);
assert_eq!(checksum.len(), 12, "HMAC-SHA1-96 produces 12 bytes");
}
#[test]
fn checksum_aes256_produces_12_bytes() {
let key = [0x22; 32];
let data = b"checksum test data";
let checksum = compute_checksum(&key, 7, data, EncryptionType::Aes256CtsHmacSha196);
assert_eq!(checksum.len(), 12);
}
#[test]
fn checksum_rc4_produces_16_bytes() {
let key = [0x33; 16];
let data = b"checksum test data";
let checksum = compute_checksum(&key, 7, data, EncryptionType::Rc4Hmac);
assert_eq!(checksum.len(), 16, "HMAC-MD5 produces 16 bytes");
}
#[test]
fn checksum_aes_deterministic() {
let key = [0x44; 16];
let data = b"determinism test";
let c1 = compute_checksum(&key, 7, data, EncryptionType::Aes128CtsHmacSha196);
let c2 = compute_checksum(&key, 7, data, EncryptionType::Aes128CtsHmacSha196);
assert_eq!(c1, c2);
}
#[test]
fn checksum_different_usage_produces_different_result() {
let key = [0x55; 16];
let data = b"usage test";
let c1 = compute_checksum(&key, 1, data, EncryptionType::Aes128CtsHmacSha196);
let c2 = compute_checksum(&key, 2, data, EncryptionType::Aes128CtsHmacSha196);
assert_ne!(c1, c2);
}
#[test]
fn checksum_rc4_deterministic() {
let key = [0x66; 16];
let data = b"rc4 checksum test";
let c1 = compute_checksum(&key, 7, data, EncryptionType::Rc4Hmac);
let c2 = compute_checksum(&key, 7, data, EncryptionType::Rc4Hmac);
assert_eq!(c1, c2);
}
#[test]
fn usage_enc_format() {
let u = usage_enc(7);
assert_eq!(u, [0, 0, 0, 7, 0xAA]);
}
#[test]
fn usage_int_format() {
let u = usage_int(7);
assert_eq!(u, [0, 0, 0, 7, 0x55]);
}
fn hex(s: &str) -> Vec<u8> {
let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn string_to_key_aes256_matches_mit_kdc_keytab() {
let key = string_to_key_aes("testpass", "TEST.LOCALtestuser", 32);
let expected = hex("7964c7e6f475912def26f886f2683da03f58257a987bca47e461daddb18cb336");
assert_eq!(key, expected, "key must match MIT KDC keytab");
}
#[test]
fn aes_cts_known_vectors() {
let key = hex("636869636b656e207465726979616b69");
let iv = [0u8; 16];
let full_plain = b"I would like the General Gau's Chicken, please, and wonton soup.";
let ct_17 = encrypt_aes_cts(&key, &iv, &full_plain[..17]);
assert_eq!(
ct_17,
hex("c6353568f2bf8cb4d8a580362da7ff7f97"),
"17-byte CTS failed"
);
for len in [17, 31, 32, 47, 48, 64] {
let ct = encrypt_aes_cts(&key, &iv, &full_plain[..len]);
assert_eq!(ct.len(), len, "CTS ciphertext length for {len} bytes");
let pt = decrypt_aes_cts(&key, &iv, &ct).unwrap();
assert_eq!(&pt[..], &full_plain[..len], "CTS roundtrip for {len} bytes");
}
}
#[test]
fn kerberos_encrypt_decrypt_aes256() {
let key = string_to_key_aes("password", "EXAMPLE.COMuser", 32);
let plaintext = b"Hello, Kerberos!";
let ciphertext = kerberos_encrypt(&key, 7, plaintext, EncryptionType::Aes256CtsHmacSha196);
let decrypted =
kerberos_decrypt(&key, 7, &ciphertext, EncryptionType::Aes256CtsHmacSha196).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn kerberos_encrypt_decrypt_aes128() {
let key = string_to_key_aes("password", "EXAMPLE.COMuser", 16);
let plaintext = b"Hello, Kerberos AES-128!";
let ciphertext = kerberos_encrypt(&key, 3, plaintext, EncryptionType::Aes128CtsHmacSha196);
let decrypted =
kerberos_decrypt(&key, 3, &ciphertext, EncryptionType::Aes128CtsHmacSha196).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn kerberos_encrypt_decrypt_rc4() {
let key = string_to_key_rc4("password");
let plaintext = b"Hello, RC4!";
let ciphertext = kerberos_encrypt(&key, 7, plaintext, EncryptionType::Rc4Hmac);
let decrypted = kerberos_decrypt(&key, 7, &ciphertext, EncryptionType::Rc4Hmac).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn kerberos_decrypt_wrong_key_fails() {
let key = string_to_key_aes("password", "EXAMPLE.COMuser", 32);
let wrong_key = string_to_key_aes("wrong", "EXAMPLE.COMuser", 32);
let plaintext = b"secret data";
let ciphertext = kerberos_encrypt(&key, 1, plaintext, EncryptionType::Aes256CtsHmacSha196);
let result = kerberos_decrypt(
&wrong_key,
1,
&ciphertext,
EncryptionType::Aes256CtsHmacSha196,
);
assert!(result.is_err(), "decryption with wrong key should fail");
}
#[test]
fn kerberos_decrypt_wrong_usage_fails() {
let key = string_to_key_aes("password", "EXAMPLE.COMuser", 32);
let plaintext = b"secret data";
let ciphertext = kerberos_encrypt(&key, 1, plaintext, EncryptionType::Aes256CtsHmacSha196);
let result = kerberos_decrypt(&key, 7, &ciphertext, EncryptionType::Aes256CtsHmacSha196);
assert!(result.is_err(), "decryption with wrong usage should fail");
}
#[test]
fn etype_from_i32_valid() {
assert_eq!(
etype_from_i32(18).unwrap(),
EncryptionType::Aes256CtsHmacSha196
);
assert_eq!(
etype_from_i32(17).unwrap(),
EncryptionType::Aes128CtsHmacSha196
);
assert_eq!(etype_from_i32(23).unwrap(), EncryptionType::Rc4Hmac);
}
#[test]
fn etype_from_i32_unsupported() {
assert!(etype_from_i32(99).is_err());
assert!(etype_from_i32(0).is_err());
}
#[test]
fn generate_random_key_sizes() {
assert_eq!(
generate_random_key(EncryptionType::Aes256CtsHmacSha196).len(),
32
);
assert_eq!(
generate_random_key(EncryptionType::Aes128CtsHmacSha196).len(),
16
);
assert_eq!(generate_random_key(EncryptionType::Rc4Hmac).len(), 16);
}
}