SALT and HASH password in nodejs w/ crypto

74,063

Solution 1

In whatever persistence mechanism (database) you're using, you would store the resulting hash alongside the salt and number of iterations, both of which would be plaintext. If each password uses different salt (which you should do), you must also save that information.

You would then compare the new plain text password, hash that using the same salt (and iterations), then compare the byte sequence with the stored one.

To generate the password (pseudo)

function hashPassword(password) {
    var salt = crypto.randomBytes(128).toString('base64');
    var iterations = 10000;
    var hash = pbkdf2(password, salt, iterations);

    return {
        salt: salt,
        hash: hash,
        iterations: iterations
    };
}

To validate password (pseudo)

function isPasswordCorrect(savedHash, savedSalt, savedIterations, passwordAttempt) {
    return savedHash == pbkdf2(passwordAttempt, savedSalt, savedIterations);
}

Solution 2

Based on the nodejs documentation (http://nodejs.org/api/crypto.html), it doesn't look like there is a specific method that will validate a password for you. To validate it manually, you will need to compute the hash of the currently provided password and compare it to the stored one for equality. Basically, you will do the same thing with the challenge password that you did with the original, but use the salt stored in the database instead of generating a new one, and then compare the two hashes.

If you aren't too committed to using the built in crypto library, I might recommend using bcrypt instead. The two are about equal on the security front, but I think bcrypt has a more user-friendly interface. An example of how to use it (taken directly from the bcrypt docs on the page linked above) would be this:

Create a hash:

var bcrypt = require('bcrypt');
var salt = bcrypt.genSaltSync(10);
var hash = bcrypt.hashSync("B4c0/\/", salt);
// Store hash in your password DB.

To check a password:

// Load hash from your password DB.
bcrypt.compareSync("B4c0/\/", hash); // true
bcrypt.compareSync("not_bacon", hash); // false

Edit to add:

Another advantage of bcrypt is that the output of the genSalt function contains both the hash and the salt in one string. This means that you can store just the single item in your database, instead of two. There is also a method provided that will generate a salt at the same time that the hashing occurs, so you don't have to worry about managing the salt at all.

Edit to update:

In response to the comment from Peter Lyons: you're 100% correct. I had assumed that the bcrypt module that I had recommended was a javascript implementation, and therefor using it asynchronously wouldn't really speed things up on node's single threaded model. It turns out that this is not the case; the bcrypt module uses native c++ code for it's computations and will run faster asynchronously. Peter Lyons is right, you should use the asynchronous version of the method first and only pick the synchronous one when necessary. The asynchronous method might be as slow as the synchronous one, but the synchronous one will always be slow.

Solution 3

Either store password and salt in separate columns in your database, or (my preferred method), store your passwords in your database in a format that's compatible with RFC 2307 section 5.3. An example would be {X-PBKDF2}base64salt:base64digest. You could also store your iteration count in there, which allows you to increase the iteration count in the future for new accounts and accounts that update your passwords, without breaking logins for everyone else.

An example hash from my own PBKDF2 module for Perl looks like
{X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk= which includes the specific hash algorithm used, as well as the number of iterations, the salt, and the resulting key.

Solution 4

This is a modified version of @Matthews answer, using TypeScript

import * as crypto from "crypto";

const PASSWORD_LENGTH = 256;
const SALT_LENGTH = 64;
const ITERATIONS = 10000;
const DIGEST = "sha256";
const BYTE_TO_STRING_ENCODING = "hex"; // this could be base64, for instance

/**
 * The information about the password that is stored in the database
 */
interface PersistedPassword {
  salt: string;
  hash: string;
  iterations: number;
}

/**
 * Generates a PersistedPassword given the password provided by the user.
 * This should be called when creating a user or redefining the password
 */
export function generateHashPassword(
  password: string
): Promise<PersistedPassword> {
  return new Promise<PersistedPassword>((accept, reject) => {
    const salt = crypto
      .randomBytes(SALT_LENGTH)
      .toString(BYTE_TO_STRING_ENCODING);
    crypto.pbkdf2(
      password,
      salt,
      ITERATIONS,
      PASSWORD_LENGTH,
      DIGEST,
      (error, hash) => {
        if (error) {
          return reject(error);
        }

        accept({
          salt,
          hash: hash.toString(BYTE_TO_STRING_ENCODING),
          iterations: ITERATIONS,
        });
      }
    );
  });
}

/**
 * Verifies the attempted password against the password information saved in
 * the database. This should be called when
 * the user tries to log in.
 */
export function verifyPassword(
  persistedPassword: PersistedPassword,
  passwordAttempt: string
): Promise<boolean> {
  return new Promise<boolean>((accept, reject) => {
    crypto.pbkdf2(
      passwordAttempt,
      persistedPassword.salt,
      persistedPassword.iterations,
      PASSWORD_LENGTH,
      DIGEST,
      (error, hash) => {
        if (error) {
          return reject(error);
        }

        accept(
          persistedPassword.hash === hash.toString(BYTE_TO_STRING_ENCODING)
        );
      }
    );
  });
}

Solution 5

Faced with the same question I brought everything together into one module: https://www.npmjs.org/package/password-hash-and-salt

It uses pbkdf2 and stores hash, salt, algorithm, and iterations in a single field. Hope it helps.

Share:
74,063
lostintranslation
Author by

lostintranslation

Updated on August 04, 2022

Comments

  • lostintranslation
    lostintranslation almost 2 years

    I am trying to figure out how to salt and hash a password in nodejs using the crypto module. I am able to create the hashed password doing this:

    UserSchema.pre('save', function(next) {
      var user = this;
    
      var salt = crypto.randomBytes(128).toString('base64');
      crypto.pbkdf2(user.password, salt, 10000, 512, function(err, derivedKey) {
        user.password = derivedKey;
        next();
      });
    });
    

    However I am confused about how to later validate the password.

    UserSchema.methods.validPassword = function(password) {    
      // need to salt and hash this password I think to compare
      // how to I get the salt?
    }
    
  • lostintranslation
    lostintranslation about 11 years
    ah so I have to store the salt created for each user as well as the hashed password?
  • Matthew
    Matthew about 11 years
    Yes, you cannot reconstruct the same hash using the same plain text if you do not know the salt that was used to generate the hash.
  • Peter Lyons
    Peter Lyons about 11 years
    I think posting examples of synchronous calls for node.js questions is misleading and confusing. You simply cannot use such functions in a network service such as the mongoose-based web app the asker is almost certainly working on. Please only post asynchronous examples unless you are certain synchronous is appropriate, but it almost never is.
  • lostintranslation
    lostintranslation about 11 years
    Also seems like the latest NIST recommendation it to use pbkdf2 instead of bcrypt. Although not meaning to start a religious war.
  • TwentyMiles
    TwentyMiles about 11 years
    You are absolutely correct that NIST recommends pbkdf2. However, for reasons explained pretty well in this question, bcrypt can be more secure in certain situations.
  • Peter Lyons
    Peter Lyons about 11 years
    @TwentyMiles it's not about speed, it's about single threaded. In a multiuser network service like a webapp, the entire node process will halt and do nothing other than the cpu-intensive crypto stuff if you use the synchronous APIs. This will cause ALL other network clients to experience the server freezing. You simply cannot use any synchronous calls in a network server. It fundamentally breaks the #1 basic concept of why node works at all. See: stackoverflow.com/questions/16827373/…
  • slevin
    slevin over 8 years
    @Matthew Hi. Can you give an example code , how to validate the password? Salt confuses me. I manually join the salt and hash string and save it. I know that salt is, say, 10 characters. I get the first 10 characters of the saved salt+hash to get the salt. I use pbkdf2 to hash the incoming, to-be-validated password, with the same salt. If original salt+hash = to-be-validated salt+hash then I let the user in? Is that it?
  • Green
    Green about 8 years
    bcrypt is nothing but a whole slew of errors. It doesn't install anyhow.
  • Jonas Drotleff
    Jonas Drotleff almost 8 years
    @PeterLyons your arguments concerning the speed of bcrypt are true. Anyway, they are not relevant as the whole point of bcrypt is not only that it can't be cracked with rainbow tables but also that it is more difficult to brute force. It is slow and that is why it is so effective. As computers and computing power increases, you can just modify the salt rounds. A good read: codahale.com/how-to-safely-store-a-password
  • Peter Lyons
    Peter Lyons almost 8 years
    @JonasDrotleff you are not understanding the context. I'm talking about async vs sync which is the difference between making 1 user wait for a slow encrypt (async) vs making EVERY client wait for any encrypt (sync). The point is "you must not use synchronous calls, at all, in a node.js network server". The fact that the calls in this case are crypto is just circumstantial.
  • Master James
    Master James over 5 years
    Note: Now days SHA1 is not collision proof enough.