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 :
- Is it ok if I save the
combined
from thehashPassword
as text in
the DB? (column type text) - I have to convert from text back in Buffer in the
verifyPassword
—hash = Buffer.from(hash, 'hex');
part (that’s becausetimingSafeEqual
only accepts buffer). Is this ok? Does it take a lot of time? - Is using text ok, or should I use and save buffer for this?
Any suggestions? Thank you
Solution
- Is it ok if I save the
combined
from thehashPassword
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.
- 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.
- 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.