dcrypt_algorithms/kdf/pbkdf2/
mod.rs1use crate::error::{validate, Error, Result};
19use crate::hash::HashFunction;
20#[cfg(feature = "std")]
21use crate::kdf::common::constant_time_eq;
22use crate::kdf::{KdfAlgorithm, KdfOperation, KeyDerivationFunction, ParamProvider, SecurityLevel};
23#[cfg(feature = "std")]
24use crate::kdf::{PasswordHash, PasswordHashFunction};
25use crate::mac::hmac::Hmac;
26use crate::types::salt::Pbkdf2Compatible;
27use crate::types::Salt;
28#[cfg(feature = "std")]
29use crate::types::{ByteSerializable, SecretBytes};
30
31use dcrypt_common::security::SecretVec;
33
34#[cfg(feature = "std")]
36use std::collections::BTreeMap;
37#[cfg(feature = "std")]
38use std::string::String;
39#[cfg(feature = "std")]
40use std::time::{Duration, Instant};
41
42#[cfg(all(feature = "alloc", not(feature = "std")))]
43use alloc::string::String;
44
45use core::marker::PhantomData;
46use dcrypt_internal::random::{CryptoRng, RngCore};
47use dcrypt_internal::zeroing::{
48 boxed_bytes_zeroed, Zeroize, ZeroizeOnDrop, Zeroizing, ZeroizingBytes,
49};
50
51pub enum Pbkdf2Algorithm<H: HashFunction> {
53 _Hash(PhantomData<H>),
55}
56
57impl<H: HashFunction> KdfAlgorithm for Pbkdf2Algorithm<H> {
58 const MIN_SALT_SIZE: usize = 16;
59 const DEFAULT_OUTPUT_SIZE: usize = 32;
60 const ALGORITHM_ID: &'static str = "PBKDF2";
61
62 fn name() -> String {
63 format!("{}-{}", Self::ALGORITHM_ID, H::name())
64 }
65
66 fn security_level() -> SecurityLevel {
67 match H::output_size() * 8 {
69 bits if bits >= 512 => SecurityLevel::L128, bits if bits >= 384 => SecurityLevel::L128,
71 bits if bits >= 256 => SecurityLevel::L128,
72 bits => SecurityLevel::Custom(bits as u32 / 2),
73 }
74 }
75}
76
77#[derive(Clone, Debug)]
79pub struct Pbkdf2Params<const S: usize = 16> {
80 pub salt: Salt<S>,
82
83 pub iterations: u32,
85
86 pub key_length: usize,
88}
89
90impl<const S: usize> Zeroize for Pbkdf2Params<S> {
91 fn zeroize(&mut self) {
92 self.salt.zeroize();
93 self.iterations.zeroize();
94 self.key_length.zeroize();
95 }
96}
97
98impl<const S: usize> Default for Pbkdf2Params<S>
99where
100 Salt<S>: Pbkdf2Compatible,
101{
102 fn default() -> Self {
103 Self {
104 salt: Salt::<S>::zeroed(), iterations: 600_000, key_length: 32, }
108 }
109}
110
111#[derive(Clone)]
116pub struct Pbkdf2<H: HashFunction + Clone, const S: usize = 16> {
117 _hash_type: PhantomData<H>,
119
120 params: Pbkdf2Params<S>,
122}
123
124impl<H: HashFunction + Clone, const S: usize> Zeroize for Pbkdf2<H, S> {
125 fn zeroize(&mut self) {
126 self.params.zeroize();
127 }
128}
129
130impl<H: HashFunction + Clone, const S: usize> Drop for Pbkdf2<H, S> {
131 fn drop(&mut self) {
132 self.zeroize();
133 }
134}
135
136impl<H: HashFunction + Clone, const S: usize> ZeroizeOnDrop for Pbkdf2<H, S> {}
137
138pub struct Pbkdf2Builder<'a, H: HashFunction + Clone, const S: usize = 16> {
140 kdf: &'a Pbkdf2<H, S>,
141 ikm: Option<&'a [u8]>,
142 salt: Option<&'a [u8]>,
143 iterations: u32,
144 length: usize,
145}
146
147impl<H: HashFunction + Clone, const S: usize> Pbkdf2Builder<'_, H, S> {
149 pub fn with_iterations(mut self, iterations: u32) -> Self {
151 self.iterations = iterations;
152 self
153 }
154}
155
156impl<'a, H: HashFunction + Clone, const S: usize> KdfOperation<'a, Pbkdf2Algorithm<H>>
157 for Pbkdf2Builder<'a, H, S>
158where
159 Salt<S>: Pbkdf2Compatible,
160{
161 fn with_ikm(mut self, ikm: &'a [u8]) -> Self {
162 self.ikm = Some(ikm);
163 self
164 }
165
166 fn with_salt(mut self, salt: &'a [u8]) -> Self {
167 self.salt = Some(salt);
168 self
169 }
170
171 fn with_info(self, _info: &'a [u8]) -> Self {
172 self
174 }
175
176 fn with_output_length(mut self, length: usize) -> Self {
177 self.length = length;
178 self
179 }
180
181 fn derive(self) -> Result<ZeroizingBytes> {
182 let ikm = self.ikm.ok_or_else(|| {
183 Error::param("input_keying_material", "Input keying material is required")
184 })?;
185
186 let salt = match self.salt {
187 Some(s) => s,
188 None => self.kdf.params.salt.as_ref(),
189 };
190
191 Pbkdf2::<H, S>::pbkdf2_secure(ikm, salt, self.iterations, self.length)
193 }
194
195 fn derive_array<const N: usize>(self) -> Result<Zeroizing<[u8; N]>> {
196 validate::length("PBKDF2 output", self.length, N)?;
198
199 let vec = self.derive()?;
200
201 let mut array = Zeroizing::new([0u8; N]);
203 array.copy_from_slice(&vec);
204 Ok(array)
205 }
206}
207
208impl<H: HashFunction + Clone, const S: usize> Pbkdf2<H, S> {
209 pub fn pbkdf2(
223 password: &[u8],
224 salt: &[u8],
225 iterations: u32,
226 key_length: usize,
227 ) -> Result<ZeroizingBytes> {
228 let secure_password = SecretVec::from_slice(password);
230 Self::pbkdf2_internal(&secure_password, salt, iterations, key_length)
231 }
232
233 pub fn pbkdf2_secure(
235 password: &[u8],
236 salt: &[u8],
237 iterations: u32,
238 key_length: usize,
239 ) -> Result<ZeroizingBytes> {
240 Self::pbkdf2(password, salt, iterations, key_length)
241 }
242
243 fn pbkdf2_internal(
245 password: &SecretVec,
246 salt: &[u8],
247 iterations: u32,
248 key_length: usize,
249 ) -> Result<ZeroizingBytes> {
250 validate::parameter(
252 iterations > 0,
253 "iterations",
254 "PBKDF2 iteration count must be > 0",
255 )?;
256
257 validate::parameter(
258 key_length > 0,
259 "key_length",
260 "PBKDF2 output length must be > 0",
261 )?;
262
263 let hash_len = H::output_size();
264
265 let block_count = key_length.div_ceil(hash_len);
267
268 if block_count > 0xFFFFFFFF {
271 return Err(Error::Length {
272 context: "PBKDF2 output length",
273 expected: 0xFFFFFFFF * hash_len,
274 actual: key_length,
275 });
276 }
277
278 let mut result = Zeroizing::new(boxed_bytes_zeroed(key_length));
279 let mut result_offset = 0usize;
280
281 for block_index in 1..=block_count {
284 let block =
285 Self::pbkdf2_f::<H>(password.as_ref(), salt, iterations, block_index as u32)?;
286
287 let to_copy = if block_index == block_count {
290 let remainder = key_length % hash_len;
291 if remainder == 0 {
292 hash_len
293 } else {
294 remainder
295 }
296 } else {
297 hash_len
298 };
299
300 result[result_offset..result_offset + to_copy].copy_from_slice(&block[..to_copy]);
301 result_offset += to_copy;
302 }
303
304 Ok(result)
305 }
306
307 fn pbkdf2_f<T: HashFunction + Clone>(
316 password: &[u8],
317 salt: &[u8],
318 iterations: u32,
319 block_index: u32,
320 ) -> Result<ZeroizingBytes> {
321 let mut hmac = Hmac::<T>::new(password)?;
324 hmac.update(salt)?;
325 hmac.update(&block_index.to_be_bytes())?;
326 let result = hmac.finalize()?;
327
328 let mut prev = result.clone();
329
330 let mut output = result;
334
335 for _ in 1..iterations {
336 let mut hmac = Hmac::<T>::new(password)?;
337 hmac.update(&prev)?;
338 prev = hmac.finalize()?;
339
340 for i in 0..output.len() {
342 output[i] ^= prev[i];
343 }
344 }
345
346 Ok(output)
347 }
348}
349
350impl<H: HashFunction + Clone, const S: usize> ParamProvider for Pbkdf2<H, S> {
351 type Params = Pbkdf2Params<S>;
352
353 fn with_params(params: Self::Params) -> Self {
354 Self {
355 _hash_type: PhantomData,
356 params,
357 }
358 }
359
360 fn params(&self) -> &Self::Params {
361 &self.params
362 }
363
364 fn set_params(&mut self, params: Self::Params) {
365 self.params = params;
366 }
367}
368
369impl<H: HashFunction + Clone, const S: usize> KeyDerivationFunction for Pbkdf2<H, S>
370where
371 Salt<S>: Pbkdf2Compatible,
372{
373 type Algorithm = Pbkdf2Algorithm<H>;
374 type Salt = Salt<S>;
375
376 fn new() -> Self {
377 Self {
378 _hash_type: PhantomData,
379 params: Pbkdf2Params::default(),
380 }
381 }
382
383 #[cfg(feature = "alloc")]
384 fn derive_key(
385 &self,
386 input: &[u8],
387 salt: Option<&[u8]>,
388 _info: Option<&[u8]>,
389 length: usize,
390 ) -> Result<ZeroizingBytes> {
391 let effective_salt = match salt {
393 Some(s) => s,
394 None => self.params.salt.as_ref(),
395 };
396
397 let effective_length = if length > 0 {
399 length
400 } else {
401 self.params.key_length
402 };
403
404 Self::pbkdf2_secure(
406 input,
407 effective_salt,
408 self.params.iterations,
409 effective_length,
410 )
411 }
412
413 fn builder(&self) -> impl KdfOperation<'_, Self::Algorithm> {
415 Pbkdf2Builder::<H, S> {
416 kdf: self,
417 ikm: None,
418 salt: None,
419 iterations: self.params.iterations,
420 length: self.params.key_length,
421 }
422 }
423
424 fn generate_salt<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self::Salt> {
425 Salt::random_with_size(rng, Self::Algorithm::MIN_SALT_SIZE)
426 }
427
428 fn security_level() -> SecurityLevel {
429 Self::Algorithm::security_level()
430 }
431}
432
433#[cfg(feature = "std")]
434impl<H: HashFunction + Clone, const S: usize> PasswordHashFunction for Pbkdf2<H, S>
435where
436 Salt<S>: Pbkdf2Compatible,
437{
438 type Password = SecretBytes<32>; fn hash_password(&self, password: &Self::Password) -> Result<PasswordHash> {
441 let hash = Self::pbkdf2(
443 password.as_ref(),
444 self.params.salt.as_ref(),
445 self.params.iterations,
446 self.params.key_length,
447 )?;
448
449 let mut params = BTreeMap::new();
451 params.insert("i".to_string(), self.params.iterations.to_string());
452
453 Ok(PasswordHash {
454 algorithm: format!("pbkdf2-{}", H::name().to_lowercase()),
455 params,
456 salt: self.params.salt.to_bytes(),
457 hash: hash.into_inner().into_vec(),
458 })
459 }
460
461 fn verify(&self, password: &Self::Password, hash: &PasswordHash) -> Result<bool> {
462 let expected_alg = format!("pbkdf2-{}", H::name().to_lowercase());
464 validate::parameter(
465 hash.algorithm == expected_alg,
466 "algorithm",
467 "Algorithm mismatch",
468 )?;
469
470 let iterations = match hash.param("i") {
472 Some(i) => i
473 .parse::<u32>()
474 .map_err(|_| Error::param("iterations", "Invalid iterations parameter"))?,
475 None => return Err(Error::param("iterations", "Missing iterations parameter")),
476 };
477
478 let derived = Self::pbkdf2(password.as_ref(), &hash.salt, iterations, hash.hash.len())?;
480
481 Ok(constant_time_eq(&derived, &hash.hash))
483 }
484
485 fn benchmark(&self) -> Duration {
486 let start = Instant::now();
487 let password = SecretBytes::new([0u8; 32]); match self.hash_password(&password) {
492 Ok(_) => {}
493 Err(_) => {
494 }
498 }
499
500 start.elapsed()
501 }
502
503 fn recommended_params(target_duration: Duration) -> Self::Params {
504 let mut params = Pbkdf2Params::default();
506
507 let instance = Self::with_params(params.clone());
509
510 let current_duration = instance.benchmark();
512
513 let ratio = target_duration.as_secs_f64() / current_duration.as_secs_f64();
515 params.iterations = (params.iterations as f64 * ratio) as u32;
516
517 params.iterations = core::cmp::max(params.iterations, 10_000);
519
520 params
521 }
522}
523
524#[cfg(test)]
525mod tests;