Skip to main content

encryption/
factory.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! 主密钥工厂:从 32 字节主密钥派生各算法独立子密钥,避免同一密钥跨算法复用
4//! (对应 PHP 版 `EncryptionManagerFactory`)。
5//!
6//! 派生方案只有**修正后的参数顺序**一种:`HMAC-SHA256(key = 主密钥, msg = 用途标签)`。
7//! 原版历史遗留的 v1 反序方案(标签作 key)不复刻——本 crate 采用 Rust 独立格式。
8
9use hmac::{Hmac, Mac};
10use sha2::Sha256;
11
12use crate::contract::SymmetricCipher;
13use crate::encryptor::{Aes256CbcEncryptor, Aes256GcmEncryptor, SodiumXChaCha20Encryptor};
14use crate::error::{Error, Result};
15use crate::guomi::{Sm4CbcEncryptor, Zuc128Encryptor};
16use crate::manager::EncryptionManager;
17use crate::registry::Registry;
18
19// 用途标签(与原版一致;标签不承载秘密,秘密始终是主密钥)。
20const LABEL_AES_GCM: &[u8] = b"dgn:derive:aes-gcm";
21const LABEL_AES_CBC: &[u8] = b"dgn:derive:aes-cbc";
22const LABEL_SM4: &[u8] = b"dgn:derive:sm4";
23const LABEL_ZUC: &[u8] = b"dgn:derive:zuc";
24const LABEL_SODIUM: &[u8] = b"dgn:derive:sodium";
25
26/// 从一把主密钥构建 [`EncryptionManager`]。
27///
28/// 一次性注册 `aes-256-gcm`、`aes-256-cbc-hmac`、`sodium-xchacha20`
29/// (以及国密的 `sm4-cbc`、`zuc-128`);`default_identifier` 必须已注册。
30pub struct EncryptionManagerFactory;
31
32impl EncryptionManagerFactory {
33    pub fn from_master_key(
34        master_key: &[u8],
35        default_identifier: &str,
36    ) -> Result<EncryptionManager> {
37        let master_key: [u8; 32] = master_key.try_into().map_err(|_| Error::InvalidMasterKey {
38            got: master_key.len(),
39        })?;
40
41        let mut registry: Registry<Box<dyn SymmetricCipher>> = Registry::new("symmetric cipher");
42        registry.register(Box::new(Aes256GcmEncryptor::new(derive_subkey(
43            &master_key,
44            LABEL_AES_GCM,
45        ))));
46        registry.register(Box::new(Aes256CbcEncryptor::new(derive_subkey(
47            &master_key,
48            LABEL_AES_CBC,
49        ))));
50        registry.register(Box::new(SodiumXChaCha20Encryptor::new(derive_subkey(
51            &master_key,
52            LABEL_SODIUM,
53        ))));
54        registry.register(Box::new(Sm4CbcEncryptor::new(derive_subkey_16(
55            &master_key,
56            LABEL_SM4,
57        ))));
58        registry.register(Box::new(Zuc128Encryptor::new(derive_subkey_16(
59            &master_key,
60            LABEL_ZUC,
61        ))));
62
63        EncryptionManager::new(registry, default_identifier)
64    }
65}
66
67/// 子密钥 = `HMAC-SHA256(key = 主密钥, msg = 用途标签)`。
68fn derive_subkey(master_key: &[u8; 32], label: &[u8]) -> [u8; 32] {
69    let mut mac = Hmac::<Sha256>::new_from_slice(master_key).expect("HMAC accepts any key size");
70    mac.update(label);
71    mac.finalize().into_bytes().into()
72}
73
74/// SM4 / ZUC 用 16 字节子密钥:取派生结果前 16 字节(与原版一致)。
75fn derive_subkey_16(master_key: &[u8; 32], label: &[u8]) -> [u8; 16] {
76    let full = derive_subkey(master_key, label);
77    full[..16].try_into().expect("slice is exactly 16 bytes")
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn factory_validates_master_key_and_default() {
86        assert_eq!(
87            EncryptionManagerFactory::from_master_key(&[0u8; 31], "aes-256-gcm").unwrap_err(),
88            Error::InvalidMasterKey { got: 31 }
89        );
90        assert_eq!(
91            EncryptionManagerFactory::from_master_key(&[0u8; 32], "aes-512").unwrap_err(),
92            Error::UnknownIdentifier {
93                kind: "symmetric cipher",
94                identifier: "aes-512".to_string()
95            }
96        );
97    }
98
99    #[test]
100    fn factory_registers_all_five_and_they_roundtrip() {
101        let manager =
102            EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
103        let expected = [
104            "aes-256-cbc-hmac",
105            "aes-256-gcm",
106            "sm4-cbc",
107            "sodium-xchacha20",
108            "zuc-128",
109        ];
110        assert_eq!(manager.registry().identifiers(), expected);
111
112        for identifier in expected {
113            let blob = manager.encrypt_with(identifier, b"cross-check").unwrap();
114            assert_eq!(
115                manager.decrypt_with(identifier, &blob).unwrap(),
116                b"cross-check"
117            );
118        }
119    }
120
121    #[test]
122    fn subkeys_are_independent_per_algorithm() {
123        let manager =
124            EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
125        let blob = manager.encrypt_with("aes-256-gcm", b"secret").unwrap();
126        // 同一把主密钥,但换了算法:密文结构不同 / 密钥不同,解不开。
127        assert!(manager.decrypt_with("aes-256-cbc-hmac", &blob).is_err());
128        assert!(manager.decrypt_with("sm4-cbc", &blob).is_err());
129    }
130}