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