wolf_crypto/kdf/pbkdf.rs
1//! The Password Based Key Derivation Function 1 and 2
2
3use wolf_crypto_sys::{wc_PBKDF2};
4
5use crate::{can_cast_i32, const_can_cast_i32, Fips, Unspecified};
6use crate::kdf::{Salt, Iters};
7
8#[cfg(feature = "allow-non-fips")]
9use crate::kdf::salt::NonEmpty as MinSize;
10
11#[cfg(not(feature = "allow-non-fips"))]
12use crate::kdf::salt::Min16 as MinSize;
13
14use crate::mac::hmac::algo::Hash;
15use core::fmt;
16
17/// The minimum output key length as stated in [SP 800-132, Section 5][1].
18///
19/// ```text
20/// The kLen value shall be at least 112 bits in length.
21/// ```
22///
23/// [1]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf#%5B%7B%22num%22%3A16%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C0%2C399%2Cnull%5D
24pub const FIPS_MIN_KEY: usize = 14;
25
26unsafe fn pbkdf2_unchecked<H: Hash>(
27 password: &[u8],
28 salt: impl Salt<MinSize>,
29 iters: Iters,
30 out: &mut [u8]
31) {
32 debug_assert!(
33 can_cast_i32(out.len())
34 && can_cast_i32(password.len())
35 && salt.i_is_valid_size()
36 && iters.is_valid_size()
37 );
38 #[cfg(not(feature = "allow-non-fips"))] {
39 debug_assert!(out.len() >= FIPS_MIN_KEY);
40 }
41
42 // Infallible, see HMAC internal commentary as well as this crates hash module's infallibility
43 // commentary.
44 let _res = wc_PBKDF2(
45 out.as_mut_ptr(),
46 password.as_ptr(),
47 password.len() as i32,
48 salt.ptr(),
49 salt.i_size(),
50 iters.get() as i32,
51 out.len() as i32,
52 H::type_id()
53 );
54
55 debug_assert_eq!(_res, 0);
56}
57
58#[cfg(not(feature = "allow-non-fips"))]
59#[inline]
60#[must_use]
61const fn check_key_len(len: usize) -> bool {
62 can_cast_i32(len) && len >= FIPS_MIN_KEY
63}
64
65#[cfg(feature = "allow-non-fips")]
66#[inline]
67#[must_use]
68const fn check_key_len(len: usize) -> bool {
69 can_cast_i32(len)
70}
71
72#[cfg(not(feature = "allow-non-fips"))]
73#[inline]
74#[must_use]
75const fn const_check_key_len<const L: usize>() -> bool {
76 const_can_cast_i32::<L>() && L >= FIPS_MIN_KEY
77}
78
79#[cfg(feature = "allow-non-fips")]
80#[inline]
81#[must_use]
82const fn const_check_key_len<const L: usize>() -> bool {
83 const_can_cast_i32::<L>()
84}
85
86/// Performs PBKDF2 and writes the result into the provided `out_key` buffer.
87///
88/// # Arguments
89///
90/// * `password` - The password to use for the key derivation.
91/// * `salt` - The salt to use for key derivation.
92/// * `iters` - The number of times to process the hash.
93/// * `out_key` - The buffer to write the generated key into.
94///
95/// # Errors
96///
97/// - The length of the `password` was greater than [`i32::MAX`].
98/// - The length of the `salt` was greater than [`i32::MAX`].
99/// - The number of `iters` was greater than [`i32::MAX`].
100/// - The length of the `out_key` was greater than [`i32::MAX`].
101///
102/// ## FIPS Errors
103///
104/// If the `allow-non-fips` feature flag is disabled this will return an error if the `out_key`
105/// length is not at least [`FIPS_MIN_KEY`] (14 bytes).
106///
107/// # Example
108///
109/// ```
110/// use wolf_crypto::kdf::{pbkdf2_into, Sha256, Iters};
111///
112/// let password = b"my secret password";
113/// let salt = [42; 16];
114/// let iters = Iters::new(600_000).unwrap();
115/// let mut out_key = [0u8; 32];
116///
117/// pbkdf2_into::<Sha256>(password, salt, iters, out_key.as_mut_slice()).unwrap();
118/// ```
119pub fn pbkdf2_into<H: Hash>(
120 password: &[u8],
121 salt: impl Salt<MinSize>,
122 iters: Iters,
123 out_key: &mut [u8]
124) -> Result<(), Unspecified> {
125 if can_cast_i32(password.len())
126 && salt.i_is_valid_size()
127 && iters.is_valid_size()
128 && check_key_len(out_key.len()) {
129 unsafe { pbkdf2_unchecked::<H>(password, salt, iters, out_key) };
130 Ok(())
131 } else {
132 Err(Unspecified)
133 }
134}
135
136/// Performs PBKDF2 and returns the result as a fixed-size array.
137///
138/// # Arguments
139///
140/// * `password` - The password to use for the key derivation.
141/// * `salt` - The salt to use for key derivation.
142/// * `iters` - The number of times to process the hash.
143///
144/// # Errors
145///
146/// - The length of the `password` was greater than [`i32::MAX`].
147/// - The length of the `salt` was greater than [`i32::MAX`].
148/// - The number of `iters` was greater than [`i32::MAX`].
149/// - The `KL` generic was greater than [`i32::MAX`].
150///
151/// ## FIPS Errors
152///
153/// If the `allow-non-fips` feature flag is disabled this will return an error if the `KL`
154/// generic is not at least [`FIPS_MIN_KEY`] (14 bytes).
155///
156/// # Example
157///
158/// ```
159/// use wolf_crypto::kdf::{pbkdf2, Sha256, Iters};
160///
161/// let password = b"my secret password";
162/// let salt = [42; 16];
163/// let iters = Iters::new(600_000).unwrap();
164///
165/// let key = pbkdf2::<32, Sha256>(password, salt, iters).unwrap();
166/// assert_eq!(key.len(), 32);
167/// ```
168pub fn pbkdf2<const KL: usize, H: Hash>(
169 password: &[u8],
170 salt: impl Salt<MinSize>,
171 iters: Iters
172) -> Result<[u8; KL], Unspecified> {
173 if const_check_key_len::<KL>()
174 && can_cast_i32(password.len())
175 && salt.i_is_valid_size()
176 && iters.is_valid_size() {
177 let mut out = [0u8; KL];
178 unsafe { pbkdf2_unchecked::<H>(password, salt, iters, out.as_mut_slice()) };
179 Ok(out)
180 } else {
181 Err(Unspecified)
182 }
183}
184
185use core::marker::PhantomData;
186use crate::kdf::salt::Min16;
187use crate::sealed::FipsSealed;
188
189/// A wrapper type enforcing FIPS compliance for PBKDF2 operations.
190///
191/// The `FipsPbkdf2` type ensures that PBKDF2 operations are performed using
192/// FIPS-compliant hash functions and salts at the type level.
193///
194/// If the `allow-non-fips` flag is disabled, the constraints the `FipsPbkdf2` struct enforces are
195/// equivalent to that of the [`pbkdf2`] and [`pbkdf2_into`] functions alone.
196///
197/// This type implements this crate's [`Fips`] marker type.
198///
199/// # Examples
200///
201/// ```rust
202/// use wolf_crypto::kdf::FipsPbkdf2;
203/// use wolf_crypto::hash::Sha256;
204/// use wolf_crypto::kdf::Iters;
205///
206/// let password = b"my password";
207/// let salt = [42; 16];
208/// let iters = Iters::new(100_000).unwrap();
209///
210/// let key = FipsPbkdf2::<Sha256>::array::<32>(password, salt, iters).unwrap();
211/// assert_eq!(key.len(), 32);
212/// ```
213///
214/// ```compile_fail
215/// # use wolf_crypto::kdf::FipsPbkdf2;
216/// # use wolf_crypto::hash::Sha256;
217/// # use wolf_crypto::kdf::Iters;
218/// #
219/// # let password = b"my password";
220/// let salt = [3; 8]; // won't compile! must be at least 16 bytes for FIPS compliance.
221/// # let iters = Iters::new(1_000).unwrap();
222/// # let mut out_key = [0u8; 32];
223///
224/// FipsPbkdf2::<Sha256>::into(password, salt, iters, &mut out_key).unwrap();
225/// ```
226#[derive(Copy, Clone)]
227pub struct FipsPbkdf2<H: Hash + Fips> {
228 _hash: PhantomData<H>
229}
230
231impl<H: Hash + Fips> fmt::Debug for FipsPbkdf2<H> {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 f.write_str("FipsPbkdf2<")
234 .and_then(|_| H::write_alg_name(f))
235 .and_then(|_| f.write_str(">"))
236 }
237}
238
239impl<H: Hash + Fips> FipsPbkdf2<H> {
240 /// Performs PBKDF2 and returns the result as a fixed-size array.
241 ///
242 /// # Arguments
243 ///
244 /// * `password` - The password to use for the key derivation.
245 /// * `salt` - The salt to use for key derivation.
246 /// * `iters` - The number of times to process the hash.
247 ///
248 /// # Errors
249 ///
250 /// - The length of the `password` was greater than [`i32::MAX`].
251 /// - The length of the `salt` was greater than [`i32::MAX`].
252 /// - The number of `iters` was greater than [`i32::MAX`].
253 /// - The `KL` generic was greater than [`i32::MAX`].
254 /// - The `KL` generic was less than [`FIPS_MIN_KEY`] (14 bytes).
255 ///
256 /// # Example
257 ///
258 /// ```
259 /// use wolf_crypto::kdf::{FipsPbkdf2, Sha256, Iters};
260 ///
261 /// type Sha256Pbkdf2 = FipsPbkdf2<Sha256>;
262 ///
263 /// let password = b"my secret password";
264 /// let salt = [42; 16];
265 /// let iters = Iters::new(600_000).unwrap();
266 ///
267 /// let key = Sha256Pbkdf2::array::<32>(password, salt, iters).unwrap();
268 /// assert_eq!(key.len(), 32);
269 /// ```
270 #[inline]
271 pub fn array<const KL: usize>(
272 password: &[u8],
273 salt: impl Salt<Min16>,
274 iters: Iters
275 ) -> Result<[u8; KL], Unspecified> {
276 #[cfg(feature = "allow-non-fips")] {
277 // this is already checked if allow-non-fips is disabled.
278 if KL < FIPS_MIN_KEY { return Err(Unspecified) }
279 }
280 pbkdf2::<KL, H>(password, salt, iters)
281 }
282
283 /// Performs PBKDF2 and writes the result into the provided `out_key` buffer.
284 ///
285 /// # Arguments
286 ///
287 /// * `password` - The password to use for the key derivation.
288 /// * `salt` - The salt to use for key derivation.
289 /// * `iters` - The number of times to process the hash.
290 /// * `out_key` - The buffer to write the generated key into.
291 ///
292 /// # Errors
293 ///
294 /// - The length of the `password` was greater than [`i32::MAX`].
295 /// - The length of the `salt` was greater than [`i32::MAX`].
296 /// - The number of `iters` was greater than [`i32::MAX`].
297 /// - The length of the `out_key` was greater than [`i32::MAX`].
298 /// - The length of the `out_key` was less than [`FIPS_MIN_KEY`] (14 bytes).
299 ///
300 /// # Example
301 ///
302 /// ```
303 /// use wolf_crypto::kdf::{FipsPbkdf2, Sha256, Iters};
304 ///
305 /// type Sha256Pbkdf2 = FipsPbkdf2<Sha256>;
306 ///
307 /// let password = b"my secret password";
308 /// let salt = [42; 16];
309 /// let iters = Iters::new(600_000).unwrap();
310 /// let mut out_key = [0u8; 32];
311 ///
312 /// Sha256Pbkdf2::into(password, salt, iters, out_key.as_mut_slice()).unwrap();
313 /// ```
314 #[inline]
315 pub fn into(
316 password: &[u8],
317 salt: impl Salt<Min16>,
318 iters: Iters,
319 out_key: &mut [u8]
320 ) -> Result<(), Unspecified> {
321 #[cfg(feature = "allow-non-fips")] {
322 // this is already checked if allow-non-fips is disabled.
323 if out_key.len() < FIPS_MIN_KEY { return Err(Unspecified) }
324 }
325
326 pbkdf2_into::<H>(password, salt, iters, out_key)
327 }
328
329 #[cfg(test)]
330 const fn new() -> Self {
331 Self { _hash: PhantomData }
332 }
333}
334
335impl<H: Hash + Fips> FipsSealed for FipsPbkdf2<H> {}
336impl<H: Hash + Fips> Fips for FipsPbkdf2<H> {}
337
338
339non_fips! {
340 use wolf_crypto_sys::wc_PBKDF1;
341
342 unsafe fn pbkdf1_unchecked<H: Hash>(
343 password: &[u8],
344 salt: impl Salt<MinSize>,
345 iters: Iters,
346 out: &mut [u8]
347 ) {
348 debug_assert!(
349 can_cast_i32(out.len())
350 && can_cast_i32(password.len())
351 && salt.i_is_valid_size()
352 && iters.is_valid_size()
353 );
354
355 // Infallible, see HMAC internal commentary as well as this crates hash module's infallibility
356 // commentary.
357 let _res = wc_PBKDF1(
358 out.as_mut_ptr(),
359 password.as_ptr(),
360 password.len() as i32,
361 salt.ptr(),
362 salt.i_size(),
363 iters.get() as i32,
364 out.len() as i32,
365 H::type_id()
366 );
367
368 debug_assert_eq!(_res, 0);
369 }
370
371 /// Performs PBKDF1 and writes the result into the provided `out_key` buffer.
372 ///
373 /// # Arguments
374 ///
375 /// * `password` - The password to use for the key derivation.
376 /// * `salt` - The salt to use for key derivation.
377 /// * `iters` - The number of times to process the hash.
378 /// * `out_key` - The buffer to write the generated key into.
379 ///
380 /// # Errors
381 ///
382 /// - The length of the `password` was greater than [`i32::MAX`].
383 /// - The length of the `salt` was greater than [`i32::MAX`].
384 /// - The number of `iters` was greater than [`i32::MAX`].
385 /// - The length of the `out_key` was greater than [`i32::MAX`].
386 ///
387 /// # Example
388 ///
389 /// ```
390 /// use wolf_crypto::kdf::{pbkdf1_into, Sha256, Iters};
391 ///
392 /// let password = b"my secret password";
393 /// let salt = [42; 16];
394 /// let iters = Iters::new(600_000).unwrap();
395 /// let mut out_key = [0u8; 32];
396 ///
397 /// pbkdf1_into::<Sha256>(password, salt, iters, out_key.as_mut_slice()).unwrap();
398 /// ```
399 pub fn pbkdf1_into<H: Hash>(
400 password: &[u8],
401 salt: impl Salt<MinSize>,
402 iters: Iters,
403 out_key: &mut [u8]
404 ) -> Result<(), Unspecified> {
405 if can_cast_i32(password.len())
406 && salt.i_is_valid_size()
407 && iters.is_valid_size()
408 && check_key_len(out_key.len()) {
409 unsafe { pbkdf1_unchecked::<H>(password, salt, iters, out_key) };
410 Ok(())
411 } else {
412 Err(Unspecified)
413 }
414 }
415
416 /// Performs PBKDF1 and returns the result as a fixed-size array.
417 ///
418 /// # Arguments
419 ///
420 /// * `password` - The password to use for the key derivation.
421 /// * `salt` - The salt to use for key derivation.
422 /// * `iters` - The number of times to process the hash.
423 ///
424 /// # Errors
425 ///
426 /// - The length of the `password` was greater than [`i32::MAX`].
427 /// - The length of the `salt` was greater than [`i32::MAX`].
428 /// - The number of `iters` was greater than [`i32::MAX`].
429 /// - The `KL` generic was greater than [`i32::MAX`].
430 ///
431 /// # Example
432 ///
433 /// ```
434 /// use wolf_crypto::kdf::{pbkdf1, Sha256, Iters};
435 ///
436 /// let password = b"my secret password";
437 /// let salt = [42; 16];
438 /// let iters = Iters::new(600_000).unwrap();
439 ///
440 /// let key = pbkdf1::<32, Sha256>(password, salt, iters).unwrap();
441 /// assert_eq!(key.len(), 32);
442 /// ```
443 pub fn pbkdf1<const KL: usize, H: Hash>(
444 password: &[u8],
445 salt: impl Salt<MinSize>,
446 iters: Iters
447 ) -> Result<[u8; KL], Unspecified> {
448 if const_check_key_len::<KL>()
449 && can_cast_i32(password.len())
450 && salt.i_is_valid_size()
451 && iters.is_valid_size() {
452 let mut out = [0u8; KL];
453 unsafe { pbkdf1_unchecked::<H>(password, salt, iters, out.as_mut_slice()) };
454 Ok(out)
455 } else {
456 Err(Unspecified)
457 }
458 }
459}
460
461#[cfg(test)]
462mod tests {
463 use super::*;
464 use crate::kdf::{FipsSaltSlice, Sha256};
465
466 macro_rules! bogus_slice {
467 ($sz:expr) => {{
468 unsafe { core::slice::from_raw_parts(b"bogus".as_ptr(), $sz) }
469 }};
470 (mut $sz:expr) => {{
471 unsafe { core::slice::from_raw_parts_mut(b"bogus".as_ptr().cast_mut(), $sz) }
472 }};
473 }
474
475 #[test]
476 fn catch_pwd_overflow() {
477 let pass = bogus_slice!(i32::MAX as usize + 1);
478 assert!(pbkdf2::<32, Sha256>(pass, [0u8; 16], Iters::new(100).unwrap()).is_err());
479 #[cfg(feature = "allow-non-fips")] {
480 assert!(pbkdf1::<32, Sha256>(pass, [0u8; 16], Iters::new(100).unwrap()).is_err());
481 }
482
483 let mut out = [0; 69];
484 assert!(pbkdf2_into::<Sha256>(pass, [0u8; 16], Iters::new(100).unwrap(), &mut out).is_err());
485 #[cfg(feature = "allow-non-fips")] {
486 assert!(pbkdf1_into::<Sha256>(pass, [0u8; 16], Iters::new(100).unwrap(), &mut out).is_err());
487 }
488 }
489
490 #[test]
491 fn catch_salt_overflow() {
492 let salt = FipsSaltSlice::new(bogus_slice!(i32::MAX as usize + 1)).unwrap();
493 let pass = b"my password";
494 assert!(pbkdf2::<32, Sha256>(pass, salt.clone(), Iters::new(100).unwrap()).is_err());
495 #[cfg(feature = "allow-non-fips")] {
496 assert!(pbkdf1::<32, Sha256>(pass, salt.clone(), Iters::new(100).unwrap()).is_err());
497 }
498
499 let mut out = [0; 69];
500 assert!(pbkdf2_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), &mut out).is_err());
501 #[cfg(feature = "allow-non-fips")] {
502 assert!(pbkdf1_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), &mut out).is_err());
503 }
504 }
505
506 #[test]
507 fn catch_iters_overflow() {
508 let salt = [0u8; 16];
509 let pass = b"my password";
510 let iters = Iters::new(i32::MAX as u32 + 1).unwrap();
511 assert!(pbkdf2::<32, Sha256>(pass, salt.clone(), iters).is_err());
512 #[cfg(feature = "allow-non-fips")] {
513 assert!(pbkdf1::<32, Sha256>(pass, salt.clone(), iters).is_err());
514 }
515
516 let mut out = [0; 69];
517 assert!(pbkdf2_into::<Sha256>(pass, salt.clone(), iters, &mut out).is_err());
518 #[cfg(feature = "allow-non-fips")] {
519 assert!(pbkdf1_into::<Sha256>(pass, salt.clone(), iters, &mut out).is_err());
520 }
521 }
522
523 #[test]
524 fn catch_desired_key_overflow() {
525 // we don't want to put u32 max on the stack, so we will not test the array convenience func
526 // in this case.
527 let desired = bogus_slice!(mut i32::MAX as usize + 1);
528 let salt = [0u8; 16];
529 let pass = b"my password";
530 assert!(pbkdf2_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), desired).is_err());
531 #[cfg(feature = "allow-non-fips")] {
532 assert!(pbkdf1_into::<Sha256>(pass, salt.clone(), Iters::new(100).unwrap(), desired).is_err());
533 }
534 }
535
536 #[test]
537 #[cfg_attr(feature = "allow-non-fips", ignore)]
538 fn catch_fips_min_key() {
539 let mut out = [0u8; 13];
540 assert!(pbkdf2::<13, Sha256>(b"hello world", [0u8; 16], Iters::new(100).unwrap()).is_err());
541 assert!(pbkdf2_into::<Sha256>(b"hello world", [0u8; 16], Iters::new(100).unwrap(), &mut out).is_err());
542 }
543
544 #[test]
545 fn fmt_fips_pbkdf2() {
546 assert_eq!(format!("{:?}", FipsPbkdf2::<Sha256>::new()), "FipsPbkdf2<Sha256>");
547 }
548}
549
550#[cfg(test)]
551mod property_tests {
552 // TODO: impl NIST CAVS tests.
553
554 use proptest::prelude::*;
555 use crate::aes::test_utils::BoundList;
556 use super::*;
557
558 use crate::kdf::{Sha256, Sha384, Sha512};
559 use crate::kdf::DynSaltSlice as SaltSlice;
560
561 use pbkdf2::{pbkdf2_hmac};
562
563 macro_rules! against_rc_into {
564 (
565 name: $name:ident,
566 cases: $cases:literal,
567 max_iters: $max_iters:literal,
568 algo: $algo:ident
569 ) => {proptest! {
570 #![proptest_config(ProptestConfig::with_cases($cases))]
571
572 #[test]
573 fn $name(
574 pwd in any::<BoundList<512>>(),
575 salt in any::<BoundList<512>>(),
576 // I do not have the remainder of the year to wait for this to pass. I've run this
577 // with 100k on release, I ate a meal and it was still running.
578 iters in 1..$max_iters,
579 key_len in 1..1024usize
580 ) {
581 #[cfg(feature = "allow-non-fips")] {
582 prop_assume!(!salt.as_slice().is_empty());
583 }
584
585 #[cfg(not(feature = "allow-non-fips"))] {
586 prop_assume!(salt.len() >= 16);
587 prop_assume!(key_len >= 14);
588 }
589
590 let mut key_buf = BoundList::<1024>::new_zeroes(key_len);
591 let mut rc_key_buf = key_buf.create_self();
592
593 pbkdf2_into::<$algo>(
594 pwd.as_slice(),
595 SaltSlice::new(salt.as_slice()).unwrap(),
596 Iters::new(iters).unwrap(),
597 key_buf.as_mut_slice()
598 ).unwrap();
599
600 pbkdf2_hmac::<sha2::$algo>(
601 pwd.as_slice(),
602 salt.as_slice(),
603 iters,
604 rc_key_buf.as_mut_slice()
605 );
606
607 prop_assert_eq!(key_buf.as_slice(), rc_key_buf.as_slice());
608 }
609 }};
610 }
611
612 against_rc_into! {
613 name: rust_crypto_equivalence_sha256,
614 cases: 5000,
615 max_iters: 100u32,
616 algo: Sha256
617 }
618
619 against_rc_into! {
620 name: rust_crypto_equivalence_sha384,
621 cases: 5000,
622 max_iters: 100u32,
623 algo: Sha384
624 }
625
626 against_rc_into! {
627 name: rust_crypto_equivalence_sha512,
628 cases: 5000,
629 max_iters: 50u32,
630 algo: Sha512
631 }
632}