test(aes): add AES-CBC NIST SP 800-38A test vectors

Add integration tests for AES-CBC mode:
    - Single block encrypt/decrypt with NIST vectors
    - Multi-block encrypt with NIST vectors
    - Multi-block roundtrip verification
    - Empty plaintext handling
    - Arbitrary length plaintext
This commit is contained in:
Kristofers Solo 2025-12-31 04:11:15 +02:00
parent 1ffc0327b3
commit ae33c596ef
Signed by: kristoferssolo
GPG Key ID: 8687F2D3EEE6F0ED
2 changed files with 119 additions and 3 deletions

115
aes/tests/aes_cbc.rs Normal file
View File

@ -0,0 +1,115 @@
//! NIST SP 800-38A AES-128-CBC test vectors.
use aes::{AesCbc, Iv};
use claims::assert_ok;
// NIST SP 800-38A test vectors for AES-128-CBC
const NIST_KEY: u128 = 0x2b7e1516_28aed2a6_abf71588_09cf4f3c;
const NIST_IV: u128 = 0x00010203_04050607_08090a0b_0c0d0e0f;
// Test vector blocks (plaintext -> ciphertext)
const NIST_BLOCKS: [(u128, u128); 4] = [
(
0x6bc1bee2_2e409f96_e93d7e11_7393172a,
0x7649abac_8119b246_cee98e9b_12e9197d,
),
(
0xae2d8a57_1e03ac9c_9eb76fac_45af8e51,
0x5086cb9b_507219ee_95db113a_917678b2,
),
(
0x30c81c46_a35ce411_e5fbc119_1a0a52ef,
0x73bed6b8_e3c1743b_7116e69e_22229516,
),
(
0xf69f2445_df4f9b17_ad2b417b_e66c3710,
0x3ff1caa1_681fac09_120eca30_7586e1a1,
),
];
#[test]
fn nist_single_block_encrypt() {
let cipher = AesCbc::new(NIST_KEY, Iv::new(NIST_IV));
let plaintext = NIST_BLOCKS[0].0.to_be_bytes();
let expected = NIST_BLOCKS[0].1.to_be_bytes();
let ciphertext = assert_ok!(cipher.encrypt(&plaintext));
// Result includes PKCS#7 padding (16 bytes padding for aligned input)
assert_eq!(ciphertext.len(), 32);
assert_eq!(&ciphertext[..16], &expected);
}
#[test]
fn nist_single_block_decrypt() {
let cipher = AesCbc::new(NIST_KEY, Iv::new(NIST_IV));
let plaintext = NIST_BLOCKS[0].0.to_be_bytes();
// First encrypt to get valid padded ciphertext
let ciphertext = assert_ok!(cipher.encrypt(&plaintext));
let decrypted = assert_ok!(cipher.decrypt(&ciphertext));
assert_eq!(decrypted, plaintext);
}
#[test]
fn nist_multi_block_encrypt() {
let cipher = AesCbc::new(NIST_KEY, Iv::new(NIST_IV));
// Concatenate all 4 plaintext blocks (64 bytes)
let mut plaintext = Vec::with_capacity(64);
for (pt, _) in &NIST_BLOCKS {
plaintext.extend_from_slice(&pt.to_be_bytes());
}
// Concatenate expected ciphertext blocks
let mut expected = Vec::with_capacity(64);
for (_, ct) in &NIST_BLOCKS {
expected.extend_from_slice(&ct.to_be_bytes());
}
let ciphertext = assert_ok!(cipher.encrypt(&plaintext));
// Result includes padding (64 + 16 = 80 bytes)
assert_eq!(ciphertext.len(), 80);
// First 3 blocks should match NIST vectors exactly
assert_eq!(&ciphertext[..48], &expected[..48]);
}
#[test]
fn nist_multi_block_roundtrip() {
let cipher = AesCbc::new(NIST_KEY, Iv::new(NIST_IV));
let mut plaintext = Vec::with_capacity(64);
for (pt, _) in &NIST_BLOCKS {
plaintext.extend_from_slice(&pt.to_be_bytes());
}
let ciphertext = assert_ok!(cipher.encrypt(&plaintext));
let decrypted = assert_ok!(cipher.decrypt(&ciphertext));
assert_eq!(decrypted, plaintext);
}
#[test]
fn empty_plaintext() {
let cipher = AesCbc::new(NIST_KEY, Iv::new(NIST_IV));
let ciphertext = assert_ok!(cipher.encrypt(&[]));
// Empty input gets full block of padding
assert_eq!(ciphertext.len(), 16);
let decrypted = assert_ok!(cipher.decrypt(&ciphertext));
assert!(decrypted.is_empty());
}
#[test]
fn arbitrary_length_plaintext() {
let cipher = AesCbc::new(NIST_KEY, Iv::new(NIST_IV));
let plaintext = b"This is a test message with arbitrary length!";
let ciphertext = assert_ok!(cipher.encrypt(plaintext));
let decrypted = assert_ok!(cipher.decrypt(&ciphertext));
assert_eq!(decrypted, plaintext);
}

View File

@ -55,9 +55,10 @@ impl CipherContext {
} }
fn process_cbc(&self) -> CipherResult<String> { fn process_cbc(&self) -> CipherResult<String> {
let iv = self.iv.as_ref().ok_or_else(|| { let iv = self
CipherError::InvalidPadding("CBC mode requires an IV".into()) .iv
})?; .as_ref()
.ok_or_else(|| CipherError::InvalidPadding("CBC mode requires an IV".into()))?;
let cipher = self.algorithm.new_cbc_cipher(&self.key, iv)?; let cipher = self.algorithm.new_cbc_cipher(&self.key, iv)?;