git_simple_encrypt/crypt/
key.rs1use std::sync::{Arc, OnceLock};
2
3use argon2::Argon2;
4use dashmap::DashMap;
5use zeroize::Zeroizing;
6
7use crate::{
8 crypt::header::{FILE_ID_LEN, NONCE_LEN, SALT_LEN},
9 error::{Error, Result},
10};
11
12pub fn derive_key(password: &[u8], salt: &[u8]) -> Result<Zeroizing<[u8; 32]>> {
13 let mut key = Zeroizing::new([0u8; 32]);
14 Argon2::default()
15 .hash_password_into(password, salt, &mut *key)
16 .map_err(|e| Error::Argon2(e.to_string()))?;
17 Ok(key)
18}
19
20pub(super) fn split_keys(master_key: &[u8; 32]) -> (Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>) {
21 let key_enc = blake3::derive_key("git-simple-encrypt-enc", master_key);
22 let key_mac = blake3::derive_key("git-simple-encrypt-mac", master_key);
23 (Zeroizing::new(key_enc), Zeroizing::new(key_mac))
24}
25
26pub(super) fn derive_nonce(
27 key_mac: &[u8; 32],
28 file_id: &[u8; FILE_ID_LEN],
29 plaintext: &[u8],
30 chunk_idx: u64,
31) -> [u8; NONCE_LEN] {
32 let mut hasher = blake3::Hasher::new_keyed(key_mac);
33 hasher.update(file_id);
34 hasher.update(plaintext);
35 hasher.update(&chunk_idx.to_le_bytes());
36 let hash = hasher.finalize();
37 let mut nonce = [0u8; NONCE_LEN];
38 nonce.copy_from_slice(&hash.as_bytes()[..NONCE_LEN]);
39 nonce
40}
41
42pub(super) type KeyCache =
43 DashMap<[u8; SALT_LEN], Arc<OnceLock<Result<Zeroizing<[u8; 32]>, String>>>>;
44
45pub(super) fn get_or_derive_key(
46 key_cache: &KeyCache,
47 master_key: &[u8],
48 salt: &[u8; SALT_LEN],
49) -> Result<Zeroizing<[u8; 32]>> {
50 let lock = {
51 let guard = key_cache
52 .entry(*salt)
53 .or_insert_with(|| Arc::new(OnceLock::new()));
54 Arc::clone(&*guard)
55 };
56
57 match lock.get_or_init(|| derive_key(master_key, salt).map_err(|e| e.to_string())) {
58 Ok(key) => Ok(key.clone()),
59 Err(msg) => Err(Error::Argon2(msg.clone())),
60 }
61}