user3339562
user3339562

Reputation: 1385

Password Hashing in Node.js

This is my current code for password hashing and saving to the database:

var config = {
  hashBytes: 64,
  saltBytes: 64,
  iterations: 8000
};

crypto.randomBytes(config.saltBytes, function(err, salt) {

  crypto.pbkdf2(password, salt, config.iterations, config.hashBytes,
      function(err, hash) {

          var combined = new Buffer(hash.length + salt.length + 8);

          combined.writeUInt32BE(salt.length, 0, true);
          combined.writeUInt32BE(config.iterations, 4, true);
          salt.copy(combined, 8);
          hash.copy(combined, salt.length + 8);

          callback(combined);
      });
  });

The goal of the code was to save salt together with the hash to one field in database. Is this acceptable way to store a password/salt in the same field in the database? I've found this algorithm long time ago and now so I'm not sure do I understand this well.

As I understand, first we create a buffer which has enough space to store hash, salt, number of iterations and salt length (not sure why are we adding 8 here):

var combined = new Buffer(hash.length + salt.length + 8); 

Then we save salt length byte to position 0:

combined.writeUInt32BE(salt.length, 0, true);

We save iterations position 4 (why 4?):

combined.writeUInt32BE(config.iterations, 4, true);

We save salt to position 8:

salt.copy(combined, 8);

We save hash to position which is the length of salt plus the size where we saved iterations and salt length:

hash.copy(combined, salt.length + 8);

Upvotes: 2

Views: 6919

Answers (3)

Bash Lord
Bash Lord

Reputation: 123

you don't need bcrypt anymore:

const crypto = require('crypto');

const SCRYPT_PARAMS = { N: 32768, r: 8, p: 1, maxmem: 64 * 1024 * 1024 };
const SALT_LEN = 32;
const KEY_LEN = 64;
const SCRYPT_PREFIX = '$scrypt$N=32768,r=8,p=1,maxmem=67108864$';
const HASH_PARTS = 5;

const hashPassword = (password) =>
  new Promise((resolve, reject) => {
    crypto.randomBytes(SALT_LEN, (err, salt) => {
      if (err) return void reject(err);
      crypto.scrypt(password, salt, KEY_LEN, SCRYPT_PARAMS, (err, hash) => {
        if (err) return void reject(err);
        resolve(serializeHash(hash, salt));
      });
    });
  });

const serializeHash = (hash, salt) => {
  const saltString = salt.toString('base64').split('=')[0];
  const hashString = hash.toString('base64').split('=')[0];
  return `${SCRYPT_PREFIX}${saltString}$${hashString}`;
};

const validatePassword = (password, serHash) => {
  const { params, salt, hash } = deserializeHash(serHash);
  return new Promise((resolve, reject) => {
    const callback = (err, hashedPassword) => {
      if (err) return void reject(err);
      resolve(crypto.timingSafeEqual(hashedPassword, hash));
    };
    crypto.scrypt(password, salt, hash.length, params, callback);
  });
};

const deserializeHash = (phcString) => {
  const parts = phcString.split('$');
  if (parts.length !== HASH_PARTS) {
    throw new Error('Invalid format; Expected $name$options$salt$hash');
  }
  const [, name, options, salt64, hash64] = parts;
  if (name !== 'scrypt') {
    throw new Error('Node.js crypto module only supports scrypt');
  }
  const params = parseOptions(options);
  const salt = Buffer.from(salt64, 'base64');
  const hash = Buffer.from(hash64, 'base64');
  return { params, salt, hash };
}; 

Upvotes: 0

Aamer Shahzad
Aamer Shahzad

Reputation: 2957

using library bcrypt its easy to generate password hash.

install & Include

npm install --save bcrypt

then include the library

const bcrypt = require( 'bcrypt' );

Generate & Verify Hash

to generate hash in Asynchronous way use the following method.

bcrypt.hash( 'passwordToHash', 10, function( err, hash ) {
  // Store hash in database
});

10 is the number of rounds to use when generating a salt. to verify password

bcrypt.compare( 'passwordToCompare', hash, function( err, res ) {
  if( res ) {
   // Password matched
  } else {
   // Password didn't match
  } 
});

Generate & Verify Hash

to generate and verify hash in synchronous way use the following method.

let hash = bcrypt.hashSync( 'passwordToHash', 10 );

10 is the number of rounds to use when generating a salt. To verify hash

if( bcrypt.compareSync( 'passwordToCompare', hash ) ) {
   // Password matched
} else {
   // Password didn't match
}

Upvotes: 9

Mike Robinson
Mike Robinson

Reputation: 8995

This code seems to be making the now-dangerous assumption that "an integer is 4 bytes long, therefore two integers are 8 bytes."

In today's 64-bit world, that assumption would no longer be true. And, in any case, we don't need this level of complexity and byte-twiddling!

A far better (and, far simpler) strategy ... (source-code example not shown) ... is to simply store all three values as strings, separated by a known character. For instance, 12:34:5678 being used to store a salt-length of 12, iterations 34, hash-value 5678. The code that stores the value simply concatenates the three strings and stores them in a VARCHAR field of sufficient size. And, in like manner, the code that retrieves and checks the value first "splits" the string into its three constituent parts (e.g. using a regex ...), converts the first two strings to integers, and proceeds to evaluate.

This is preferable for a great many reasons, not the least of which is that a human being, querying the database, can plainly see the three parts. (So, if there's a bug in the code somewhere, s/he can see the consequences of the bug "at a glance.")

Upvotes: 3

Related Questions