1use argon2::{Algorithm, Argon2, Params, Version};
8use rand::TryRngCore;
9
10use crate::errors::{EnvVaultError, Result};
11
12const SALT_LEN: usize = 32;
14
15const KEY_LEN: usize = 32;
17
18#[derive(Debug, Clone, Copy)]
23pub struct Argon2Params {
24 pub memory_kib: u32,
26 pub iterations: u32,
28 pub parallelism: u32,
30}
31
32impl Default for Argon2Params {
33 fn default() -> Self {
34 Self {
35 memory_kib: 65_536,
36 iterations: 3,
37 parallelism: 4,
38 }
39 }
40}
41
42pub fn derive_master_key(password: &[u8], salt: &[u8]) -> Result<[u8; KEY_LEN]> {
47 derive_master_key_with_params(password, salt, &Argon2Params::default())
48}
49
50const MIN_MEMORY_KIB: u32 = 8_192;
52
53pub fn derive_master_key_with_params(
58 password: &[u8],
59 salt: &[u8],
60 argon2_params: &Argon2Params,
61) -> Result<[u8; KEY_LEN]> {
62 if argon2_params.memory_kib < MIN_MEMORY_KIB {
63 return Err(EnvVaultError::KeyDerivationFailed(format!(
64 "Argon2 memory_kib must be at least {MIN_MEMORY_KIB} (got {})",
65 argon2_params.memory_kib
66 )));
67 }
68 if argon2_params.iterations < 1 {
69 return Err(EnvVaultError::KeyDerivationFailed(
70 "Argon2 iterations must be at least 1".into(),
71 ));
72 }
73 if argon2_params.parallelism < 1 {
74 return Err(EnvVaultError::KeyDerivationFailed(
75 "Argon2 parallelism must be at least 1".into(),
76 ));
77 }
78
79 let params = Params::new(
80 argon2_params.memory_kib,
81 argon2_params.iterations,
82 argon2_params.parallelism,
83 Some(KEY_LEN),
84 )
85 .map_err(|e| EnvVaultError::KeyDerivationFailed(format!("invalid Argon2 params: {e}")))?;
86
87 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
88
89 let mut key = [0u8; KEY_LEN];
90 argon2
91 .hash_password_into(password, salt, &mut key)
92 .map_err(|e| EnvVaultError::KeyDerivationFailed(format!("Argon2id hashing failed: {e}")))?;
93
94 Ok(key)
95}
96
97pub fn generate_salt() -> [u8; SALT_LEN] {
99 let mut salt = [0u8; SALT_LEN];
100 rand::rngs::OsRng
101 .try_fill_bytes(&mut salt)
102 .expect("OS RNG failed");
103 salt
104}