dcrypt_algorithms/kdf/pbkdf2/
mod.rs

1//! Password-Based Key Derivation Function 2 (PBKDF2)
2//!
3//! This module implements PBKDF2 as specified in RFC 8018.
4//! PBKDF2 applies a pseudorandom function (such as HMAC) to the input password
5//! along with a salt value and repeats the process many times to produce a
6//! derived key, which can then be used as a cryptographic key in subsequent operations.
7
8#![cfg_attr(not(feature = "std"), no_std)]
9
10use crate::error::{validate, Error, Result};
11use crate::hash::HashFunction;
12use crate::kdf::common::constant_time_eq;
13use crate::kdf::{KdfAlgorithm, KdfOperation, PasswordHash, SecurityLevel};
14use crate::kdf::{KeyDerivationFunction, ParamProvider, PasswordHashFunction};
15use crate::mac::hmac::Hmac;
16use crate::types::salt::Pbkdf2Compatible;
17use crate::types::{ByteSerializable, Salt, SecretBytes};
18
19// Import security types
20use dcrypt_common::security::SecretVec;
21
22// Conditional imports based on features
23#[cfg(feature = "std")]
24use std::collections::BTreeMap;
25#[cfg(feature = "std")]
26use std::string::String;
27#[cfg(feature = "std")]
28use std::time::{Duration, Instant};
29#[cfg(feature = "std")]
30use std::vec::Vec;
31
32#[cfg(all(feature = "alloc", not(feature = "std")))]
33use alloc::collections::BTreeMap;
34#[cfg(all(feature = "alloc", not(feature = "std")))]
35use alloc::string::String;
36#[cfg(all(feature = "alloc", not(feature = "std")))]
37use alloc::vec::Vec;
38
39#[cfg(not(feature = "std"))]
40use core::time::Duration;
41
42use rand::{CryptoRng, RngCore};
43use std::marker::PhantomData;
44use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
45
46/// Type-level constants for PBKDF2 algorithm
47pub enum Pbkdf2Algorithm<H: HashFunction> {
48    /// Phantom field for the hash function
49    _Hash(PhantomData<H>),
50}
51
52impl<H: HashFunction> KdfAlgorithm for Pbkdf2Algorithm<H> {
53    const MIN_SALT_SIZE: usize = 16;
54    const DEFAULT_OUTPUT_SIZE: usize = 32;
55    const ALGORITHM_ID: &'static str = "PBKDF2";
56
57    fn name() -> String {
58        format!("{}-{}", Self::ALGORITHM_ID, H::name())
59    }
60
61    fn security_level() -> SecurityLevel {
62        // PBKDF2 security depends on the underlying hash
63        match H::output_size() * 8 {
64            bits if bits >= 512 => SecurityLevel::L128, // Conservative estimate
65            bits if bits >= 384 => SecurityLevel::L128,
66            bits if bits >= 256 => SecurityLevel::L128,
67            bits => SecurityLevel::Custom(bits as u32 / 2),
68        }
69    }
70}
71
72/// Parameters for PBKDF2
73#[derive(Clone, Debug, Zeroize)]
74pub struct Pbkdf2Params<const S: usize = 16> {
75    /// Salt value
76    pub salt: Salt<S>,
77
78    /// Number of iterations
79    pub iterations: u32,
80
81    /// Length of derived key in bytes
82    pub key_length: usize,
83}
84
85impl<const S: usize> Default for Pbkdf2Params<S>
86where
87    Salt<S>: Pbkdf2Compatible,
88{
89    fn default() -> Self {
90        Self {
91            salt: Salt::<S>::zeroed(), // Will be filled with random data during initialization
92            iterations: 600_000,       // OWASP recommended minimum as of 2023
93            key_length: 32,            // 256 bits
94        }
95    }
96}
97
98/// PBKDF2 implementation using any HMAC-based PRF
99///
100/// PBKDF2 can be used with any pseudorandom function, but this implementation
101/// uses HMAC with a configurable hash function.
102#[derive(Clone, Zeroize, ZeroizeOnDrop)]
103pub struct Pbkdf2<H: HashFunction + Clone, const S: usize = 16> {
104    /// The hash function type
105    _hash_type: PhantomData<H>,
106
107    /// PBKDF2 parameters
108    params: Pbkdf2Params<S>,
109}
110
111/// PBKDF2 builder implementation
112pub struct Pbkdf2Builder<'a, H: HashFunction + Clone, const S: usize = 16> {
113    kdf: &'a Pbkdf2<H, S>,
114    ikm: Option<&'a [u8]>,
115    salt: Option<&'a [u8]>,
116    iterations: u32,
117    length: usize,
118}
119
120// FIXED: Elided lifetime in impl block
121impl<H: HashFunction + Clone, const S: usize> Pbkdf2Builder<'_, H, S> {
122    /// Set the number of iterations
123    pub fn with_iterations(mut self, iterations: u32) -> Self {
124        self.iterations = iterations;
125        self
126    }
127}
128
129impl<'a, H: HashFunction + Clone, const S: usize> KdfOperation<'a, Pbkdf2Algorithm<H>>
130    for Pbkdf2Builder<'a, H, S>
131where
132    Salt<S>: Pbkdf2Compatible,
133{
134    fn with_ikm(mut self, ikm: &'a [u8]) -> Self {
135        self.ikm = Some(ikm);
136        self
137    }
138
139    fn with_salt(mut self, salt: &'a [u8]) -> Self {
140        self.salt = Some(salt);
141        self
142    }
143
144    fn with_info(self, _info: &'a [u8]) -> Self {
145        // PBKDF2 doesn't use info, but we implement for API compatibility
146        self
147    }
148
149    fn with_output_length(mut self, length: usize) -> Self {
150        self.length = length;
151        self
152    }
153
154    fn derive(self) -> Result<Vec<u8>> {
155        let ikm = self.ikm.ok_or_else(|| {
156            Error::param("input_keying_material", "Input keying material is required")
157        })?;
158
159        let salt = match self.salt {
160            Some(s) => s,
161            None => self.kdf.params.salt.as_ref(),
162        };
163
164        // Use PBKDF2 with secure key handling
165        Pbkdf2::<H, S>::pbkdf2_secure(ikm, salt, self.iterations, self.length)
166    }
167
168    fn derive_array<const N: usize>(self) -> Result<[u8; N]> {
169        // Ensure the requested size matches
170        validate::length("PBKDF2 output", self.length, N)?;
171
172        let vec = self.derive()?;
173
174        // Convert to fixed-size array
175        let mut array = [0u8; N];
176        array.copy_from_slice(&vec);
177        Ok(array)
178    }
179}
180
181impl<H: HashFunction + Clone, const S: usize> Pbkdf2<H, S> {
182    /// Internal PBKDF2 implementation with secure key handling
183    ///
184    /// This implements the core PBKDF2 algorithm as defined in RFC 8018 Section 5.2
185    /// with enhanced security for key material handling.
186    ///
187    /// # Arguments
188    /// * `password` - The password to derive the key from
189    /// * `salt` - The salt value
190    /// * `iterations` - The number of iterations
191    /// * `key_length` - The length of the derived key in bytes
192    ///
193    /// # Returns
194    /// The derived key of length key_length bytes
195    pub fn pbkdf2(
196        password: &[u8],
197        salt: &[u8],
198        iterations: u32,
199        key_length: usize,
200    ) -> Result<Zeroizing<Vec<u8>>> {
201        // Wrap password in secure buffer for internal operations
202        let secure_password = SecretVec::from_slice(password);
203        Self::pbkdf2_internal(&secure_password, salt, iterations, key_length)
204    }
205
206    /// Secure PBKDF2 implementation that returns regular Vec
207    pub fn pbkdf2_secure(
208        password: &[u8],
209        salt: &[u8],
210        iterations: u32,
211        key_length: usize,
212    ) -> Result<Vec<u8>> {
213        let result = Self::pbkdf2(password, salt, iterations, key_length)?;
214        Ok(result.to_vec())
215    }
216
217    /// Internal PBKDF2 implementation using secure types
218    fn pbkdf2_internal(
219        password: &SecretVec,
220        salt: &[u8],
221        iterations: u32,
222        key_length: usize,
223    ) -> Result<Zeroizing<Vec<u8>>> {
224        // Strict parameter validation
225        validate::parameter(
226            iterations > 0,
227            "iterations",
228            "PBKDF2 iteration count must be > 0",
229        )?;
230
231        validate::parameter(
232            key_length > 0,
233            "key_length",
234            "PBKDF2 output length must be > 0",
235        )?;
236
237        let hash_len = H::output_size();
238
239        // Calculate how many blocks we need to generate - FIXED: Using div_ceil
240        let block_count = key_length.div_ceil(hash_len);
241
242        // Check that the output length is not too large
243        // RFC 8018 section 5.2 states that the maximum output length is (2^32 - 1) * hash_len
244        if block_count > 0xFFFFFFFF {
245            return Err(Error::Length {
246                context: "PBKDF2 output length",
247                expected: 0xFFFFFFFF * hash_len,
248                actual: key_length,
249            });
250        }
251
252        let mut result = Zeroizing::new(Vec::with_capacity(key_length));
253
254        // Derive each block of the output
255        // Each block is calculated independently using the F function
256        for block_index in 1..=block_count {
257            let block =
258                Self::pbkdf2_f::<H>(password.as_ref(), salt, iterations, block_index as u32)?;
259
260            // Determine how much of this block to use
261            // Most blocks are used completely, but the last one might be partial
262            let to_copy = if block_index == block_count {
263                let remainder = key_length % hash_len;
264                if remainder == 0 {
265                    hash_len
266                } else {
267                    remainder
268                }
269            } else {
270                hash_len
271            };
272
273            // Append the needed bytes to the result
274            result.extend_from_slice(&block[..to_copy]);
275        }
276
277        Ok(result)
278    }
279
280    /// F function for PBKDF2 as defined in RFC 8018
281    ///
282    /// This function applies the pseudorandom function (PRF) iteratively and
283    /// combines the results by XOR.
284    ///
285    /// Computes F(P, S, c, i) = U_1 XOR U_2 XOR ... XOR U_c
286    /// where U_1 = PRF(P, S || INT_32_BE(i))
287    ///       U_j = PRF(P, U_{j-1})
288    fn pbkdf2_f<T: HashFunction + Clone>(
289        password: &[u8],
290        salt: &[u8],
291        iterations: u32,
292        block_index: u32,
293    ) -> Result<Zeroizing<Vec<u8>>> {
294        // First iteration: HMAC(password, salt || block_index)
295        // U_1 = PRF(P, S || INT_32_BE(i))
296        let mut hmac = Hmac::<T>::new(password)?;
297        hmac.update(salt)?;
298        hmac.update(&block_index.to_be_bytes())?;
299        let result = Zeroizing::new(hmac.finalize()?);
300
301        let mut prev = result.clone();
302
303        // Subsequent iterations: HMAC(password, prev_result)
304        // U_j = PRF(P, U_{j-1})
305        // Combine results by XOR: U_1 XOR U_2 XOR ... XOR U_c
306        let mut output = Zeroizing::new(result.to_vec());
307
308        for _ in 1..iterations {
309            let mut hmac = Hmac::<T>::new(password)?;
310            hmac.update(&prev)?;
311            prev = Zeroizing::new(hmac.finalize()?);
312
313            // XOR the result with prev
314            for i in 0..output.len() {
315                output[i] ^= prev[i];
316            }
317        }
318
319        Ok(output)
320    }
321}
322
323impl<H: HashFunction + Clone, const S: usize> ParamProvider for Pbkdf2<H, S> {
324    type Params = Pbkdf2Params<S>;
325
326    fn with_params(params: Self::Params) -> Self {
327        Self {
328            _hash_type: PhantomData,
329            params,
330        }
331    }
332
333    fn params(&self) -> &Self::Params {
334        &self.params
335    }
336
337    fn set_params(&mut self, params: Self::Params) {
338        self.params = params;
339    }
340}
341
342impl<H: HashFunction + Clone, const S: usize> KeyDerivationFunction for Pbkdf2<H, S>
343where
344    Salt<S>: Pbkdf2Compatible,
345{
346    type Algorithm = Pbkdf2Algorithm<H>;
347    type Salt = Salt<S>;
348
349    fn new() -> Self {
350        Self {
351            _hash_type: PhantomData,
352            params: Pbkdf2Params::default(),
353        }
354    }
355
356    #[cfg(feature = "alloc")]
357    fn derive_key(
358        &self,
359        input: &[u8],
360        salt: Option<&[u8]>,
361        _info: Option<&[u8]>,
362        length: usize,
363    ) -> Result<Vec<u8>> {
364        // Use provided salt or fallback to default from params - FIXED: Removed needless borrow
365        let effective_salt = match salt {
366            Some(s) => s,
367            None => self.params.salt.as_ref(),
368        };
369
370        // Use provided length or fallback to default from params
371        let effective_length = if length > 0 {
372            length
373        } else {
374            self.params.key_length
375        };
376
377        // Use the secure version
378        Self::pbkdf2_secure(
379            input,
380            effective_salt,
381            self.params.iterations,
382            effective_length,
383        )
384    }
385
386    // FIXED: Elided lifetime
387    fn builder(&self) -> impl KdfOperation<'_, Self::Algorithm> {
388        Pbkdf2Builder {
389            kdf: self,
390            ikm: None,
391            salt: None,
392            iterations: self.params.iterations,
393            length: self.params.key_length,
394        }
395    }
396
397    fn generate_salt<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Salt {
398        Salt::random_with_size(rng, Self::Algorithm::MIN_SALT_SIZE).expect("Salt generation failed")
399    }
400
401    fn security_level() -> SecurityLevel {
402        Self::Algorithm::security_level()
403    }
404}
405
406#[cfg(feature = "std")]
407impl<H: HashFunction + Clone, const S: usize> PasswordHashFunction for Pbkdf2<H, S>
408where
409    Salt<S>: Pbkdf2Compatible,
410{
411    type Password = SecretBytes<32>; // Using a 32-byte buffer for passwords
412
413    fn hash_password(&self, password: &Self::Password) -> Result<PasswordHash> {
414        // Derive the key using secure implementation
415        let hash = Self::pbkdf2(
416            password.as_ref(),
417            self.params.salt.as_ref(),
418            self.params.iterations,
419            self.params.key_length,
420        )?;
421
422        // Create parameters map
423        let mut params = BTreeMap::new();
424        params.insert("i".to_string(), self.params.iterations.to_string());
425
426        Ok(PasswordHash {
427            algorithm: format!("pbkdf2-{}", H::name().to_lowercase()),
428            params,
429            salt: Zeroizing::new(self.params.salt.to_bytes()),
430            hash,
431        })
432    }
433
434    fn verify(&self, password: &Self::Password, hash: &PasswordHash) -> Result<bool> {
435        // Verify the algorithm
436        let expected_alg = format!("pbkdf2-{}", H::name().to_lowercase());
437        validate::parameter(
438            hash.algorithm == expected_alg,
439            "algorithm",
440            "Algorithm mismatch",
441        )?;
442
443        // Get iterations from the hash parameters
444        let iterations = match hash.param("i") {
445            Some(i) => i
446                .parse::<u32>()
447                .map_err(|_| Error::param("iterations", "Invalid iterations parameter"))?,
448            None => return Err(Error::param("iterations", "Missing iterations parameter")),
449        };
450
451        // Derive key with the same parameters, using secure implementation
452        let derived = Self::pbkdf2(password.as_ref(), &hash.salt, iterations, hash.hash.len())?;
453
454        // Compare in constant time
455        Ok(constant_time_eq(&derived, &hash.hash))
456    }
457
458    fn benchmark(&self) -> Duration {
459        let start = Instant::now();
460        let password = SecretBytes::new([0u8; 32]); // Use a dummy password for benchmarking
461
462        // If hash_password fails, we still return a valid Duration
463        // This is acceptable since benchmark is not critical for security
464        match self.hash_password(&password) {
465            Ok(_) => {}
466            Err(_) => {
467                // We could log the error here if we had a logging system
468                // For now, we'll just continue and return the elapsed time
469                // This gives a reasonable approximation even on error
470            }
471        }
472
473        start.elapsed()
474    }
475
476    fn recommended_params(target_duration: Duration) -> Self::Params {
477        // Start with the default parameters
478        let mut params = Pbkdf2Params::default();
479
480        // Create a temporary instance
481        let instance = Self::with_params(params.clone());
482
483        // Measure the current execution time
484        let current_duration = instance.benchmark();
485
486        // Calculate the ratio and adjust iterations
487        let ratio = target_duration.as_secs_f64() / current_duration.as_secs_f64();
488        params.iterations = (params.iterations as f64 * ratio) as u32;
489
490        // Ensure iterations is at least 10,000
491        params.iterations = core::cmp::max(params.iterations, 10_000);
492
493        params
494    }
495}
496
497#[cfg(test)]
498mod tests;