Skip to main content

git_simple_encrypt/crypt/
key.rs

1use 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}