55

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?
}
gevorg
  • 4,835
  • 4
  • 35
  • 52
lostintranslation
  • 23,756
  • 50
  • 159
  • 262

6 Answers6

77

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);
}
Matthew
  • 24,703
  • 9
  • 76
  • 110
  • ah so I have to store the salt created for each user as well as the hashed password? – lostintranslation Jun 19 '13 at 21:21
  • 6
    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. – Matthew Jun 19 '13 at 21:23
  • @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? – slevin Dec 26 '15 at 00:25
  • In 2022, use `crypto.pbkdf2(password, salt, iterations, 128, 'sha256')` to generate the hash, and to compare use `crypto.timingSafeEqual()` – Francisco Gomes Dec 14 '22 at 21:21
  • Why on EARTH would you publish pseudo code for your answer? What is the actual code I can copy and paste into my project, including imports and npm install/pnpm install? Also, why on Earth would you use 128-byte salt? A 256-bit/64-byte salt is more than long enough to stop dictionary attacks. To store your passwords in an SQL database it's best to memory align your tables to optimize load times from the disk-RAM cache, so you should probably use a 62-byte salt. The 256-bit/64-byte password has a null term char so 65+63 = 128, which is memory aligned. – Cale McCollough Aug 15 '23 at 22:46
  • @CaleMcCollough I posted pseudo code so people can focus on the logic instead of libraries. This answer was posted over a decade ago, and libraries at the time may not have the same signature as libraries today. The answer also makes no assumptions about how the hashes are stored, perhaps you can submit your own answer to expand upon the concerns you outlined. – Matthew Aug 16 '23 at 02:01
32

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.

Community
  • 1
  • 1
TwentyMiles
  • 4,063
  • 3
  • 30
  • 37
  • 6
    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. – Peter Lyons Jun 20 '13 at 02:49
  • 1
    Also seems like the latest NIST recommendation it to use pbkdf2 instead of bcrypt. Although not meaning to start a religious war. – lostintranslation Jun 20 '13 at 13:11
  • You are absolutely correct that NIST recommends pbkdf2. However, for reasons [explained pretty well in this question](http://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage), bcrypt can be more secure in certain situations. – TwentyMiles Jun 20 '13 at 15:06
  • 6
    @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: http://stackoverflow.com/questions/16827373/should-i-use-the-async-file-io-methods-over-their-synchronous-equivalents-for-lo/16827473#16827473 – Peter Lyons Jun 20 '13 at 15:58
  • `bcrypt` is nothing but a whole slew of errors. It doesn't install anyhow. – Green Jun 20 '16 at 06:32
  • @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: https://codahale.com/how-to-safely-store-a-password/ – Jonas Aug 18 '16 at 16:51
  • 3
    @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. – Peter Lyons Aug 18 '16 at 22:04
12

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.

hobbs
  • 223,387
  • 19
  • 210
  • 288
12

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)
        );
      }
    );
  });
}
Esteban Borai
  • 2,311
  • 1
  • 21
  • 27
Andre Pena
  • 56,650
  • 48
  • 196
  • 243
7

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.

florian
  • 391
  • 3
  • 6
4

There are two major steps involved in this scenario

1) Creating and Storing password

Here you will have to do the following.

  • Take the user password
  • Generate a string of random chars (salt)
  • Combine the salt with the user entered password
  • Hash the combined string.
  • Store the hash and the salt in the database.

2) Validating user password

This step would be required to authenticate the user.

  • The user will enter the username/email and the password.

  • Fetch the hash and the salt based on the username entered

  • Combine the salt with the user password

  • Hash the combination with the same hashing algorithm.

  • Compare the result.

This tutorial has a detailed explaination on how to do it with nodejs crypto. Exactly what you are looking for. Salt Hash passwords using NodeJS crypto

rahil471
  • 304
  • 2
  • 7