use cbc::cipher::{
Block, BlockCipher, BlockDecryptMut, BlockEncryptMut, BlockSizeUser, KeyIvInit, Unsigned,
block_padding::Pkcs7,
};
use cbc::{Decryptor, Encryptor};
use rand_core::{OsRng, RngCore};
use crate::contract::{Identified, SymmetricCipher};
use crate::error::{Error, Result};
use crate::internal::etm::{self, EtmKeys};
pub struct CbcHmacEncryptor<
C: BlockCipher + BlockEncryptMut + BlockDecryptMut + cbc::cipher::KeyInit,
const KEY_LEN: usize,
> {
keys: EtmKeys<KEY_LEN>,
identifier: &'static str,
label: &'static str,
_cipher: std::marker::PhantomData<fn() -> C>,
}
impl<C, const KEY_LEN: usize> CbcHmacEncryptor<C, KEY_LEN>
where
C: BlockCipher + BlockEncryptMut + BlockDecryptMut + cbc::cipher::KeyInit,
{
pub fn with_names(key: [u8; KEY_LEN], identifier: &'static str, label: &'static str) -> Self {
Self {
keys: EtmKeys::new(key),
identifier,
label,
_cipher: std::marker::PhantomData,
}
}
pub fn with_names_from_slice(
key: &[u8],
identifier: &'static str,
label: &'static str,
) -> Result<Self> {
Ok(Self {
keys: EtmKeys::from_slice(key, label)?,
identifier,
label,
_cipher: std::marker::PhantomData,
})
}
pub const fn iv_len() -> usize {
<C as BlockSizeUser>::BlockSize::USIZE
}
}
impl<C, const KEY_LEN: usize> SymmetricCipher for CbcHmacEncryptor<C, KEY_LEN>
where
C: BlockCipher + BlockEncryptMut + BlockDecryptMut + cbc::cipher::KeyInit,
{
fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
let mut iv = Block::<C>::default();
OsRng.fill_bytes(&mut iv);
let ciphertext = Encryptor::<C>::new_from_slices(self.keys.cipher(), &iv)
.expect("key and iv lengths are fixed")
.encrypt_padded_vec_mut::<Pkcs7>(plaintext);
Ok(etm::pack(&iv, &ciphertext, self.keys.mac()))
}
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
let (iv, body) =
etm::unpack_and_verify(ciphertext, Self::iv_len(), self.keys.mac(), self.label)?;
Decryptor::<C>::new_from_slices(self.keys.cipher(), iv)
.expect("key and iv lengths were verified")
.decrypt_padded_vec_mut::<Pkcs7>(body)
.map_err(|_| Error::DecryptionFailed { label: self.label })
}
}
impl<C, const KEY_LEN: usize> Identified for CbcHmacEncryptor<C, KEY_LEN>
where
C: BlockCipher + BlockEncryptMut + BlockDecryptMut + cbc::cipher::KeyInit,
{
fn identifier(&self) -> &str {
self.identifier
}
}
impl<C, const KEY_LEN: usize> std::fmt::Debug for CbcHmacEncryptor<C, KEY_LEN>
where
C: BlockCipher + BlockEncryptMut + BlockDecryptMut + cbc::cipher::KeyInit,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CbcHmacEncryptor")
.field("identifier", &self.identifier)
.finish_non_exhaustive()
}
}
macro_rules! cbc_hmac_aliases {
($( $(#[$doc:meta])* $name:ident = $cipher:ty, $key_len:literal, $label:literal, $identifier:literal; )*) => {
$(
$(#[$doc])*
pub type $name = CbcHmacEncryptor<$cipher, $key_len>;
impl CbcHmacEncryptor<$cipher, $key_len> {
/// 注册表标识。
pub const IDENTIFIER: &'static str = $identifier;
/// 以定长密钥构造(长度由类型保证,不会失败)。
pub fn new(key: [u8; $key_len]) -> Self {
Self::with_names(key, $identifier, $label)
}
pub fn from_slice(key: &[u8]) -> Result<Self> {
Self::with_names_from_slice(key, $identifier, $label)
}
}
)*
};
}
cbc_hmac_aliases! {
Aes256CbcEncryptor = aes::Aes256, 32, "AES-256-CBC", "aes-256-cbc-hmac";
Camellia256CbcEncryptor = camellia::Camellia256, 32, "Camellia-256-CBC", "camellia-256-cbc-hmac";
Aria256CbcEncryptor = aria::Aria256, 32, "ARIA-256-CBC", "aria-256-cbc-hmac";
Kuznyechik256CbcEncryptor = kuznyechik::Kuznyechik, 32, "Kuznyechik-256-CBC", "kuznyechik-256-cbc-hmac";
Threefish512CbcEncryptor = threefish::Threefish512, 64, "Threefish-512-CBC", "threefish-512-cbc-hmac";
Sm4CbcEncryptor = sm4::Sm4, 16, "SM4", "sm4-cbc";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_cbc_hmac_alias_roundtrips_and_detects_tampering() {
fn check<E: SymmetricCipher>(cipher: &E, label: &str, block_len: usize) {
let exact = vec![b'0'; block_len];
let one_over = vec![b'1'; block_len + 1];
let multi = vec![0xa5u8; block_len * 3 + 7];
for plaintext in [
b"".as_slice(),
b"x",
exact.as_slice(),
one_over.as_slice(),
multi.as_slice(),
] {
let blob = cipher.encrypt(plaintext).unwrap();
assert_eq!(cipher.decrypt(&blob).unwrap(), plaintext, "{label}");
let mut tampered = blob.clone();
let last = tampered.len() - 1;
tampered[last] ^= 0x01;
assert!(cipher.decrypt(&tampered).is_err(), "{label} 篡改未被发现");
}
}
check(&Sm4CbcEncryptor::new([5u8; 16]), "sm4-cbc", 16);
check(&Aes256CbcEncryptor::new([5u8; 32]), "aes-256-cbc-hmac", 16);
check(&Camellia256CbcEncryptor::new([5u8; 32]), "camellia", 16);
check(&Aria256CbcEncryptor::new([5u8; 32]), "aria", 16);
check(&Kuznyechik256CbcEncryptor::new([5u8; 32]), "kuznyechik", 16);
check(
&Threefish512CbcEncryptor::new([5u8; 64]),
"threefish-512",
64,
);
}
#[test]
fn iv_length_follows_the_block_cipher_block_size() {
assert_eq!(Aes256CbcEncryptor::iv_len(), 16);
assert_eq!(Camellia256CbcEncryptor::iv_len(), 16);
assert_eq!(Aria256CbcEncryptor::iv_len(), 16);
assert_eq!(Kuznyechik256CbcEncryptor::iv_len(), 16);
assert_eq!(Sm4CbcEncryptor::iv_len(), 16);
assert_eq!(Threefish512CbcEncryptor::iv_len(), 64);
}
#[test]
fn identifiers_are_stable() {
assert_eq!(
Aes256CbcEncryptor::new([0u8; 32]).identifier(),
"aes-256-cbc-hmac"
);
assert_eq!(
Camellia256CbcEncryptor::new([0u8; 32]).identifier(),
"camellia-256-cbc-hmac"
);
assert_eq!(
Aria256CbcEncryptor::new([0u8; 32]).identifier(),
"aria-256-cbc-hmac"
);
assert_eq!(
Kuznyechik256CbcEncryptor::new([0u8; 32]).identifier(),
"kuznyechik-256-cbc-hmac"
);
assert_eq!(
Threefish512CbcEncryptor::new([0u8; 64]).identifier(),
"threefish-512-cbc-hmac"
);
assert_eq!(Sm4CbcEncryptor::new([0u8; 16]).identifier(), "sm4-cbc");
}
#[test]
fn iv_is_fresh_per_encryption() {
let cipher = Aes256CbcEncryptor::new([5u8; 32]);
let first = cipher.encrypt(b"same").unwrap();
let second = cipher.encrypt(b"same").unwrap();
assert_ne!(first, second);
assert_ne!(first[2..18], second[2..18]);
}
#[test]
fn wrong_key_and_bad_frames_are_rejected() {
let blob = Aes256CbcEncryptor::new([5u8; 32])
.encrypt(b"secret")
.unwrap();
let other = Aes256CbcEncryptor::new([6u8; 32]);
assert_eq!(
other.decrypt(&blob).unwrap_err(),
Error::MacVerificationFailed {
label: "AES-256-CBC"
}
);
assert_eq!(
other.decrypt(b"v9 nope").unwrap_err(),
Error::InvalidPrefix {
label: "AES-256-CBC"
}
);
assert_eq!(
other.decrypt(b"v1 short").unwrap_err(),
Error::TooShort {
label: "AES-256-CBC"
}
);
}
#[test]
fn key_length_is_checked_from_slice() {
assert_eq!(
Aes256CbcEncryptor::from_slice(&[0u8; 16]).unwrap_err(),
Error::InvalidKeyLength {
label: "AES-256-CBC",
expected: 32,
got: 16
}
);
assert_eq!(
Sm4CbcEncryptor::from_slice(&[0u8; 32]).unwrap_err(),
Error::InvalidKeyLength {
label: "SM4",
expected: 16,
got: 32
}
);
assert_eq!(
Threefish512CbcEncryptor::from_slice(&[0u8; 32]).unwrap_err(),
Error::InvalidKeyLength {
label: "Threefish-512-CBC",
expected: 64,
got: 32
}
);
}
#[test]
fn bare_block_ciphers_match_their_standard_vectors() {
use cbc::cipher::{BlockEncrypt, KeyInit};
fn unhex(text: &str) -> Vec<u8> {
crate::internal::hex_decode(text).unwrap()
}
fn block_encrypt<C: BlockEncrypt + KeyInit>(key: &[u8], plaintext: &[u8]) -> Vec<u8> {
let cipher = C::new_from_slice(key).unwrap();
let mut block = Block::<C>::clone_from_slice(plaintext);
cipher.encrypt_block(&mut block);
block.to_vec()
}
assert_eq!(
block_encrypt::<sm4::Sm4>(
&unhex("0123456789abcdeffedcba9876543210"),
&unhex("0123456789abcdeffedcba9876543210")
),
unhex("681edf34d206965e86b3e94f536e4246")
);
assert_eq!(
block_encrypt::<camellia::Camellia256>(
&unhex("0123456789abcdeffedcba987654321000112233445566778899aabbccddeeff"),
&unhex("0123456789abcdeffedcba9876543210")
),
unhex("9acc237dff16d76c20ef7c919e3a7509")
);
assert_eq!(
block_encrypt::<aria::Aria256>(
&unhex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
&unhex("00112233445566778899aabbccddeeff")
),
unhex("f92bd7c79fb72e2f2b8f80c1972d24fc")
);
assert_eq!(
block_encrypt::<kuznyechik::Kuznyechik>(
&unhex("8899aabbccddeeff0011223344556677fedcba98765432100123456789abcdef"),
&unhex("1122334455667700ffeeddccbbaa9988")
),
unhex("7f679d90bebc24305a468d42b9d4edcd")
);
let threefish_vectors = [
(
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
concat!(
"b1a2bbc6ef6025bc40eb3822161f36e375d1bb0aee3186fbd19e47c5d479947b",
"7bc2f8586e35f0cff7e7f03084b0b7b1f1ab3961a580a3e97eb41ea14a6d7bbe"
),
),
(
concat!(
"b1a2bbc6ef6025bc40eb3822161f36e375d1bb0aee3186fbd19e47c5d479947b",
"7bc2f8586e35f0cff7e7f03084b0b7b1f1ab3961a580a3e97eb41ea14a6d7bbe"
),
"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
concat!(
"f13ca06760dd9bbeab87b6c56f3bbbdb",
"e9d08a77978b942ac02d471dc10268f2",
"261c3d4330d6ca341f4bd4115dee16a2",
"1dcda2a34a0a76fba976174e4cf1e306"
),
),
];
for (key, plaintext, expected) in threefish_vectors {
assert_eq!(
block_encrypt::<threefish::Threefish512>(&unhex(key), &unhex(plaintext)),
unhex(expected),
"Threefish-512 official KAT"
);
}
}
}