use encryption::contract::{Identified, SymmetricCipher};
use encryption::error::{Error, Result};
use encryption::factory::EncryptionManagerFactory;
use encryption::guomi::Zuc128Encryptor;
use encryption::registry::Registry;
const ALL_IDENTIFIERS: usize = 14;
#[test]
fn same_plaintext_yields_different_ciphertext_every_time() {
let manager = EncryptionManagerFactory::from_master_key_all(&[7u8; 32], "aes-256-gcm").unwrap();
assert_eq!(manager.registry().identifiers().len(), ALL_IDENTIFIERS);
for identifier in manager.registry().identifiers() {
let first = manager.encrypt_with(identifier, b"same input").unwrap();
let second = manager.encrypt_with(identifier, b"same input").unwrap();
assert_ne!(first, second, "{identifier} 必用新的 IV / nonce");
}
}
#[test]
fn ciphertext_from_one_algorithm_never_decrypts_with_another() {
let manager = EncryptionManagerFactory::from_master_key_all(&[7u8; 32], "aes-256-gcm").unwrap();
let identifiers = manager.registry().identifiers().to_vec();
assert_eq!(identifiers.len(), ALL_IDENTIFIERS);
for source in &identifiers {
let blob = manager.encrypt_with(source, b"secret").unwrap();
for target in &identifiers {
if target == source {
continue;
}
assert!(
manager.decrypt_with(target, &blob).is_err(),
"{source} 的密文不该被 {target} 解开"
);
}
}
}
#[test]
fn tampered_blobs_are_rejected() {
let manager = EncryptionManagerFactory::from_master_key_all(&[7u8; 32], "aes-256-gcm").unwrap();
for identifier in manager.registry().identifiers() {
let blob = manager.encrypt_with(identifier, b"payload").unwrap();
let mut broken_prefix = blob.clone();
broken_prefix[0] = b'v';
broken_prefix[1] = b'9';
assert!(
manager.decrypt_with(identifier, &broken_prefix).is_err(),
"{identifier}"
);
assert!(
manager.decrypt_with(identifier, &blob[..3]).is_err(),
"{identifier}"
);
let mut tampered = blob.clone();
let last = tampered.len() - 1;
tampered[last] ^= 0x01;
assert!(
manager.decrypt_with(identifier, &tampered).is_err(),
"{identifier}"
);
}
}
#[test]
fn custom_cipher_plugins_can_be_registered() {
struct XorDemoCipher {
key: [u8; 16],
}
impl Identified for XorDemoCipher {
fn identifier(&self) -> &str {
"xor-demo"
}
}
impl SymmetricCipher for XorDemoCipher {
fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
Ok(plaintext
.iter()
.zip(self.key.iter().cycle())
.map(|(byte, key)| byte ^ key)
.collect())
}
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
self.encrypt(ciphertext)
}
}
let manager = EncryptionManagerFactory::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap();
let mut manager = manager;
manager
.registry_mut()
.register(Box::new(XorDemoCipher { key: [3u8; 16] }));
let blob = manager.encrypt_with("xor-demo", b"plugin data").unwrap();
assert_eq!(
manager.decrypt_with("xor-demo", &blob).unwrap(),
b"plugin data"
);
}
#[test]
fn unknown_identifiers_report_structured_errors() {
let manager = EncryptionManagerFactory::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap();
assert_eq!(
manager.encrypt_with("no-such-cipher", b"x").unwrap_err(),
Error::UnknownIdentifier {
kind: "symmetric cipher",
identifier: "no-such-cipher".to_string()
}
);
}
#[test]
fn zuc_handles_non_word_aligned_lengths() {
let cipher = Zuc128Encryptor::new([1u8; 16]);
for length in 0..16usize {
let payload = vec![0xabu8; length];
let blob = cipher.encrypt(&payload).unwrap();
assert_eq!(cipher.decrypt(&blob).unwrap(), payload, "length={length}");
}
}
#[test]
fn registry_reports_unknown_identifiers() {
let registry: Registry<Box<dyn SymmetricCipher>> = Registry::new("symmetric cipher");
assert!(registry.get("missing").is_err());
assert!(registry.identifiers().is_empty());
}
#[test]
fn from_master_key_still_registers_exactly_the_five_frozen_defaults() {
let manager = EncryptionManagerFactory::from_master_key(&[7u8; 32], "aes-256-gcm").unwrap();
assert_eq!(
manager.registry().identifiers(),
[
"aes-256-cbc-hmac",
"aes-256-gcm",
"sm4-cbc",
"sodium-xchacha20",
"zuc-128"
]
);
}
#[test]
fn from_master_key_all_adds_the_extensions_without_disturbing_the_defaults() {
let master = [7u8; 32];
let five = EncryptionManagerFactory::from_master_key(&master, "aes-256-gcm").unwrap();
let all = EncryptionManagerFactory::from_master_key_all(&master, "aes-256-gcm").unwrap();
let five_ids = five.registry().identifiers();
let all_ids = all.registry().identifiers();
assert_eq!(all_ids.len(), ALL_IDENTIFIERS);
assert_eq!(five_ids.len(), ALL_IDENTIFIERS - 9);
for identifier in &five_ids {
assert!(all_ids.contains(identifier), "{identifier} 缺席全量工厂");
}
for identifier in &five_ids {
let blob = five.encrypt_with(identifier, b"cross-factory").unwrap();
assert_eq!(
all.decrypt_with(identifier, &blob).unwrap(),
b"cross-factory",
"{identifier} 的子密钥在两种构造方式下不一致"
);
}
}
#[test]
fn every_derived_subkey_is_independent_across_all_fourteen() {
let manager = EncryptionManagerFactory::from_master_key_all(&[7u8; 32], "aes-256-gcm").unwrap();
let identifiers = manager.registry().identifiers().to_vec();
for source in &identifiers {
let blob = manager.encrypt_with(source, b"subkey-separation").unwrap();
for target in &identifiers {
if target == source {
continue;
}
assert!(
manager.decrypt_with(target, &blob).is_err(),
"{source} 的密文被 {target} 解开了——两者子密钥相同"
);
}
}
}
#[test]
fn extension_ciphertexts_do_not_survive_a_master_key_change() {
let original =
EncryptionManagerFactory::from_master_key_all(&[7u8; 32], "aes-256-gcm").unwrap();
let rotated = EncryptionManagerFactory::from_master_key_all(&[8u8; 32], "aes-256-gcm").unwrap();
for identifier in original.registry().identifiers() {
let blob = original.encrypt_with(identifier, b"rotate me").unwrap();
assert!(
rotated.decrypt_with(identifier, &blob).is_err(),
"{identifier} 换主密钥后仍能解开——子密钥没跟着主密钥走"
);
}
}