use crate::BlockCipher;
pub fn ecb_encrypt<C: BlockCipher>(cipher: &C, data: &mut [u8]) {
let bs = C::BLOCK_LEN;
assert!(
data.len() % bs == 0,
"ECB: data length must be a multiple of {} (got {})",
bs,
data.len()
);
for chunk in data.chunks_mut(bs) {
cipher.encrypt_block(chunk);
}
}
pub fn ecb_decrypt<C: BlockCipher>(cipher: &C, data: &mut [u8]) {
let bs = C::BLOCK_LEN;
assert!(
data.len() % bs == 0,
"ECB: data length must be a multiple of {} (got {})",
bs,
data.len()
);
for chunk in data.chunks_mut(bs) {
cipher.decrypt_block(chunk);
}
}
pub fn cbc_encrypt<C: BlockCipher>(cipher: &C, iv: &[u8], data: &mut [u8]) {
let bs = C::BLOCK_LEN;
assert_eq!(iv.len(), bs, "CBC: IV must be {} bytes", bs);
assert!(
data.len() % bs == 0,
"CBC: data length must be a multiple of {} (got {})",
bs,
data.len()
);
let mut prev = vec![0u8; bs];
prev.copy_from_slice(iv);
for chunk in data.chunks_mut(bs) {
for i in 0..bs {
chunk[i] ^= prev[i];
}
cipher.encrypt_block(chunk);
prev.copy_from_slice(chunk);
}
}
pub fn cbc_decrypt<C: BlockCipher>(cipher: &C, iv: &[u8], data: &mut [u8]) {
let bs = C::BLOCK_LEN;
assert_eq!(iv.len(), bs, "CBC: IV must be {} bytes", bs);
assert!(
data.len() % bs == 0,
"CBC: data length must be a multiple of {} (got {})",
bs,
data.len()
);
let mut prev = vec![0u8; bs];
prev.copy_from_slice(iv);
for chunk in data.chunks_mut(bs) {
let ct_copy: Vec<u8> = chunk.to_vec();
cipher.decrypt_block(chunk);
for i in 0..bs {
chunk[i] ^= prev[i];
}
prev.copy_from_slice(&ct_copy);
}
}
pub fn ctr_encrypt<C: BlockCipher>(cipher: &C, nonce: &[u8], data: &mut [u8]) {
let bs = C::BLOCK_LEN;
assert!(
nonce.len() < bs,
"CTR: nonce must be shorter than block size ({} bytes)",
bs
);
let counter_bytes = bs - nonce.len();
let mut counter_block = vec![0u8; bs];
counter_block[..nonce.len()].copy_from_slice(nonce);
let mut counter: u64 = 1;
for chunk in data.chunks_mut(bs) {
let counter_be = counter.to_be_bytes();
let start = 8usize.saturating_sub(counter_bytes);
for i in 0..counter_bytes {
counter_block[nonce.len() + i] = if i + start < 8 { counter_be[i + start] } else { 0 };
}
let mut keystream = vec![0u8; bs];
keystream.copy_from_slice(&counter_block);
cipher.encrypt_block(&mut keystream);
for i in 0..chunk.len() {
chunk[i] ^= keystream[i];
}
counter += 1;
}
}
pub struct Gcm;
impl Gcm {
pub fn encrypt<C: BlockCipher>(cipher: &C, nonce: &[u8; 12], aad: &[u8], plaintext: &[u8]) -> (Vec<u8>, [u8; 16]) {
assert_eq!(C::BLOCK_LEN, 16, "GCM requires a 128-bit block cipher");
let mut h = [0u8; 16];
cipher.encrypt_block(&mut h);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let mut ciphertext = plaintext.to_vec();
gctr(cipher, &inc32(&j0), &mut ciphertext);
let tag = ghash_compute(&h, aad, &ciphertext);
let mut e_j0 = j0;
cipher.encrypt_block(&mut e_j0);
let mut final_tag = [0u8; 16];
for i in 0..16 {
final_tag[i] = tag[i] ^ e_j0[i];
}
(ciphertext, final_tag)
}
pub fn decrypt<C: BlockCipher>(
cipher: &C,
nonce: &[u8; 12],
aad: &[u8],
ciphertext: &[u8],
tag: &[u8; 16],
) -> Option<Vec<u8>> {
assert_eq!(C::BLOCK_LEN, 16, "GCM requires a 128-bit block cipher");
let mut h = [0u8; 16];
cipher.encrypt_block(&mut h);
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(nonce);
j0[15] = 1;
let ghash_tag = ghash_compute(&h, aad, ciphertext);
let mut e_j0 = j0;
cipher.encrypt_block(&mut e_j0);
let mut expected_tag = [0u8; 16];
for i in 0..16 {
expected_tag[i] = ghash_tag[i] ^ e_j0[i];
}
let mut diff = 0u8;
for i in 0..16 {
diff |= tag[i] ^ expected_tag[i];
}
if diff != 0 {
return None;
}
let mut plaintext = ciphertext.to_vec();
gctr(cipher, &inc32(&j0), &mut plaintext);
Some(plaintext)
}
}
fn inc32(block: &[u8; 16]) -> [u8; 16] {
let mut out = *block;
let ctr = u32::from_be_bytes([out[12], out[13], out[14], out[15]]);
let new_ctr = ctr.wrapping_add(1);
out[12..16].copy_from_slice(&new_ctr.to_be_bytes());
out
}
fn gctr<C: BlockCipher>(cipher: &C, icb: &[u8; 16], data: &mut [u8]) {
if data.is_empty() {
return;
}
let mut cb = *icb;
for chunk in data.chunks_mut(16) {
let mut keystream = cb;
cipher.encrypt_block(&mut keystream);
for i in 0..chunk.len() {
chunk[i] ^= keystream[i];
}
cb = inc32(&cb);
}
}
pub(crate) fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] {
let mut z = [0u8; 16];
let mut v = *x;
for i in 0..128 {
let byte_idx = i / 8;
let bit_idx = 7 - (i % 8);
if (y[byte_idx] >> bit_idx) & 1 == 1 {
for j in 0..16 {
z[j] ^= v[j];
}
}
let lsb = v[15] & 1;
for j in (1..16).rev() {
v[j] = (v[j] >> 1) | (v[j - 1] << 7);
}
v[0] >>= 1;
if lsb == 1 {
v[0] ^= 0xE1;
}
}
z
}
fn ghash_compute(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] {
let mut y = [0u8; 16];
ghash_update(&mut y, h, aad);
ghash_update(&mut y, h, ciphertext);
let mut len_block = [0u8; 16];
let a_bits = (aad.len() as u64) * 8;
let c_bits = (ciphertext.len() as u64) * 8;
len_block[0..8].copy_from_slice(&a_bits.to_be_bytes());
len_block[8..16].copy_from_slice(&c_bits.to_be_bytes());
for i in 0..16 {
y[i] ^= len_block[i];
}
y = gf128_mul(&y, h);
y
}
pub(crate) fn ghash_update(y: &mut [u8; 16], h: &[u8; 16], data: &[u8]) {
for chunk in data.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
y[i] ^= block[i];
}
*y = gf128_mul(y, h);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cipher::aes::Aes128;
fn hex_to_bytes(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn ecb_aes128_round_trip() {
let key = hex_to_bytes("2b7e151628aed2a6abf7158809cf4f3c");
let cipher = Aes128::new(&key);
let plaintext = hex_to_bytes("3243f6a8885a308d313198a2e03707343243f6a8885a308d313198a2e0370734");
let mut data = plaintext.clone();
ecb_encrypt(&cipher, &mut data);
assert_ne!(data, plaintext);
ecb_decrypt(&cipher, &mut data);
assert_eq!(data, plaintext);
}
#[test]
fn cbc_aes128_round_trip() {
let key = hex_to_bytes("2b7e151628aed2a6abf7158809cf4f3c");
let iv = hex_to_bytes("000102030405060708090a0b0c0d0e0f");
let cipher = Aes128::new(&key);
let plaintext = hex_to_bytes("6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51");
let mut data = plaintext.clone();
cbc_encrypt(&cipher, &iv, &mut data);
assert_ne!(data, plaintext);
cbc_decrypt(&cipher, &iv, &mut data);
assert_eq!(data, plaintext);
}
#[test]
fn ctr_aes128_round_trip() {
let key = hex_to_bytes("2b7e151628aed2a6abf7158809cf4f3c");
let nonce = hex_to_bytes("f0f1f2f3f4f5f6f7f8f9fafb");
let cipher = Aes128::new(&key);
let plaintext = hex_to_bytes("6bc1bee22e409f96e93d7e117393172a");
let mut data = plaintext.clone();
ctr_encrypt(&cipher, &nonce, &mut data);
assert_ne!(data, plaintext);
ctr_encrypt(&cipher, &nonce, &mut data);
assert_eq!(data, plaintext);
}
#[test]
fn gcm_aes128_test_case_2() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let cipher = Aes128::new(&key);
let plaintext = [0u8; 16];
let (ct, tag) = Gcm::encrypt(&cipher, &nonce, &[], &plaintext);
let pt = Gcm::decrypt(&cipher, &nonce, &[], &ct, &tag);
assert!(pt.is_some());
assert_eq!(pt.unwrap(), plaintext);
}
#[test]
fn gcm_bad_tag() {
let key = [0u8; 16];
let nonce = [0u8; 12];
let cipher = Aes128::new(&key);
let (ct, mut tag) = Gcm::encrypt(&cipher, &nonce, &[], b"hello world12345");
tag[0] ^= 0xFF; assert!(Gcm::decrypt(&cipher, &nonce, &[], &ct, &tag).is_none());
}
#[test]
fn gcm_aad_affects_tag() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let nonce = [0u8; 12];
let cipher = Aes128::new(&key);
let (ct1, tag1) = Gcm::encrypt(&cipher, &nonce, b"aad1", b"plaintext1234567");
let (ct2, tag2) = Gcm::encrypt(&cipher, &nonce, b"aad2", b"plaintext1234567");
assert_eq!(ct1, ct2); assert_ne!(tag1, tag2); }
#[test]
fn gcm_nist_test_case_3() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let nonce_bytes = hex_to_bytes("cafebabefacedbaddecaf888");
let nonce: [u8; 12] = nonce_bytes.try_into().unwrap();
let pt = hex_to_bytes(
"d9313225f88406e5a55909c5aff5269a\
86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525\
b16aedf5aa0de657ba637b391aafd255",
);
let expected_ct = hex_to_bytes(
"42831ec2217774244b7221b784d0d49c\
e3aa212f2c02a4e035c17e2329aca12e\
21d514b25466931c7d8f6a5aac84aa05\
1ba30b396a0aac973d58e091473f5985",
);
let expected_tag = hex_to_bytes("4d5c2af327cd64a62cf35abd2ba6fab4");
let cipher = Aes128::new(&key);
let (ct, tag) = Gcm::encrypt(&cipher, &nonce, &[], &pt);
assert_eq!(ct, expected_ct, "GCM ciphertext mismatch");
assert_eq!(tag.to_vec(), expected_tag, "GCM tag mismatch");
let decrypted = Gcm::decrypt(&cipher, &nonce, &[], &ct, &tag).unwrap();
assert_eq!(decrypted, pt);
}
#[test]
fn gcm_nist_test_case_4() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let nonce_bytes = hex_to_bytes("cafebabefacedbaddecaf888");
let nonce: [u8; 12] = nonce_bytes.try_into().unwrap();
let pt = hex_to_bytes(
"d9313225f88406e5a55909c5aff5269a\
86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525\
b16aedf5aa0de657ba637b39",
);
let aad = hex_to_bytes(
"feedfacedeadbeeffeedfacedeadbeef\
abaddad2",
);
let expected_ct = hex_to_bytes(
"42831ec2217774244b7221b784d0d49c\
e3aa212f2c02a4e035c17e2329aca12e\
21d514b25466931c7d8f6a5aac84aa05\
1ba30b396a0aac973d58e091",
);
let expected_tag = hex_to_bytes("5bc94fbc3221a5db94fae95ae7121a47");
let cipher = Aes128::new(&key);
let (ct, tag) = Gcm::encrypt(&cipher, &nonce, &aad, &pt);
assert_eq!(ct, expected_ct, "GCM TC4 ciphertext mismatch");
assert_eq!(tag.to_vec(), expected_tag, "GCM TC4 tag mismatch");
let decrypted = Gcm::decrypt(&cipher, &nonce, &aad, &ct, &tag).unwrap();
assert_eq!(decrypted, pt);
}
}