From Nodejs v10, crypto module has a built-in implementation of scrypt algorithm that can be used as a password hashing algorithm. To the best of my knowledge, the state-of-art algorithm to hash and store passwords in Nodejs is bcrypt. bcrypt is a very popular module in NPM with nearly half a million downloads per week. I'm not a security expert to tell which one is better, but if you want to use Scrypt as another powerful hash algorithm, its simple:
Hash Password
Scrypt is a salted hashing algorithm. In order to hash passwords using Scrypt you need to create a unique salt on every hash.
The salt should be as unique as possible. It is recommended that a salt is random and at least 16 bytes long. See NIST SP 800-132 for
const crypto = require("crypto")
async function hash(password) {
return new Promise((resolve, reject) => {
// generate random 16 bytes long salt
const salt = crypto.randomBytes(16).toString("hex")
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) reject(err);
resolve(salt + ":" + derivedKey.toString('hex'))
});
})
}
It is important to save generated salt with your hash, because without the salt there is no way to verify the password, and yes, you can store the salt in plaintex.
Check Password
As I said before we need salt to verify password. The salt can be extraced from result of hash
function.
async function verify(password, hash) {
return new Promise((resolve, reject) => {
const [salt, key] = hash.split(":")
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) reject(err);
resolve(key == derivedKey.toString('hex'))
});
})
}
Putting all together
const crypto = require("crypto")
async function hash(password) {
return new Promise((resolve, reject) => {
const salt = crypto.randomBytes(8).toString("hex")
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) reject(err);
resolve(salt + ":" + derivedKey.toString('hex'))
});
})
}
async function verify(password, hash) {
return new Promise((resolve, reject) => {
const [salt, key] = hash.split(":")
crypto.scrypt(password, salt, 64, (err, derivedKey) => {
if (err) reject(err);
resolve(key == derivedKey.toString('hex'))
});
})
}
(async function run () {
const password1 = await hash("123456")
const password2 = await hash("123456")
console.log("password1", await verify("123456", password1));
console.log("password2", await verify("123456", password2));
console.log("password1 == password2", password1 == password2);
})()
And here is the result:
password1 true
password2 true
password1 == password2 false
As you can see from the result, hashing single string with different salts results different outputs, but they both can verified.
Discussion
It's probably best to use
crypto.timingSafeEqual(a, b)
to compare the keys in theverify
function to protect against timing attacks.Thanks !
Cool, Thanks man !
Thanks, much appreciated!