Skip to main content

shadow_crypt_core/v3/
key.rs

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/// Argon2id parameters for XChacha20-Poly1305 key derivation.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct KeyDerivationParams {
12    pub memory_cost: u32, // Memory cost in kibibytes (KiB)
13    pub time_cost: u32,   // Number of iterations
14    pub parallelism: u32, // Number of parallel threads
15    pub key_size: u8,     // Output key size in bytes
16}
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    /// Standard-profile Argon2id parameters: the OWASP Password Storage
29    /// Cheat Sheet's recommended configuration with the highest memory
30    /// hardness of the equivalent set.
31    ///
32    /// - Memory Cost: 47,104 KiB (46 MiB)
33    /// - Time Cost: 1 iteration
34    /// - Parallelism: 1 thread
35    /// - Key Size: 32 bytes (256 bits)
36    pub fn standard_defaults() -> Self {
37        Self {
38            memory_cost: 46 * 1024, // 47,104 KiB (46 MiB)
39            time_cost: 1,           // 1 iteration
40            parallelism: 1,         // 1 thread
41            key_size: 32,           // 32 bytes (256 bits)
42        }
43    }
44
45    /// Paranoid-profile Argon2id parameters for high-value archives.
46    ///
47    /// - Memory Cost: 1,048,576 KiB (1 GiB)
48    /// - Time Cost: 10 iterations
49    /// - Parallelism: 4 threads
50    /// - Key Size: 32 bytes (256 bits)
51    pub fn paranoid_defaults() -> Self {
52        Self {
53            memory_cost: 1024 * 1024, // 1,048,576 KiB (1 GiB)
54            time_cost: 10,            // 10 iterations
55            parallelism: 4,           // 4 threads
56            key_size: 32,             // 32 bytes (256 bits)
57        }
58    }
59
60    /// Test defaults for Argon2id parameters.
61    ///
62    /// - Memory Cost: 1,024 KiB (1 MiB)
63    /// - Time Cost: 1 iteration
64    /// - Parallelism: 1 thread
65    /// - Key Size: 32 bytes (256 bits)
66    pub fn test_defaults() -> Self {
67        Self {
68            memory_cost: 1024, // 1,024 KiB (1 MiB)
69            time_cost: 1,      // 1 iteration
70            parallelism: 1,    // 1 thread
71            key_size: 32,      // 32 bytes (256 bits)
72        }
73    }
74
75    /// Derives an encryption key from a password and salt with Argon2id,
76    /// using these parameters.
77    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; // Version 19
85        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(); // Clear sensitive data from memory
105
106        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}