Reputation: 1385
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
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
Reputation: 2957
using library bcrypt its easy to generate password hash.
npm install --save bcrypt
then include the library
const bcrypt = require( 'bcrypt' );
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
}
});
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
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