#![forbid(unsafe_code)]
use crate::algorithm::AesKeySize;
use crate::backend::{require_supported, Operation};
use crate::error::{Error, Result};
use crate::software::cipher::{pkcs7_pad, xmlenc_unpad};
use zeroize::Zeroizing;
pub fn encrypt(size: AesKeySize, key: &[u8], plaintext: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Encrypt(
crate::algorithm::CipherAlgorithm::AesCbc(size),
))?;
use cbc::cipher::{BlockModeEncrypt, KeyIvInit};
let expected = size.key_len();
if key.len() != expected {
return Err(Error::Crypto(format!(
"expected {expected} byte key, got {}",
key.len()
)));
}
let mut iv = [0u8; 16];
crate::backend::fill_random(&mut iv)?;
let mut buf = Zeroizing::new(pkcs7_pad(plaintext, 16));
let buf_len = buf.len();
macro_rules! do_encrypt {
($aes:ty) => {{
let enc = cbc::Encryptor::<$aes>::new_from_slices(key, &iv)
.map_err(|e| Error::Crypto(format!("AES-CBC init: {e}")))?;
enc.encrypt_padded::<cbc::cipher::block_padding::NoPadding>(&mut buf, buf_len)
.map_err(|e| Error::Crypto(format!("AES-CBC encrypt: {e}")))?;
}};
}
match size {
AesKeySize::Aes128 => do_encrypt!(aes::Aes128),
AesKeySize::Aes192 => do_encrypt!(aes::Aes192),
AesKeySize::Aes256 => do_encrypt!(aes::Aes256),
}
let mut result = Vec::with_capacity(16 + buf.len());
result.extend_from_slice(&iv);
result.extend_from_slice(&buf);
Ok(result)
}
pub fn decrypt(size: AesKeySize, key: &[u8], data: &[u8]) -> Result<Vec<u8>> {
require_supported(Operation::Decrypt(
crate::algorithm::CipherAlgorithm::AesCbc(size),
))?;
use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
let expected = size.key_len();
if key.len() != expected {
return Err(Error::Crypto(format!(
"expected {expected} byte key, got {}",
key.len()
)));
}
let opaque = || Error::Crypto("AES-CBC decrypt failed".into());
if data.len() < 32 || !data.len().is_multiple_of(16) {
return Err(opaque());
}
let iv = &data[..16];
let ciphertext = &data[16..];
let mut buf = Zeroizing::new(ciphertext.to_vec());
macro_rules! do_decrypt {
($aes:ty) => {{
let dec = cbc::Decryptor::<$aes>::new_from_slices(key, iv).map_err(|_| opaque())?;
dec.decrypt_padded::<cbc::cipher::block_padding::NoPadding>(&mut buf)
.map_err(|_| opaque())?;
}};
}
match size {
AesKeySize::Aes128 => do_decrypt!(aes::Aes128),
AesKeySize::Aes192 => do_decrypt!(aes::Aes192),
AesKeySize::Aes256 => do_decrypt!(aes::Aes256),
}
xmlenc_unpad(&buf, 16).map_err(|_| opaque())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_all_sizes() {
let cases: &[(AesKeySize, usize)] = &[
(AesKeySize::Aes128, 16),
(AesKeySize::Aes192, 24),
(AesKeySize::Aes256, 32),
];
let plaintexts: &[&[u8]] = &[
b"A",
b"Hello",
b"Hello, World!",
b"Exactly16bytes!!",
b"This is a much longer test message that spans multiple AES blocks.",
];
for &(size, key_len) in cases {
let key: Vec<u8> = (0..key_len).map(|i| i as u8).collect();
for &pt in plaintexts {
let ct = encrypt(size, &key, pt).unwrap();
let decrypted = decrypt(size, &key, &ct).unwrap();
assert_eq!(decrypted, pt, "roundtrip {size:?} pt_len={}", pt.len());
}
}
}
#[test]
fn decrypt_errors_are_opaque() {
let key = [0x42u8; 16];
let expected = "AES-CBC decrypt failed";
let err = decrypt(AesKeySize::Aes128, &key, &[0u8; 8]).unwrap_err();
assert!(err.to_string().contains(expected), "got {err}");
let err = decrypt(AesKeySize::Aes128, &key, &[]).unwrap_err();
assert!(err.to_string().contains(expected), "got {err}");
let err = decrypt(AesKeySize::Aes128, &key, &[0u8; 16]).unwrap_err();
assert!(err.to_string().contains(expected), "got {err}");
let err = decrypt(AesKeySize::Aes128, &key, &[0u8; 33]).unwrap_err();
assert!(err.to_string().contains(expected), "got {err}");
let ct = encrypt(AesKeySize::Aes128, &key, b"sixteen-byte-msg").unwrap();
assert_eq!(ct.len(), 48, "IV(16) + 2 blocks(32) expected");
let mut bad = ct.clone();
bad[31] ^= 0x10;
let err = decrypt(AesKeySize::Aes128, &key, &bad).unwrap_err();
assert!(err.to_string().contains(expected), "got {err}");
}
#[test]
fn key_size_error_is_distinct() {
let err = decrypt(AesKeySize::Aes128, &[0u8; 15], &[0u8; 32]).unwrap_err();
let msg = err.to_string();
assert!(msg.contains("16 byte key"), "got {msg}");
}
}