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}