Description
In a few words: The new scrypt API in v10.5 is dangerously designed and will cause people to make security mistakes. A new API is needed for the 98% problem (i.e. password hashing). Additionally, the documentation for the old API should make it abundantly clear that it's not meant for non-expert users, and to instead use whatever the new API is called.
Copying my comment (with slight corrections) from #20816
This is how PHP does password hashing:
// All in one go:
$hash = password_hash($userProvidedPassword, PASSWORD_DEFAULT);
// Later:
if (password_verify($userProvidedPassword, $hash)) {
// Everything works out. Optionally, do this too:
if (password_needs_rehash($userProvidedPassword, PASSWORD_DEFAULT)) {
// Someone upgraded PHP and there's a new default algorithm. We should rehash the password.
}
}
This is almost an optimal UX for PHP developers for password storage: Salts are explicitly generated by the kernel's CSPRNG, encoded, and included in the password hash.
A step further would be making PASSWORD_DEFAULT
--, well, the default.
You should only need one input for generating a new password hash: The password.
You should only need two inputs for verification: The challenge and the attempt (or the hash and password, respectively).
All other parameters should be optional.
Note: Key derivation is a separate concern, where you don't want the salt stored alongside the password hash, but that's a smaller corner of the cryptography usability market than password hashing.
Let's look at how another library handles both corner cases:
Key Derivation
#define PASSWORD "Correct Horse Battery Staple"
#define KEY_LEN crypto_box_SEEDBYTES
unsigned char salt[crypto_pwhash_SALTBYTES];
unsigned char key[KEY_LEN];
randombytes_buf(salt, sizeof salt);
if (crypto_pwhash
(key, sizeof key, PASSWORD, strlen(PASSWORD), salt,
crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT) != 0) {
/* out of memory */
}
Password Hashing
#define PASSWORD "Correct Horse Battery Staple"
char hashed_password[crypto_pwhash_STRBYTES];
if (crypto_pwhash_str
(hashed_password, PASSWORD, strlen(PASSWORD),
crypto_pwhash_OPSLIMIT_SENSITIVE, crypto_pwhash_MEMLIMIT_SENSITIVE) != 0) {
/* out of memory */
}
if (crypto_pwhash_str_verify
(hashed_password, PASSWORD, strlen(PASSWORD)) != 0) {
/* wrong password */
}
Usable cryptography API design is a nontrivial undertaking, and getting it wrong will mean years (or even decades) of clean-up.
I would propose, at minimum, an alternative API that has a similar user experience to PHP's password_hash()
that transparently wraps scrypt as part of the Node.js core, and then recommend that API for users who want to store password hashes (which is roughly 98% of the use case when someone reaches for bcrypt, scrypt, or Argon2).
Or you could always just incorporate the design of @joepie91's scrypt-for-humans
into the Node.js core. Designing cryptography APIs for humans is a good idea.