aws_lc_rs/
pbkdf2.rs

1// Copyright 2015-2022 Brian Smith.
2// SPDX-License-Identifier: ISC
3// Modifications copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
4// SPDX-License-Identifier: Apache-2.0 OR ISC
5
6//! PBKDF2 derivation and verification.
7//!
8//! Use `derive` to derive PBKDF2 outputs. Use `verify` to verify secret
9//! against previously-derived outputs.
10//!
11//! PBKDF2 is specified in [RFC 2898 Section 5.2] with test vectors given in
12//! [RFC 6070]. See also [NIST Special Publication 800-132].
13//!
14//! [RFC 2898 Section 5.2]: https://tools.ietf.org/html/rfc2898#section-5.2
15//! [RFC 6070]: https://tools.ietf.org/html/rfc6070
16//! [NIST Special Publication 800-132]:
17//!    http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf
18//!
19//! # Examples
20//!
21//! ## Password Database Example
22//!
23//! ```
24//! use aws_lc_rs::{digest, pbkdf2};
25//! use std::{collections::HashMap, num::NonZeroU32};
26//!
27//! static PBKDF2_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256;
28//! const CREDENTIAL_LEN: usize = digest::SHA256_OUTPUT_LEN;
29//! pub type Credential = [u8; CREDENTIAL_LEN];
30//!
31//! enum Error {
32//!     WrongUsernameOrPassword
33//! }
34//!
35//! struct PasswordDatabase {
36//!     pbkdf2_iterations: NonZeroU32,
37//!     db_salt_component: [u8; 16],
38//!
39//!     // Normally this would be a persistent database.
40//!     storage: HashMap<String, Credential>,
41//! }
42//!
43//! impl PasswordDatabase {
44//!     pub fn store_password(&mut self, username: &str, password: &str) {
45//!         let salt = self.salt(username);
46//!         let mut to_store: Credential = [0u8; CREDENTIAL_LEN];
47//!         pbkdf2::derive(PBKDF2_ALG, self.pbkdf2_iterations, &salt,
48//!                        password.as_bytes(), &mut to_store);
49//!         self.storage.insert(String::from(username), to_store);
50//!     }
51//!
52//!     pub fn verify_password(&self, username: &str, attempted_password: &str)
53//!                            -> Result<(), Error> {
54//!         match self.storage.get(username) {
55//!            Some(actual_password) => {
56//!                let salt = self.salt(username);
57//!                pbkdf2::verify(PBKDF2_ALG, self.pbkdf2_iterations, &salt,
58//!                               attempted_password.as_bytes(),
59//!                               actual_password)
60//!                     .map_err(|_| Error::WrongUsernameOrPassword)
61//!            },
62//!
63//!            None => Err(Error::WrongUsernameOrPassword)
64//!         }
65//!     }
66//!
67//!     // The salt should have a user-specific component so that an attacker
68//!     // cannot crack one password for multiple users in the database. It
69//!     // should have a database-unique component so that an attacker cannot
70//!     // crack the same user's password across databases in the unfortunate
71//!     // but common case that the user has used the same password for
72//!     // multiple systems.
73//!     fn salt(&self, username: &str) -> Vec<u8> {
74//!         let mut salt = Vec::with_capacity(self.db_salt_component.len() +
75//!                                           username.as_bytes().len());
76//!         salt.extend(self.db_salt_component.as_ref());
77//!         salt.extend(username.as_bytes());
78//!         salt
79//!     }
80//! }
81//!
82//! fn main() {
83//!     // Normally these parameters would be loaded from a configuration file.
84//!     let mut db = PasswordDatabase {
85//!         pbkdf2_iterations: NonZeroU32::new(100_000).unwrap(),
86//!         db_salt_component: [
87//!             // This value was generated from a secure PRNG.
88//!             0xd6, 0x26, 0x98, 0xda, 0xf4, 0xdc, 0x50, 0x52,
89//!             0x24, 0xf2, 0x27, 0xd1, 0xfe, 0x39, 0x01, 0x8a
90//!         ],
91//!         storage: HashMap::new(),
92//!     };
93//!
94//!     db.store_password("alice", "@74d7]404j|W}6u");
95//!
96//!     // An attempt to log in with the wrong password fails.
97//!     assert!(db.verify_password("alice", "wrong password").is_err());
98//!
99//!     // Normally there should be an expoentially-increasing delay between
100//!     // attempts to further protect against online attacks.
101//!
102//!     // An attempt to log in with the right password succeeds.
103//!     assert!(db.verify_password("alice", "@74d7]404j|W}6u").is_ok());
104//! }
105
106use crate::aws_lc::PKCS5_PBKDF2_HMAC;
107use crate::error::Unspecified;
108use crate::fips::indicator_check;
109use crate::{constant_time, digest, hmac};
110use core::num::NonZeroU32;
111use zeroize::Zeroize;
112
113/// A PBKDF2 algorithm.
114///
115/// `max_output_len` is computed as u64 instead of usize to prevent overflowing on 32-bit machines.
116#[derive(Clone, Copy, PartialEq, Eq)]
117pub struct Algorithm {
118    algorithm: hmac::Algorithm,
119    max_output_len: u64,
120}
121
122/// PBKDF2 using HMAC-SHA1.
123pub static PBKDF2_HMAC_SHA1: Algorithm = Algorithm {
124    algorithm: hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY,
125    max_output_len: MAX_USIZE32 * digest::SHA1_OUTPUT_LEN as u64,
126};
127
128/// PBKDF2 using HMAC-SHA256.
129pub static PBKDF2_HMAC_SHA256: Algorithm = Algorithm {
130    algorithm: hmac::HMAC_SHA256,
131    max_output_len: MAX_USIZE32 * digest::SHA256_OUTPUT_LEN as u64,
132};
133
134/// PBKDF2 using HMAC-SHA384.
135pub static PBKDF2_HMAC_SHA384: Algorithm = Algorithm {
136    algorithm: hmac::HMAC_SHA384,
137    max_output_len: MAX_USIZE32 * digest::SHA384_OUTPUT_LEN as u64,
138};
139
140/// PBKDF2 using HMAC-SHA512.
141pub static PBKDF2_HMAC_SHA512: Algorithm = Algorithm {
142    algorithm: hmac::HMAC_SHA512,
143    max_output_len: MAX_USIZE32 * digest::SHA512_OUTPUT_LEN as u64,
144};
145
146const MAX_USIZE32: u64 = u32::MAX as u64;
147
148/// Fills `out` with the key derived using PBKDF2 with the given inputs.
149///
150/// Do not use `derive` as part of verifying a secret; use `verify` instead, to
151/// minimize the effectiveness of timing attacks.
152///
153/// `out.len()` must be no larger than the digest length * (2**32 - 1), per the
154/// PBKDF2 specification.
155///
156/// | Parameter   | RFC 2898 Section 5.2 Term
157/// |-------------|-------------------------------------------
158/// | `digest_alg`  | PRF (HMAC with the given digest algorithm)
159/// | `iterations`  | c (iteration count)
160/// | `salt`        | S (salt)
161/// | `secret`      | P (password)
162/// | `out`         | dk (derived key)
163/// | `out.len()`   | dkLen (derived key length)
164///
165/// # Panics
166///
167/// `derive` panics if `out.len()` is larger than (2**32 - 1) * the digest
168/// algorithm's output length, per the PBKDF2 specification.
169//
170// # FIPS
171// The following conditions must be met:
172// * Algorithm is one of the following:
173//   * `PBKDF2_HMAC_SHA1`
174//   * `PBKDF2_HMAC_SHA256`
175//   * `PBKDF2_HMAC_SHA384`
176//   * `PBKDF2_HMAC_SHA512`
177// * `salt.len()` >= 16
178// * `sercet.len()` >= 14
179// * `iterations` >= 1000
180#[inline]
181pub fn derive(
182    algorithm: Algorithm,
183    iterations: NonZeroU32,
184    salt: &[u8],
185    secret: &[u8],
186    out: &mut [u8],
187) {
188    try_derive(algorithm, iterations, salt, secret, out).expect("pbkdf2 derive failed");
189}
190
191#[inline]
192fn try_derive(
193    algorithm: Algorithm,
194    iterations: NonZeroU32,
195    salt: &[u8],
196    secret: &[u8],
197    out: &mut [u8],
198) -> Result<(), Unspecified> {
199    assert!(
200        out.len() as u64 <= algorithm.max_output_len,
201        "derived key too long"
202    );
203
204    if 1 != indicator_check!(unsafe {
205        PKCS5_PBKDF2_HMAC(
206            secret.as_ptr().cast(),
207            secret.len(),
208            salt.as_ptr(),
209            salt.len(),
210            iterations.get(),
211            *digest::match_digest_type(&algorithm.algorithm.digest_algorithm().id),
212            out.len(),
213            out.as_mut_ptr(),
214        )
215    }) {
216        return Err(Unspecified);
217    }
218    Ok(())
219}
220
221/// Verifies that a previously-derived (e.g., using `derive`) PBKDF2 value
222/// matches the PBKDF2 value derived from the other inputs.
223///
224/// The comparison is done in constant time to prevent timing attacks. The
225/// comparison will fail if `previously_derived` is empty (has a length of
226/// zero).
227///
228/// | Parameter                  | RFC 2898 Section 5.2 Term
229/// |----------------------------|--------------------------------------------
230/// | `digest_alg`                 | PRF (HMAC with the given digest algorithm).
231/// | `iterations`               | c (iteration count)
232/// | `salt`                     | S (salt)
233/// | `secret`                   | P (password)
234/// | `previously_derived`       | dk (derived key)
235/// | `previously_derived.len()` | dkLen (derived key length)
236///
237/// # Errors
238/// `error::Unspecified` is the inputs were not verified.
239///
240/// # Panics
241///
242/// `verify` panics if `previously_derived.len()` is larger than (2**32 - 1) * the digest
243/// algorithm's output length, per the PBKDF2 specification.
244//
245// # FIPS
246// The following conditions must be met:
247// * Algorithm is one of the following:
248//   * `PBKDF2_HMAC_SHA1`
249//   * `PBKDF2_HMAC_SHA256`
250//   * `PBKDF2_HMAC_SHA384`
251//   * `PBKDF2_HMAC_SHA512`
252// * `salt.len()` >= 16
253// * `secret.len()` >= 14
254// * `iterations` >= 1000
255#[inline]
256pub fn verify(
257    algorithm: Algorithm,
258    iterations: NonZeroU32,
259    salt: &[u8],
260    secret: &[u8],
261    previously_derived: &[u8],
262) -> Result<(), Unspecified> {
263    if previously_derived.is_empty() {
264        return Err(Unspecified);
265    }
266    assert!(
267        previously_derived.len() as u64 <= algorithm.max_output_len,
268        "derived key too long"
269    );
270
271    // Create a vector with the expected output length.
272    let mut derived_buf = vec![0u8; previously_derived.len()];
273
274    try_derive(algorithm, iterations, salt, secret, &mut derived_buf)?;
275
276    let result = constant_time::verify_slices_are_equal(&derived_buf, previously_derived);
277    derived_buf.zeroize();
278    result
279}
280
281#[cfg(test)]
282mod tests {
283    use crate::pbkdf2;
284    use core::num::NonZeroU32;
285
286    #[cfg(feature = "fips")]
287    mod fips;
288
289    #[test]
290    fn pbkdf2_coverage() {
291        // Coverage sanity check.
292        assert!(pbkdf2::PBKDF2_HMAC_SHA256 == pbkdf2::PBKDF2_HMAC_SHA256);
293        assert!(pbkdf2::PBKDF2_HMAC_SHA256 != pbkdf2::PBKDF2_HMAC_SHA384);
294
295        let iterations = NonZeroU32::new(100_u32).unwrap();
296        for &alg in &[
297            pbkdf2::PBKDF2_HMAC_SHA1,
298            pbkdf2::PBKDF2_HMAC_SHA256,
299            pbkdf2::PBKDF2_HMAC_SHA384,
300            pbkdf2::PBKDF2_HMAC_SHA512,
301        ] {
302            let mut out = vec![0u8; 64];
303            pbkdf2::derive(alg, iterations, b"salt", b"password", &mut out);
304
305            let alg_clone = alg;
306            let mut out2 = vec![0u8; 64];
307            pbkdf2::derive(alg_clone, iterations, b"salt", b"password", &mut out2);
308            assert_eq!(out, out2);
309        }
310    }
311}