dcrypt_algorithms/kdf/pbkdf2/
mod.rs1#![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
19use dcrypt_common::security::SecretVec;
21
22#[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
46pub enum Pbkdf2Algorithm<H: HashFunction> {
48 _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 match H::output_size() * 8 {
64 bits if bits >= 512 => SecurityLevel::L128, 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#[derive(Clone, Debug, Zeroize)]
74pub struct Pbkdf2Params<const S: usize = 16> {
75 pub salt: Salt<S>,
77
78 pub iterations: u32,
80
81 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(), iterations: 600_000, key_length: 32, }
95 }
96}
97
98#[derive(Clone, Zeroize, ZeroizeOnDrop)]
103pub struct Pbkdf2<H: HashFunction + Clone, const S: usize = 16> {
104 _hash_type: PhantomData<H>,
106
107 params: Pbkdf2Params<S>,
109}
110
111pub 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
120impl<H: HashFunction + Clone, const S: usize> Pbkdf2Builder<'_, H, S> {
122 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 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 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 validate::length("PBKDF2 output", self.length, N)?;
171
172 let vec = self.derive()?;
173
174 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 pub fn pbkdf2(
196 password: &[u8],
197 salt: &[u8],
198 iterations: u32,
199 key_length: usize,
200 ) -> Result<Zeroizing<Vec<u8>>> {
201 let secure_password = SecretVec::from_slice(password);
203 Self::pbkdf2_internal(&secure_password, salt, iterations, key_length)
204 }
205
206 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 fn pbkdf2_internal(
219 password: &SecretVec,
220 salt: &[u8],
221 iterations: u32,
222 key_length: usize,
223 ) -> Result<Zeroizing<Vec<u8>>> {
224 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 let block_count = key_length.div_ceil(hash_len);
241
242 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 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 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 result.extend_from_slice(&block[..to_copy]);
275 }
276
277 Ok(result)
278 }
279
280 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 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 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 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 let effective_salt = match salt {
366 Some(s) => s,
367 None => self.params.salt.as_ref(),
368 };
369
370 let effective_length = if length > 0 {
372 length
373 } else {
374 self.params.key_length
375 };
376
377 Self::pbkdf2_secure(
379 input,
380 effective_salt,
381 self.params.iterations,
382 effective_length,
383 )
384 }
385
386 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>; fn hash_password(&self, password: &Self::Password) -> Result<PasswordHash> {
414 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 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 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 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 let derived = Self::pbkdf2(password.as_ref(), &hash.salt, iterations, hash.hash.len())?;
453
454 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]); match self.hash_password(&password) {
465 Ok(_) => {}
466 Err(_) => {
467 }
471 }
472
473 start.elapsed()
474 }
475
476 fn recommended_params(target_duration: Duration) -> Self::Params {
477 let mut params = Pbkdf2Params::default();
479
480 let instance = Self::with_params(params.clone());
482
483 let current_duration = instance.benchmark();
485
486 let ratio = target_duration.as_secs_f64() / current_duration.as_secs_f64();
488 params.iterations = (params.iterations as f64 * ratio) as u32;
489
490 params.iterations = core::cmp::max(params.iterations, 10_000);
492
493 params
494 }
495}
496
497#[cfg(test)]
498mod tests;