use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use sha2::Sha256;
use zeroize::Zeroizing;
use crate::contract::SymmetricCipher;
use crate::encryptor::{
Aes256CbcEncryptor, Aes256GcmEncryptor, Aes256GcmSivEncryptor, Aria256CbcEncryptor,
Camellia256CbcEncryptor, ChaCha20Poly1305IetfEncryptor, Kuznyechik256CbcEncryptor,
Salsa20Encryptor, SodiumXChaCha20Encryptor, Threefish512CbcEncryptor,
};
use crate::error::{Error, Result};
use crate::guomi::{Sm4CbcEncryptor, Sm4GcmEncryptor, Zuc128Encryptor, Zuc256Encryptor};
use crate::manager::EncryptionManager;
use crate::registry::Registry;
const LABEL_AES_GCM: &[u8] = b"dgn:derive:aes-gcm";
const LABEL_AES_CBC: &[u8] = b"dgn:derive:aes-cbc";
const LABEL_SM4: &[u8] = b"dgn:derive:sm4";
const LABEL_ZUC: &[u8] = b"dgn:derive:zuc";
const LABEL_SODIUM: &[u8] = b"dgn:derive:sodium";
const LABEL_PRK_EXT: &[u8] = b"dgn:derive-ext";
const LABEL_CHACHA20_POLY1305_IETF: &[u8] = b"dgn:derive-ext:chacha20-poly1305-ietf";
const LABEL_AES_GCM_SIV: &[u8] = b"dgn:derive-ext:aes-256-gcm-siv";
const LABEL_CAMELLIA: &[u8] = b"dgn:derive-ext:camellia-256-cbc-hmac";
const LABEL_ARIA: &[u8] = b"dgn:derive-ext:aria-256-cbc-hmac";
const LABEL_THREEFISH: &[u8] = b"dgn:derive-ext:threefish-512-cbc-hmac";
const LABEL_KUZNYECHIK: &[u8] = b"dgn:derive-ext:kuznyechik-256-cbc-hmac";
const LABEL_SALSA20: &[u8] = b"dgn:derive-ext:salsa20";
const LABEL_SM4_GCM: &[u8] = b"dgn:derive-ext:sm4-gcm";
const LABEL_ZUC256: &[u8] = b"dgn:derive-ext:zuc-256";
pub struct EncryptionManagerFactory;
impl EncryptionManagerFactory {
pub fn from_master_key(
master_key: &[u8],
default_identifier: &str,
) -> Result<EncryptionManager> {
let master_key = master_key_32(master_key)?;
let mut registry: Registry<Box<dyn SymmetricCipher>> = Registry::new("symmetric cipher");
register_default_ciphers(&mut registry, &master_key);
EncryptionManager::new(registry, default_identifier)
}
pub fn from_master_key_all(
master_key: &[u8],
default_identifier: &str,
) -> Result<EncryptionManager> {
let master_key = master_key_32(master_key)?;
let mut registry: Registry<Box<dyn SymmetricCipher>> = Registry::new("symmetric cipher");
register_default_ciphers(&mut registry, &master_key);
register_extension_ciphers(&mut registry, &master_key);
EncryptionManager::new(registry, default_identifier)
}
pub fn register_extensions(
registry: &mut Registry<Box<dyn SymmetricCipher>>,
master_key: &[u8],
) -> Result<()> {
let master_key = master_key_32(master_key)?;
register_extension_ciphers(registry, &master_key);
Ok(())
}
}
fn master_key_32(master_key: &[u8]) -> Result<Zeroizing<[u8; 32]>> {
let key: [u8; 32] = master_key.try_into().map_err(|_| Error::InvalidMasterKey {
got: master_key.len(),
})?;
Ok(Zeroizing::new(key))
}
fn register_default_ciphers(
registry: &mut Registry<Box<dyn SymmetricCipher>>,
master_key: &[u8; 32],
) {
registry.register(Box::new(Aes256GcmEncryptor::new(derive_subkey(
master_key,
LABEL_AES_GCM,
))));
registry.register(Box::new(Aes256CbcEncryptor::new(derive_subkey(
master_key,
LABEL_AES_CBC,
))));
registry.register(Box::new(SodiumXChaCha20Encryptor::new(derive_subkey(
master_key,
LABEL_SODIUM,
))));
registry.register(Box::new(Sm4CbcEncryptor::new(derive_subkey_16(
master_key, LABEL_SM4,
))));
registry.register(Box::new(Zuc128Encryptor::new(derive_subkey_16(
master_key, LABEL_ZUC,
))));
}
fn register_extension_ciphers(
registry: &mut Registry<Box<dyn SymmetricCipher>>,
master_key: &[u8; 32],
) {
registry.register(Box::new(ChaCha20Poly1305IetfEncryptor::new(
derive_subkey_ext(master_key, LABEL_CHACHA20_POLY1305_IETF),
)));
registry.register(Box::new(Aes256GcmSivEncryptor::new(derive_subkey_ext(
master_key,
LABEL_AES_GCM_SIV,
))));
registry.register(Box::new(Camellia256CbcEncryptor::new(derive_subkey_ext(
master_key,
LABEL_CAMELLIA,
))));
registry.register(Box::new(Aria256CbcEncryptor::new(derive_subkey_ext(
master_key, LABEL_ARIA,
))));
registry.register(Box::new(Threefish512CbcEncryptor::new(derive_subkey_ext(
master_key,
LABEL_THREEFISH,
))));
registry.register(Box::new(Kuznyechik256CbcEncryptor::new(derive_subkey_ext(
master_key,
LABEL_KUZNYECHIK,
))));
registry.register(Box::new(Salsa20Encryptor::new(derive_subkey_ext(
master_key,
LABEL_SALSA20,
))));
registry.register(Box::new(Sm4GcmEncryptor::new(derive_subkey_ext(
master_key,
LABEL_SM4_GCM,
))));
registry.register(Box::new(Zuc256Encryptor::new(derive_subkey_ext(
master_key,
LABEL_ZUC256,
))));
}
fn derive_subkey(master_key: &[u8; 32], label: &[u8]) -> [u8; 32] {
let mut mac = Hmac::<Sha256>::new_from_slice(master_key).expect("HMAC accepts any key size");
mac.update(label);
mac.finalize().into_bytes().into()
}
fn derive_subkey_16(master_key: &[u8; 32], label: &[u8]) -> [u8; 16] {
let full = Zeroizing::new(derive_subkey(master_key, label));
full[..16].try_into().expect("slice is exactly 16 bytes")
}
fn derive_subkey_ext<const N: usize>(master_key: &[u8; 32], label: &[u8]) -> [u8; N] {
let prk = Zeroizing::new(derive_subkey(master_key, LABEL_PRK_EXT));
let hkdf = Hkdf::<Sha256>::from_prk(prk.as_slice()).expect("PRK is 32 bytes (= HashLen)");
let mut subkey = [0u8; N];
hkdf.expand(label, &mut subkey)
.expect("N in {16, 32, 64} is far below the HKDF limit of 255 * 32");
subkey
}
#[cfg(test)]
mod tests {
use super::*;
fn hex(bytes: &[u8]) -> String {
crate::internal::hex_encode(bytes)
}
#[test]
fn factory_validates_master_key_and_default() {
assert_eq!(
EncryptionManagerFactory::from_master_key(&[0u8; 31], "aes-256-gcm").unwrap_err(),
Error::InvalidMasterKey { got: 31 }
);
assert_eq!(
EncryptionManagerFactory::from_master_key(&[0u8; 32], "aes-512").unwrap_err(),
Error::UnknownIdentifier {
kind: "symmetric cipher",
identifier: "aes-512".to_string()
}
);
}
#[test]
fn factory_registers_all_five_and_they_roundtrip() {
let manager =
EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
let expected = [
"aes-256-cbc-hmac",
"aes-256-gcm",
"sm4-cbc",
"sodium-xchacha20",
"zuc-128",
];
assert_eq!(manager.registry().identifiers(), expected);
for identifier in expected {
let blob = manager.encrypt_with(identifier, b"cross-check").unwrap();
assert_eq!(
manager.decrypt_with(identifier, &blob).unwrap(),
b"cross-check"
);
}
}
#[test]
fn subkeys_are_independent_per_algorithm() {
let manager =
EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
let blob = manager.encrypt_with("aes-256-gcm", b"secret").unwrap();
assert!(manager.decrypt_with("aes-256-cbc-hmac", &blob).is_err());
assert!(manager.decrypt_with("sm4-cbc", &blob).is_err());
}
#[test]
fn legacy_subkeys_are_frozen() {
let master = [42u8; 32];
let cases: [(&[u8], &str); 5] = [
(
LABEL_AES_GCM,
"8c40365a67198f254b99a3e59b5da02762b2368432e436807d52646764709efa",
),
(
LABEL_AES_CBC,
"e2b808949cc38e4be732924c88c56738a607cbd63e1e6a81ff89cada485da4b5",
),
(
LABEL_SODIUM,
"24f1543b4d2d3a48c835f3d10bd758e7ad94f55fed8b0fb64fa5e29fa5ff834d",
),
(
LABEL_SM4,
"a0a1e7c2645d1eed02f2fc43eca98992009424fb80e8314b5218be6f4b831f97",
),
(
LABEL_ZUC,
"e2ac939b959fff17dd6d7a1df705b5e00840364974c0482c30419661f4195b88",
),
];
for (label, expected) in cases {
assert_eq!(
hex(&derive_subkey(&master, label)),
expected,
"label {}",
String::from_utf8_lossy(label)
);
}
assert_eq!(
hex(&derive_subkey_16(&master, LABEL_SM4)),
"a0a1e7c2645d1eed02f2fc43eca98992"
);
assert_eq!(
hex(&derive_subkey_16(&master, LABEL_ZUC)),
"e2ac939b959fff17dd6d7a1df705b5e0"
);
}
#[test]
fn extension_subkeys_are_frozen() {
let master = [42u8; 32];
assert_eq!(
hex(&derive_subkey(&master, LABEL_PRK_EXT)),
"d13e7885aa737fe03fd1aff45bfee04971cb42a65c6d83df6c81251b4a48e76a"
);
assert_eq!(
hex(&derive_subkey_ext::<32>(
&master,
LABEL_CHACHA20_POLY1305_IETF
)),
"b72beef1f703fbc67add7c0045129406a7e33bed2f44bf1ba5cfdbca46983f3c"
);
assert_eq!(
hex(&derive_subkey_ext::<32>(&master, LABEL_AES_GCM_SIV)),
"4ea65b0fec0a961de6c8f4a3a4e1d4a1a422d8e494ed976d51c6136ab3c65d84"
);
assert_eq!(
hex(&derive_subkey_ext::<32>(&master, LABEL_CAMELLIA)),
"64fc1e0b8ba07c51e5b3bcfc43be3d370f1d26276957d0b07144f6e3b12bbf21"
);
assert_eq!(
hex(&derive_subkey_ext::<32>(&master, LABEL_ARIA)),
"ae167985194b12afb4ee564310e23573767f06e8ac12967914b9ad651ef88b34"
);
assert_eq!(
hex(&derive_subkey_ext::<64>(&master, LABEL_THREEFISH)),
concat!(
"951afa424d5221c2de5f5f7f9be38e23",
"1eb96fc1842c25b04804c8cc50b7f131",
"4849ff963922f5ec93d127536f52c310",
"1920c5477ad5f04ad8ad122efd4c3b87"
)
);
assert_eq!(
hex(&derive_subkey_ext::<32>(&master, LABEL_KUZNYECHIK)),
"20825902f56afe01bc183dce96fd7d8f4a68d7e9854be51a73b9d5d1c3e3d887"
);
assert_eq!(
hex(&derive_subkey_ext::<32>(&master, LABEL_SALSA20)),
"c059a09ff49bb3c6916beb350354091b27f80c4708da3ae7d17364f1d7a6898b"
);
assert_eq!(
hex(&derive_subkey_ext::<16>(&master, LABEL_SM4_GCM)),
"3b02d8456195d02a514fdbeb5d5ff8a8"
);
assert_eq!(
hex(&derive_subkey_ext::<32>(&master, LABEL_ZUC256)),
"832ef3816ff4fde09aecce478f817d2ff878000567ffb7c7a585c21071775a4c"
);
}
#[test]
fn labels_and_subkeys_are_distinct() {
let master = [42u8; 32];
let legacy_labels = [
LABEL_AES_GCM,
LABEL_AES_CBC,
LABEL_SM4,
LABEL_ZUC,
LABEL_SODIUM,
];
let ext_labels = [
LABEL_CHACHA20_POLY1305_IETF,
LABEL_AES_GCM_SIV,
LABEL_CAMELLIA,
LABEL_ARIA,
LABEL_THREEFISH,
LABEL_KUZNYECHIK,
LABEL_SALSA20,
LABEL_SM4_GCM,
LABEL_ZUC256,
];
let mut labels: Vec<&[u8]> = legacy_labels.to_vec();
labels.extend(ext_labels.iter().copied());
for (i, first) in labels.iter().enumerate() {
for second in &labels[i + 1..] {
assert_ne!(first, second, "用途标签必须互不重名");
}
}
let mut subkeys: Vec<Vec<u8>> = legacy_labels
.iter()
.map(|label| derive_subkey(&master, label).to_vec())
.collect();
subkeys.extend(
ext_labels
.iter()
.map(|label| derive_subkey_ext::<64>(&master, label).to_vec()),
);
assert_eq!(subkeys.len(), 14);
for (i, first) in subkeys.iter().enumerate() {
for second in &subkeys[i + 1..] {
assert_ne!(
&first[..16],
&second[..16],
"任一算法的子密钥前缀都不得与其他算法相同"
);
}
}
}
#[test]
fn all_fourteen_roundtrip_and_validate() {
assert_eq!(
EncryptionManagerFactory::from_master_key_all(&[0u8; 31], "aes-256-gcm").unwrap_err(),
Error::InvalidMasterKey { got: 31 }
);
assert_eq!(
EncryptionManagerFactory::from_master_key_all(&[0u8; 32], "aes-512").unwrap_err(),
Error::UnknownIdentifier {
kind: "symmetric cipher",
identifier: "aes-512".to_string()
}
);
let manager =
EncryptionManagerFactory::from_master_key_all(&[42u8; 32], "aes-256-gcm").unwrap();
let expected = [
"aes-256-cbc-hmac",
"aes-256-gcm",
"aes-256-gcm-siv",
"aria-256-cbc-hmac",
"camellia-256-cbc-hmac",
"chacha20-poly1305-ietf",
"kuznyechik-256-cbc-hmac",
"salsa20",
"sm4-cbc",
"sm4-gcm",
"sodium-xchacha20",
"threefish-512-cbc-hmac",
"zuc-128",
"zuc-256",
];
assert_eq!(manager.registry().identifiers(), expected);
for identifier in expected {
let blob = manager.encrypt_with(identifier, b"cross-check").unwrap();
assert_eq!(
manager.decrypt_with(identifier, &blob).unwrap(),
b"cross-check",
"{identifier}"
);
}
}
#[test]
fn legacy_subkeys_are_unchanged_when_extensions_are_added() {
let five = EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
let all =
EncryptionManagerFactory::from_master_key_all(&[42u8; 32], "aes-256-gcm").unwrap();
for identifier in five.registry().identifiers() {
let from_five = five.encrypt_with(identifier, b"shared subkey").unwrap();
assert_eq!(
all.decrypt_with(identifier, &from_five).unwrap(),
b"shared subkey",
"{identifier}"
);
let from_all = all.encrypt_with(identifier, b"shared subkey").unwrap();
assert_eq!(
five.decrypt_with(identifier, &from_all).unwrap(),
b"shared subkey",
"{identifier}"
);
}
}
#[test]
fn register_extensions_composes_into_existing_registry() {
assert_eq!(
EncryptionManagerFactory::register_extensions(
&mut Registry::new("symmetric cipher"),
&[0u8; 16]
)
.unwrap_err(),
Error::InvalidMasterKey { got: 16 }
);
let mut manager =
EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
EncryptionManagerFactory::register_extensions(manager.registry_mut(), &[42u8; 32]).unwrap();
assert_eq!(manager.registry().identifiers().len(), 14);
let all =
EncryptionManagerFactory::from_master_key_all(&[42u8; 32], "aes-256-gcm").unwrap();
assert_eq!(
manager.registry().identifiers(),
all.registry().identifiers()
);
for identifier in manager.registry().identifiers() {
let composed = manager.encrypt_with(identifier, b"composed").unwrap();
assert_eq!(
all.decrypt_with(identifier, &composed).unwrap(),
b"composed",
"{identifier}"
);
}
}
}