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, ¶ms);
240 assert!(scrypt_verify(&pass, &hash), "pass: {:?}, hash: {:?}", &pass[..], hash);
241 }
242 }
243 }
244}