mundane/
password.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Password verification.
6
7/// The scrypt password hashing function.
8///
9/// scrypt was originally proposed in [Stronger Key Derivation via Sequential
10/// Memory-Hard Functions] and standardized in [RFC 7914].
11///
12/// A note on terminology: scrypt is technically a key derivation function, and
13/// its output is thus technically a key. However, we expose it here only for
14/// the purposes of password verification, and thus we use the term "hash" to
15/// refer to its output.
16///
17/// [Stronger Key Derivation via Sequential Memory-Hard Functions]: https://www.tarsnap.com/scrypt/scrypt.pdf
18/// [RFC 7914]: https://tools.ietf.org/html/rfc7914
19pub mod scrypt {
20    use boringssl;
21
22    // NOTE(joshlf): These are both set to 32 bytes (256 bits). This is probably
23    // overkill (128 bits is probably fine, as the lack of entropy in password
24    // hashes usually comes primarily from the passwords themselves), but better
25    // safe than sorry. Note that this is consistent with the Go scrypt
26    // documentation examples (https://godoc.org/golang.org/x/crypto/scrypt),
27    // while Go's bcrypt implementation
28    // (https://godoc.org/golang.org/x/crypto/bcrypt) uses a 128-bit salt and a
29    // 192-bit hash.
30
31    /// The length of an scrypt hash.
32    ///
33    /// The value of this constant - 32 - is considered part of this API. Any
34    /// changes to it will be considered breaking changes.
35    pub const SCRYPT_HASH_LEN: usize = 32;
36    /// The length of an scrypt salt.
37    ///
38    /// The value of this constant - 32 - is considered part of this API. Any
39    /// changes to it will be considered breaking changes.
40    pub const SCRYPT_SALT_LEN: usize = 32;
41
42    /// Recommended parameters for a production server.
43    ///
44    /// `SCRYPT_PARAMS_SERVER` is an appropriate set of parameters for running
45    /// scrypt on a production server in 2018. It targets 100ms of execution
46    /// time per generation or verification.
47    ///
48    /// The value of this constant may be updated periodically in order to keep
49    /// up with hardware trends.
50    pub const SCRYPT_PARAMS_SERVER: ScryptParams = ScryptParams {
51        // NOTE(joshlf): These were taken from the Go scrypt implementation
52        // (https://godoc.org/golang.org/x/crypto/scrypt) on 08/14/2018.
53        N: 32768,
54        r: 8,
55        p: 1,
56    };
57
58    /// Recommended paramaters for a laptop.
59    ///
60    /// `SCRYPT_PARAMS_LAPTOP` is an appropriate set of parameters for running
61    /// scrypt on a medium-range laptop in 2018. It targets 100ms of execution
62    /// time per generation or verification.
63    ///
64    /// The value of this constant may be updated periodically in order to keep
65    /// up with hardware trends.
66    pub const SCRYPT_PARAMS_LAPTOP: ScryptParams = ScryptParams {
67        // NOTE(joshlf): These were benchmarked on my laptop (2017 MacBook Pro
68        // 13-inch with a 3.5 GHz Intel Core i7 - model identifier
69        // MacBookPro14,2) on 08/14/2018.
70        N: 16384,
71        r: 8,
72        p: 1,
73    };
74
75    /// The parameters to the scrypt function.
76    ///
77    /// These parameters determine how much effort will be required in order to
78    /// generate or verify an scrypt hash. "Effort" here refers to utilization
79    /// of of CPU, memory, and memory bandwidth. For more details on what these
80    /// parameters mean and their implications, see [The scrypt Parameters]. For
81    /// sane defaults, see the `SCRYPT_PARAMS_XXX` constants.
82    ///
83    /// [The scrypt Parameters]: https://blog.filippo.io/the-scrypt-parameters/
84    #[allow(non_snake_case)]
85    #[allow(missing_docs)]
86    #[derive(Debug, Copy, Clone)]
87    pub struct ScryptParams {
88        // NOTE(joshlf): These are private so that the user is forced to use one
89        // of our presets. If this turns out to be too brittle, it might be
90        // worth considering making these public, and simply discouraging
91        // (perhaps via a deprecation attribute) setting them directly.
92        N: u64,
93        r: u64,
94        p: u64,
95    }
96
97    impl ScryptParams {
98        /// Gets the parameter N.
99        #[allow(non_snake_case)]
100        #[must_use]
101        pub fn N(&self) -> u64 {
102            self.N
103        }
104
105        /// Gets the parameter r.
106        #[must_use]
107        pub fn r(&self) -> u64 {
108            self.r
109        }
110
111        /// Gets the parameter p.
112        #[must_use]
113        pub fn p(&self) -> u64 {
114            self.p
115        }
116    }
117
118    // Don't put a limit on the memory used by scrypt; it's too prone to
119    // failure. Instead, rely on choosing sane defaults for N, r, and p to
120    // ensure that we don't use too much memory.
121    const SCRYPT_MAX_MEM: usize = usize::max_value();
122
123    // TODO(joshlf): Provide a custom Debug impl for ScryptHash?
124
125    /// The output of the scrypt password hashing function.
126    #[must_use]
127    #[allow(non_snake_case)]
128    #[derive(Debug, Copy, Clone)]
129    pub struct ScryptHash {
130        hash: [u8; SCRYPT_HASH_LEN],
131        salt: [u8; SCRYPT_SALT_LEN],
132        params: ScryptParams,
133    }
134
135    impl ScryptHash {
136        // NOTE(joshlf): Normally, having three different parameters in a row of
137        // the same type would be dangerous because it's too easy to
138        // accidentally pass arguments in the wrong order. In this particular
139        // case, it's less of a concern because ScryptHash is only passed to the
140        // scrypt_verify function, so the worst that reordering these parameters
141        // can due is cause a valid hash to be mistakenly rejected as invalid.
142        // If this were a constructor on ScryptParams, which is passed as an
143        // argument to scrypt_generate, then a mistake might lead to
144        // accidentally generating a hash with weak security parameters, which
145        // would be a problem.
146
147        /// Constructs a new `ScryptHash`.
148        #[allow(non_snake_case)]
149        #[must_use]
150        pub fn new(
151            hash: [u8; SCRYPT_HASH_LEN],
152            salt: [u8; SCRYPT_SALT_LEN],
153            N: u64,
154            r: u64,
155            p: u64,
156        ) -> ScryptHash {
157            ScryptHash { hash, salt, params: ScryptParams { N, r, p } }
158        }
159
160        /// Gets the hash.
161        #[must_use]
162        pub fn hash(&self) -> &[u8; SCRYPT_HASH_LEN] {
163            &self.hash
164        }
165
166        /// Gets the salt.
167        #[must_use]
168        pub fn salt(&self) -> &[u8; SCRYPT_SALT_LEN] {
169            &self.salt
170        }
171
172        /// Gets the params.
173        #[must_use]
174        pub fn params(&self) -> ScryptParams {
175            self.params
176        }
177    }
178
179    /// Generates an scrypt hash for the given password.
180    ///
181    /// `scrypt_generate` uses scrypt to generate a hash for the given
182    /// `password` using the provided `params`.
183    #[must_use]
184    pub fn scrypt_generate(password: &[u8], params: &ScryptParams) -> ScryptHash {
185        let mut salt = [0u8; SCRYPT_SALT_LEN];
186        boringssl::rand_bytes(&mut salt);
187        let mut hash = [0u8; SCRYPT_HASH_LEN];
188        // Can only fail on OOM, max_mem exceeded (SCRYPT_MAX_MEM is max usize,
189        // so that definitely won't happen), or if any of the parameters are
190        // invalid (which would be a bug on our part). Thus, we unwrap.
191        boringssl::evp_pbe_scrypt(
192            password,
193            &salt,
194            params.N,
195            params.r,
196            params.p,
197            SCRYPT_MAX_MEM,
198            &mut hash,
199        )
200        .unwrap();
201        ScryptHash { hash, salt, params: *params }
202    }
203
204    /// Verifies a password against an scrypt hash.
205    ///
206    /// `scrypt_verify` verifies that `password` is the same password that was
207    /// used to generate `hash` using scrypt.
208    #[must_use]
209    pub fn scrypt_verify(password: &[u8], hash: &ScryptHash) -> bool {
210        let mut out_hash = [0u8; SCRYPT_HASH_LEN];
211        if boringssl::evp_pbe_scrypt(
212            password,
213            &hash.salt,
214            hash.params.N,
215            hash.params.r,
216            hash.params.p,
217            SCRYPT_MAX_MEM,
218            &mut out_hash,
219        )
220        .is_err()
221        {
222            return false;
223        }
224        boringssl::crypto_memcmp(&out_hash, &hash.hash)
225    }
226
227    #[cfg(test)]
228    mod tests {
229        use super::*;
230
231        #[test]
232        fn test_scrypt() {
233            for _ in 0..16 {
234                let mut pass = [0; 128];
235                boringssl::rand_bytes(&mut pass);
236                // target 1 second of execution for this test on a laptop
237                let mut params = SCRYPT_PARAMS_LAPTOP;
238                params.N /= 4;
239                let hash = scrypt_generate(&pass, &params);
240                assert!(scrypt_verify(&pass, &hash), "pass: {:?}, hash: {:?}", &pass[..], hash);
241            }
242        }
243    }
244}