1use argon2::{Algorithm, Argon2, Params, Version};
2use zeroize::Zeroize;
3
4use crate::{
5 errors::KeyDerivationError, memory::SecureKey, profile::SecurityProfile,
6 report::KeyDerivationReport,
7};
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct KeyDerivationParams {
12 pub memory_cost: u32, pub time_cost: u32, pub parallelism: u32, pub key_size: u8, }
17
18impl KeyDerivationParams {
19 pub fn new(memory_cost: u32, time_cost: u32, parallelism: u32, key_size: u8) -> Self {
20 Self {
21 memory_cost,
22 time_cost,
23 parallelism,
24 key_size,
25 }
26 }
27
28 pub fn standard_defaults() -> Self {
37 Self {
38 memory_cost: 46 * 1024, time_cost: 1, parallelism: 1, key_size: 32, }
43 }
44
45 pub fn paranoid_defaults() -> Self {
52 Self {
53 memory_cost: 1024 * 1024, time_cost: 10, parallelism: 4, key_size: 32, }
58 }
59
60 pub fn test_defaults() -> Self {
67 Self {
68 memory_cost: 1024, time_cost: 1, parallelism: 1, key_size: 32, }
73 }
74
75 pub fn derive_key(
78 &self,
79 password: &[u8],
80 salt: &[u8],
81 ) -> Result<(SecureKey, KeyDerivationReport), KeyDerivationError> {
82 let start_time = std::time::Instant::now();
83 let algorithm = Algorithm::Argon2id;
84 let version = Version::V0x13; let params = Params::new(
86 self.memory_cost,
87 self.time_cost,
88 self.parallelism,
89 Some(self.key_size as usize),
90 )
91 .map_err(|e| {
92 KeyDerivationError::InvalidParameters(format!("Invalid KDF parameters: {}", e))
93 })?;
94 let context = Argon2::new(algorithm, version, params);
95
96 let mut buffer = [0u8; 32];
97 context
98 .hash_password_into(password, salt, &mut buffer)
99 .map_err(|e| {
100 KeyDerivationError::DerivationFailed(format!("Key derivation failed: {}", e))
101 })?;
102
103 let key = SecureKey::new(buffer);
104 buffer.zeroize(); let duration = start_time.elapsed();
107 let report = KeyDerivationReport::new(
108 "Argon2id".to_string(),
109 format!("{}", version as u8),
110 self.memory_cost,
111 self.time_cost,
112 self.parallelism,
113 self.key_size,
114 duration,
115 );
116
117 Ok((key, report))
118 }
119}
120
121impl From<SecurityProfile> for KeyDerivationParams {
122 fn from(profile: SecurityProfile) -> Self {
123 match profile {
124 SecurityProfile::Standard => KeyDerivationParams::standard_defaults(),
125 SecurityProfile::Paranoid => KeyDerivationParams::paranoid_defaults(),
126 SecurityProfile::Test => KeyDerivationParams::test_defaults(),
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn test_key_derivation_params_defaults() {
137 let standard = KeyDerivationParams::standard_defaults();
138 assert_eq!(standard.memory_cost, 46 * 1024);
139 assert_eq!(standard.time_cost, 1);
140 assert_eq!(standard.parallelism, 1);
141 assert_eq!(standard.key_size, 32);
142
143 let paranoid = KeyDerivationParams::paranoid_defaults();
144 assert_eq!(paranoid.memory_cost, 1024 * 1024);
145 assert_eq!(paranoid.time_cost, 10);
146 assert_eq!(paranoid.parallelism, 4);
147 assert_eq!(paranoid.key_size, 32);
148
149 let test = KeyDerivationParams::test_defaults();
150 assert_eq!(test.memory_cost, 1024);
151 assert_eq!(test.time_cost, 1);
152 assert_eq!(test.parallelism, 1);
153 assert_eq!(test.key_size, 32);
154 }
155
156 #[test]
157 fn test_from_security_profile() {
158 let standard_params: KeyDerivationParams = SecurityProfile::Standard.into();
159 assert_eq!(standard_params, KeyDerivationParams::standard_defaults());
160
161 let paranoid_params: KeyDerivationParams = SecurityProfile::Paranoid.into();
162 assert_eq!(paranoid_params, KeyDerivationParams::paranoid_defaults());
163
164 let test_params: KeyDerivationParams = SecurityProfile::Test.into();
165 assert_eq!(test_params, KeyDerivationParams::test_defaults());
166 }
167
168 #[test]
169 fn test_derive_key_success() {
170 let params = KeyDerivationParams::test_defaults();
171
172 let (key, report) = params
173 .derive_key(b"test_password", b"test_salt_16_bytes")
174 .unwrap();
175 assert_eq!(key.as_bytes().len(), 32);
176 assert_eq!(report.algorithm, "Argon2id");
177 assert_eq!(report.algorithm_version, "19");
178 assert_eq!(report.memory_cost_kib, params.memory_cost);
179 }
180
181 #[test]
182 fn test_derive_key_deterministic() {
183 let params = KeyDerivationParams::test_defaults();
184
185 let (key1, _) = params
186 .derive_key(b"test_password", b"test_salt_16_bytes")
187 .unwrap();
188 let (key2, _) = params
189 .derive_key(b"test_password", b"test_salt_16_bytes")
190 .unwrap();
191
192 assert_eq!(key1.as_bytes(), key2.as_bytes());
193 }
194
195 #[test]
196 fn test_derive_key_different_passwords() {
197 let params = KeyDerivationParams::test_defaults();
198
199 let (key1, _) = params
200 .derive_key(b"password1", b"test_salt_16_bytes")
201 .unwrap();
202 let (key2, _) = params
203 .derive_key(b"password2", b"test_salt_16_bytes")
204 .unwrap();
205
206 assert_ne!(key1.as_bytes(), key2.as_bytes());
207 }
208
209 #[test]
210 fn test_derive_key_invalid_parameters() {
211 let invalid_params = KeyDerivationParams::new(0, 1, 1, 32);
212 let result = invalid_params.derive_key(b"pw", b"test_salt_16_bytes");
213 assert!(matches!(
214 result,
215 Err(KeyDerivationError::InvalidParameters(_))
216 ));
217 }
218}