1use hkdf::Hkdf;
30use hmac::{Hmac, Mac};
31use sha2::Sha256;
32use zeroize::Zeroizing;
33
34use crate::contract::SymmetricCipher;
35use crate::encryptor::{
36 Aes256CbcEncryptor, Aes256GcmEncryptor, Aes256GcmSivEncryptor, Aria256CbcEncryptor,
37 Camellia256CbcEncryptor, ChaCha20Poly1305IetfEncryptor, Kuznyechik256CbcEncryptor,
38 Salsa20Encryptor, SodiumXChaCha20Encryptor, Threefish512CbcEncryptor,
39};
40use crate::error::{Error, Result};
41use crate::guomi::{Sm4CbcEncryptor, Sm4GcmEncryptor, Zuc128Encryptor, Zuc256Encryptor};
42use crate::manager::EncryptionManager;
43use crate::registry::Registry;
44
45const LABEL_AES_GCM: &[u8] = b"dgn:derive:aes-gcm";
47const LABEL_AES_CBC: &[u8] = b"dgn:derive:aes-cbc";
48const LABEL_SM4: &[u8] = b"dgn:derive:sm4";
49const LABEL_ZUC: &[u8] = b"dgn:derive:zuc";
50const LABEL_SODIUM: &[u8] = b"dgn:derive:sodium";
51
52const LABEL_PRK_EXT: &[u8] = b"dgn:derive-ext";
54
55const LABEL_CHACHA20_POLY1305_IETF: &[u8] = b"dgn:derive-ext:chacha20-poly1305-ietf";
57const LABEL_AES_GCM_SIV: &[u8] = b"dgn:derive-ext:aes-256-gcm-siv";
58const LABEL_CAMELLIA: &[u8] = b"dgn:derive-ext:camellia-256-cbc-hmac";
59const LABEL_ARIA: &[u8] = b"dgn:derive-ext:aria-256-cbc-hmac";
60const LABEL_THREEFISH: &[u8] = b"dgn:derive-ext:threefish-512-cbc-hmac";
61const LABEL_KUZNYECHIK: &[u8] = b"dgn:derive-ext:kuznyechik-256-cbc-hmac";
62const LABEL_SALSA20: &[u8] = b"dgn:derive-ext:salsa20";
63const LABEL_SM4_GCM: &[u8] = b"dgn:derive-ext:sm4-gcm";
64const LABEL_ZUC256: &[u8] = b"dgn:derive-ext:zuc-256";
65
66pub struct EncryptionManagerFactory;
75
76impl EncryptionManagerFactory {
77 pub fn from_master_key(
79 master_key: &[u8],
80 default_identifier: &str,
81 ) -> Result<EncryptionManager> {
82 let master_key = master_key_32(master_key)?;
83
84 let mut registry: Registry<Box<dyn SymmetricCipher>> = Registry::new("symmetric cipher");
85 register_default_ciphers(&mut registry, &master_key);
86
87 EncryptionManager::new(registry, default_identifier)
88 }
89
90 pub fn from_master_key_all(
110 master_key: &[u8],
111 default_identifier: &str,
112 ) -> Result<EncryptionManager> {
113 let master_key = master_key_32(master_key)?;
114
115 let mut registry: Registry<Box<dyn SymmetricCipher>> = Registry::new("symmetric cipher");
116 register_default_ciphers(&mut registry, &master_key);
117 register_extension_ciphers(&mut registry, &master_key);
118
119 EncryptionManager::new(registry, default_identifier)
120 }
121
122 pub fn register_extensions(
135 registry: &mut Registry<Box<dyn SymmetricCipher>>,
136 master_key: &[u8],
137 ) -> Result<()> {
138 let master_key = master_key_32(master_key)?;
139 register_extension_ciphers(registry, &master_key);
140 Ok(())
141 }
142}
143
144fn master_key_32(master_key: &[u8]) -> Result<Zeroizing<[u8; 32]>> {
147 let key: [u8; 32] = master_key.try_into().map_err(|_| Error::InvalidMasterKey {
148 got: master_key.len(),
149 })?;
150 Ok(Zeroizing::new(key))
151}
152
153fn register_default_ciphers(
155 registry: &mut Registry<Box<dyn SymmetricCipher>>,
156 master_key: &[u8; 32],
157) {
158 registry.register(Box::new(Aes256GcmEncryptor::new(derive_subkey(
159 master_key,
160 LABEL_AES_GCM,
161 ))));
162 registry.register(Box::new(Aes256CbcEncryptor::new(derive_subkey(
163 master_key,
164 LABEL_AES_CBC,
165 ))));
166 registry.register(Box::new(SodiumXChaCha20Encryptor::new(derive_subkey(
167 master_key,
168 LABEL_SODIUM,
169 ))));
170 registry.register(Box::new(Sm4CbcEncryptor::new(derive_subkey_16(
171 master_key, LABEL_SM4,
172 ))));
173 registry.register(Box::new(Zuc128Encryptor::new(derive_subkey_16(
174 master_key, LABEL_ZUC,
175 ))));
176}
177
178fn register_extension_ciphers(
180 registry: &mut Registry<Box<dyn SymmetricCipher>>,
181 master_key: &[u8; 32],
182) {
183 registry.register(Box::new(ChaCha20Poly1305IetfEncryptor::new(
184 derive_subkey_ext(master_key, LABEL_CHACHA20_POLY1305_IETF),
185 )));
186 registry.register(Box::new(Aes256GcmSivEncryptor::new(derive_subkey_ext(
187 master_key,
188 LABEL_AES_GCM_SIV,
189 ))));
190 registry.register(Box::new(Camellia256CbcEncryptor::new(derive_subkey_ext(
191 master_key,
192 LABEL_CAMELLIA,
193 ))));
194 registry.register(Box::new(Aria256CbcEncryptor::new(derive_subkey_ext(
195 master_key, LABEL_ARIA,
196 ))));
197 registry.register(Box::new(Threefish512CbcEncryptor::new(derive_subkey_ext(
198 master_key,
199 LABEL_THREEFISH,
200 ))));
201 registry.register(Box::new(Kuznyechik256CbcEncryptor::new(derive_subkey_ext(
202 master_key,
203 LABEL_KUZNYECHIK,
204 ))));
205 registry.register(Box::new(Salsa20Encryptor::new(derive_subkey_ext(
206 master_key,
207 LABEL_SALSA20,
208 ))));
209 registry.register(Box::new(Sm4GcmEncryptor::new(derive_subkey_ext(
210 master_key,
211 LABEL_SM4_GCM,
212 ))));
213 registry.register(Box::new(Zuc256Encryptor::new(derive_subkey_ext(
214 master_key,
215 LABEL_ZUC256,
216 ))));
217}
218
219fn derive_subkey(master_key: &[u8; 32], label: &[u8]) -> [u8; 32] {
221 let mut mac = Hmac::<Sha256>::new_from_slice(master_key).expect("HMAC accepts any key size");
222 mac.update(label);
223 mac.finalize().into_bytes().into()
224}
225
226fn derive_subkey_16(master_key: &[u8; 32], label: &[u8]) -> [u8; 16] {
228 let full = Zeroizing::new(derive_subkey(master_key, label));
229 full[..16].try_into().expect("slice is exactly 16 bytes")
230}
231
232fn derive_subkey_ext<const N: usize>(master_key: &[u8; 32], label: &[u8]) -> [u8; N] {
235 let prk = Zeroizing::new(derive_subkey(master_key, LABEL_PRK_EXT));
236 let hkdf = Hkdf::<Sha256>::from_prk(prk.as_slice()).expect("PRK is 32 bytes (= HashLen)");
237
238 let mut subkey = [0u8; N];
239 hkdf.expand(label, &mut subkey)
240 .expect("N in {16, 32, 64} is far below the HKDF limit of 255 * 32");
241 subkey
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 fn hex(bytes: &[u8]) -> String {
249 crate::internal::hex_encode(bytes)
250 }
251
252 #[test]
253 fn factory_validates_master_key_and_default() {
254 assert_eq!(
255 EncryptionManagerFactory::from_master_key(&[0u8; 31], "aes-256-gcm").unwrap_err(),
256 Error::InvalidMasterKey { got: 31 }
257 );
258 assert_eq!(
259 EncryptionManagerFactory::from_master_key(&[0u8; 32], "aes-512").unwrap_err(),
260 Error::UnknownIdentifier {
261 kind: "symmetric cipher",
262 identifier: "aes-512".to_string()
263 }
264 );
265 }
266
267 #[test]
268 fn factory_registers_all_five_and_they_roundtrip() {
269 let manager =
270 EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
271 let expected = [
272 "aes-256-cbc-hmac",
273 "aes-256-gcm",
274 "sm4-cbc",
275 "sodium-xchacha20",
276 "zuc-128",
277 ];
278 assert_eq!(manager.registry().identifiers(), expected);
279
280 for identifier in expected {
281 let blob = manager.encrypt_with(identifier, b"cross-check").unwrap();
282 assert_eq!(
283 manager.decrypt_with(identifier, &blob).unwrap(),
284 b"cross-check"
285 );
286 }
287 }
288
289 #[test]
290 fn subkeys_are_independent_per_algorithm() {
291 let manager =
292 EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
293 let blob = manager.encrypt_with("aes-256-gcm", b"secret").unwrap();
294 assert!(manager.decrypt_with("aes-256-cbc-hmac", &blob).is_err());
296 assert!(manager.decrypt_with("sm4-cbc", &blob).is_err());
297 }
298
299 #[test]
305 fn legacy_subkeys_are_frozen() {
306 let master = [42u8; 32];
307 let cases: [(&[u8], &str); 5] = [
308 (
309 LABEL_AES_GCM,
310 "8c40365a67198f254b99a3e59b5da02762b2368432e436807d52646764709efa",
311 ),
312 (
313 LABEL_AES_CBC,
314 "e2b808949cc38e4be732924c88c56738a607cbd63e1e6a81ff89cada485da4b5",
315 ),
316 (
317 LABEL_SODIUM,
318 "24f1543b4d2d3a48c835f3d10bd758e7ad94f55fed8b0fb64fa5e29fa5ff834d",
319 ),
320 (
321 LABEL_SM4,
322 "a0a1e7c2645d1eed02f2fc43eca98992009424fb80e8314b5218be6f4b831f97",
323 ),
324 (
325 LABEL_ZUC,
326 "e2ac939b959fff17dd6d7a1df705b5e00840364974c0482c30419661f4195b88",
327 ),
328 ];
329 for (label, expected) in cases {
330 assert_eq!(
331 hex(&derive_subkey(&master, label)),
332 expected,
333 "label {}",
334 String::from_utf8_lossy(label)
335 );
336 }
337 assert_eq!(
339 hex(&derive_subkey_16(&master, LABEL_SM4)),
340 "a0a1e7c2645d1eed02f2fc43eca98992"
341 );
342 assert_eq!(
343 hex(&derive_subkey_16(&master, LABEL_ZUC)),
344 "e2ac939b959fff17dd6d7a1df705b5e0"
345 );
346 }
347
348 #[test]
352 fn extension_subkeys_are_frozen() {
353 let master = [42u8; 32];
354 assert_eq!(
355 hex(&derive_subkey(&master, LABEL_PRK_EXT)),
356 "d13e7885aa737fe03fd1aff45bfee04971cb42a65c6d83df6c81251b4a48e76a"
357 );
358
359 assert_eq!(
360 hex(&derive_subkey_ext::<32>(
361 &master,
362 LABEL_CHACHA20_POLY1305_IETF
363 )),
364 "b72beef1f703fbc67add7c0045129406a7e33bed2f44bf1ba5cfdbca46983f3c"
365 );
366 assert_eq!(
367 hex(&derive_subkey_ext::<32>(&master, LABEL_AES_GCM_SIV)),
368 "4ea65b0fec0a961de6c8f4a3a4e1d4a1a422d8e494ed976d51c6136ab3c65d84"
369 );
370 assert_eq!(
371 hex(&derive_subkey_ext::<32>(&master, LABEL_CAMELLIA)),
372 "64fc1e0b8ba07c51e5b3bcfc43be3d370f1d26276957d0b07144f6e3b12bbf21"
373 );
374 assert_eq!(
375 hex(&derive_subkey_ext::<32>(&master, LABEL_ARIA)),
376 "ae167985194b12afb4ee564310e23573767f06e8ac12967914b9ad651ef88b34"
377 );
378 assert_eq!(
379 hex(&derive_subkey_ext::<64>(&master, LABEL_THREEFISH)),
380 concat!(
381 "951afa424d5221c2de5f5f7f9be38e23",
382 "1eb96fc1842c25b04804c8cc50b7f131",
383 "4849ff963922f5ec93d127536f52c310",
384 "1920c5477ad5f04ad8ad122efd4c3b87"
385 )
386 );
387 assert_eq!(
388 hex(&derive_subkey_ext::<32>(&master, LABEL_KUZNYECHIK)),
389 "20825902f56afe01bc183dce96fd7d8f4a68d7e9854be51a73b9d5d1c3e3d887"
390 );
391 assert_eq!(
392 hex(&derive_subkey_ext::<32>(&master, LABEL_SALSA20)),
393 "c059a09ff49bb3c6916beb350354091b27f80c4708da3ae7d17364f1d7a6898b"
394 );
395 assert_eq!(
396 hex(&derive_subkey_ext::<16>(&master, LABEL_SM4_GCM)),
397 "3b02d8456195d02a514fdbeb5d5ff8a8"
398 );
399 assert_eq!(
400 hex(&derive_subkey_ext::<32>(&master, LABEL_ZUC256)),
401 "832ef3816ff4fde09aecce478f817d2ff878000567ffb7c7a585c21071775a4c"
402 );
403 }
404
405 #[test]
407 fn labels_and_subkeys_are_distinct() {
408 let master = [42u8; 32];
409 let legacy_labels = [
410 LABEL_AES_GCM,
411 LABEL_AES_CBC,
412 LABEL_SM4,
413 LABEL_ZUC,
414 LABEL_SODIUM,
415 ];
416 let ext_labels = [
417 LABEL_CHACHA20_POLY1305_IETF,
418 LABEL_AES_GCM_SIV,
419 LABEL_CAMELLIA,
420 LABEL_ARIA,
421 LABEL_THREEFISH,
422 LABEL_KUZNYECHIK,
423 LABEL_SALSA20,
424 LABEL_SM4_GCM,
425 LABEL_ZUC256,
426 ];
427
428 let mut labels: Vec<&[u8]> = legacy_labels.to_vec();
429 labels.extend(ext_labels.iter().copied());
430 for (i, first) in labels.iter().enumerate() {
431 for second in &labels[i + 1..] {
432 assert_ne!(first, second, "用途标签必须互不重名");
433 }
434 }
435
436 let mut subkeys: Vec<Vec<u8>> = legacy_labels
437 .iter()
438 .map(|label| derive_subkey(&master, label).to_vec())
439 .collect();
440 subkeys.extend(
441 ext_labels
442 .iter()
443 .map(|label| derive_subkey_ext::<64>(&master, label).to_vec()),
444 );
445 assert_eq!(subkeys.len(), 14);
446 for (i, first) in subkeys.iter().enumerate() {
447 for second in &subkeys[i + 1..] {
448 assert_ne!(
450 &first[..16],
451 &second[..16],
452 "任一算法的子密钥前缀都不得与其他算法相同"
453 );
454 }
455 }
456 }
457
458 #[test]
460 fn all_fourteen_roundtrip_and_validate() {
461 assert_eq!(
462 EncryptionManagerFactory::from_master_key_all(&[0u8; 31], "aes-256-gcm").unwrap_err(),
463 Error::InvalidMasterKey { got: 31 }
464 );
465 assert_eq!(
466 EncryptionManagerFactory::from_master_key_all(&[0u8; 32], "aes-512").unwrap_err(),
467 Error::UnknownIdentifier {
468 kind: "symmetric cipher",
469 identifier: "aes-512".to_string()
470 }
471 );
472
473 let manager =
474 EncryptionManagerFactory::from_master_key_all(&[42u8; 32], "aes-256-gcm").unwrap();
475 let expected = [
476 "aes-256-cbc-hmac",
477 "aes-256-gcm",
478 "aes-256-gcm-siv",
479 "aria-256-cbc-hmac",
480 "camellia-256-cbc-hmac",
481 "chacha20-poly1305-ietf",
482 "kuznyechik-256-cbc-hmac",
483 "salsa20",
484 "sm4-cbc",
485 "sm4-gcm",
486 "sodium-xchacha20",
487 "threefish-512-cbc-hmac",
488 "zuc-128",
489 "zuc-256",
490 ];
491 assert_eq!(manager.registry().identifiers(), expected);
492
493 for identifier in expected {
494 let blob = manager.encrypt_with(identifier, b"cross-check").unwrap();
495 assert_eq!(
496 manager.decrypt_with(identifier, &blob).unwrap(),
497 b"cross-check",
498 "{identifier}"
499 );
500 }
501 }
502
503 #[test]
506 fn legacy_subkeys_are_unchanged_when_extensions_are_added() {
507 let five = EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
508 let all =
509 EncryptionManagerFactory::from_master_key_all(&[42u8; 32], "aes-256-gcm").unwrap();
510
511 for identifier in five.registry().identifiers() {
512 let from_five = five.encrypt_with(identifier, b"shared subkey").unwrap();
513 assert_eq!(
514 all.decrypt_with(identifier, &from_five).unwrap(),
515 b"shared subkey",
516 "{identifier}"
517 );
518 let from_all = all.encrypt_with(identifier, b"shared subkey").unwrap();
519 assert_eq!(
520 five.decrypt_with(identifier, &from_all).unwrap(),
521 b"shared subkey",
522 "{identifier}"
523 );
524 }
525 }
526
527 #[test]
530 fn register_extensions_composes_into_existing_registry() {
531 assert_eq!(
532 EncryptionManagerFactory::register_extensions(
533 &mut Registry::new("symmetric cipher"),
534 &[0u8; 16]
535 )
536 .unwrap_err(),
537 Error::InvalidMasterKey { got: 16 }
538 );
539
540 let mut manager =
541 EncryptionManagerFactory::from_master_key(&[42u8; 32], "aes-256-gcm").unwrap();
542 EncryptionManagerFactory::register_extensions(manager.registry_mut(), &[42u8; 32]).unwrap();
543 assert_eq!(manager.registry().identifiers().len(), 14);
544
545 let all =
546 EncryptionManagerFactory::from_master_key_all(&[42u8; 32], "aes-256-gcm").unwrap();
547 assert_eq!(
548 manager.registry().identifiers(),
549 all.registry().identifiers()
550 );
551
552 for identifier in manager.registry().identifiers() {
554 let composed = manager.encrypt_with(identifier, b"composed").unwrap();
555 assert_eq!(
556 all.decrypt_with(identifier, &composed).unwrap(),
557 b"composed",
558 "{identifier}"
559 );
560 }
561 }
562}