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}