envelope_cli/crypto/
key_derivation.rs1use argon2::{
7 password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
8 Argon2, Params,
9};
10use serde::{Deserialize, Serialize};
11
12use crate::error::{EnvelopeError, EnvelopeResult};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct KeyDerivationParams {
17 pub salt: String,
19 pub memory_cost: u32,
21 pub time_cost: u32,
23 pub parallelism: u32,
25}
26
27impl Default for KeyDerivationParams {
28 fn default() -> Self {
29 Self {
30 salt: String::new(), memory_cost: 65536, time_cost: 3,
33 parallelism: 4,
34 }
35 }
36}
37
38impl KeyDerivationParams {
39 pub fn new() -> Self {
41 let salt = SaltString::generate(&mut OsRng);
42 Self {
43 salt: salt.to_string(),
44 ..Default::default()
45 }
46 }
47
48 pub fn with_values(salt: String, memory_cost: u32, time_cost: u32, parallelism: u32) -> Self {
50 Self {
51 salt,
52 memory_cost,
53 time_cost,
54 parallelism,
55 }
56 }
57}
58
59pub struct DerivedKey {
61 key: [u8; 32],
63}
64
65impl DerivedKey {
66 pub fn as_bytes(&self) -> &[u8; 32] {
68 &self.key
69 }
70}
71
72impl Drop for DerivedKey {
73 fn drop(&mut self) {
74 self.key.iter_mut().for_each(|b| *b = 0);
76 }
77}
78
79pub fn derive_key(passphrase: &str, params: &KeyDerivationParams) -> EnvelopeResult<DerivedKey> {
81 let salt = SaltString::from_b64(¶ms.salt)
83 .map_err(|e| EnvelopeError::Encryption(format!("Invalid salt: {}", e)))?;
84
85 let argon2_params = Params::new(
87 params.memory_cost,
88 params.time_cost,
89 params.parallelism,
90 Some(32), )
92 .map_err(|e| EnvelopeError::Encryption(format!("Invalid Argon2 parameters: {}", e)))?;
93
94 let argon2 = Argon2::new(
95 argon2::Algorithm::Argon2id,
96 argon2::Version::V0x13,
97 argon2_params,
98 );
99
100 let hash = argon2
102 .hash_password(passphrase.as_bytes(), &salt)
103 .map_err(|e| EnvelopeError::Encryption(format!("Key derivation failed: {}", e)))?;
104
105 let hash_output = hash
107 .hash
108 .ok_or_else(|| EnvelopeError::Encryption("No hash output generated".to_string()))?;
109
110 let hash_bytes = hash_output.as_bytes();
111
112 if hash_bytes.len() < 32 {
113 return Err(EnvelopeError::Encryption(
114 "Hash output too short for AES-256 key".to_string(),
115 ));
116 }
117
118 let mut key = [0u8; 32];
119 key.copy_from_slice(&hash_bytes[..32]);
120
121 Ok(DerivedKey { key })
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_derive_key() {
130 let params = KeyDerivationParams::new();
131 let key = derive_key("test_passphrase", ¶ms).unwrap();
132 assert_eq!(key.as_bytes().len(), 32);
133 }
134
135 #[test]
136 fn test_same_passphrase_same_key() {
137 let params = KeyDerivationParams::new();
138 let key1 = derive_key("test_passphrase", ¶ms).unwrap();
139 let key2 = derive_key("test_passphrase", ¶ms).unwrap();
140 assert_eq!(key1.as_bytes(), key2.as_bytes());
141 }
142
143 #[test]
144 fn test_different_passphrase_different_key() {
145 let params = KeyDerivationParams::new();
146 let key1 = derive_key("passphrase1", ¶ms).unwrap();
147 let key2 = derive_key("passphrase2", ¶ms).unwrap();
148 assert_ne!(key1.as_bytes(), key2.as_bytes());
149 }
150
151 #[test]
152 fn test_different_salt_different_key() {
153 let params1 = KeyDerivationParams::new();
154 let params2 = KeyDerivationParams::new();
155 let key1 = derive_key("same_passphrase", ¶ms1).unwrap();
156 let key2 = derive_key("same_passphrase", ¶ms2).unwrap();
157 assert_ne!(key1.as_bytes(), key2.as_bytes());
159 }
160
161 #[test]
162 fn test_key_zeroized_on_drop() {
163 let params = KeyDerivationParams::new();
164 let key_ptr: *const [u8; 32];
165 {
166 let key = derive_key("test_passphrase", ¶ms).unwrap();
167 key_ptr = key.as_bytes() as *const [u8; 32];
168 }
169 let _ = key_ptr; }
174}