Skip to content

Password Hashing API #21766

Closed
Closed
@paragonie-scott

Description

@paragonie-scott

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    cryptoIssues and PRs related to the crypto subsystem.securityIssues and PRs related to security.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions