Hash and check passwords in node.js using the native pbkdf2

Posted on

Problem

I use the native crypto . I use the pbkdf2 and the randomBytes for salting, and the timingSafeEqual to check for the password validity when logging in.

I wrote the following functions, based on various examples and the aforementioned APIs and functions. Here is my code

(stack : node 8.11.1 + express 4.16.3 + PostgreSQL 10)

const crypto = require('crypto');

var config = {
  hashBytes: 32,
  saltBytes: 16,
  iterations: 10000,
  digest : 'SHA512'
};

const  hashPassword = (password) =>{
  return new Promise((resolved, rejeted)=>{
    crypto.randomBytes(config.saltBytes, (err, salt) =>{
      if (err){return rejeted('Error while sign in');}
      salt = salt.toString('hex');
      crypto.pbkdf2(password, salt, config.iterations, config.hashBytes, config.digest,(err, hash) =>{
        if (err) {return rejeted('Error while sign in');}
        hash = hash.toString('hex');
        const combined = [salt, hash, config.iterations, config.hashBytes, config.digest].join('$');
        resolved(combined);
      });//closes pbkdf2
    });//closes randomBytes
  })//closes Promise
}//closes hashPassword

const  verifyPassword = (password,original) =>{
  return new Promise((resolved, rejeted)=>{
    let hash = original.split('$')[1];
    let salt = original.split('$')[0];
    let iterations = Number(original.split('$')[2]);
    let hashBytes = Number(original.split('$')[3]);
    let digest = original.split('$')[4];
    hash = Buffer.from(hash, 'hex');

    crypto.pbkdf2(password, salt, iterations, hashBytes, digest, (err, verify) =>{
      if (err){return rejeted('Error while logging in');}
      let equals = crypto.timingSafeEqual(hash, verify);
      resolved(equals);
    });
  })//closes Promise
}//closes verifyPassword

exports.hashPassword = hashPassword;
exports.verifyPassword = verifyPassword;

This works, but, here is what bothers me :

  1. Is it ok if I save the combined from the hashPassword as text in
    the DB? (column type text)
  2. I have to convert from text back in Buffer in the verifyPassword
    hash = Buffer.from(hash, 'hex'); part (that’s because timingSafeEqual only accepts buffer). Is this ok? Does it take a lot of time?
  3. Is using text ok, or should I use and save buffer for this?

Any suggestions? Thank you

Solution

  1. Is it ok if I save the combined from the hashPassword as text in
    the DB? (column type text)

Yes, that’s OK, if you use this to store password hashes. If you use it as a (encryption) key then you should avoid text, as it can be hard to destroy the result.

  1. I have to convert from text back in Buffer in the verifyPassword
    hash = Buffer.from(hash, 'hex'); part (timingSafeEqual only accepts buffer). Is this ok? Does it take a lot of time?

That’s OK. Compared to PBKDF2 almost nothing takes a lot of time.

  1. Is using text ok, or should I use and save buffer for this?

No text is OK, see question #1.


With regards to the code, some notes:

  • Calling split multiple times is not a good idea, call it once and store the intermediate result.
  • As there are no checks on the results after the split, the hash string representation could be altered without notice (impact depends on how the code is used).
  • You could use just a counter to retrieve the various parts after split, and at least create the variables in order (e.g. salt before hash) – storing the hash last makes most sense to me.

I was afraid that you were implementing PBKDF2 yourself, but you seem to be correctly using the proper crypto calls.


A different idea of handling this (for you to ponder over).

Define your protocol somewhere and store a protocol version in your hash string. You could even use that to replace the salt size, iterations, hash type etc.. Then if you choose a higher iteration count you could just update your protocol version.

Leave a Reply

Your email address will not be published. Required fields are marked *