Skip to content

Instantly share code, notes, and snippets.

@Hammster
Forked from skeggse/crypto-pbkdf2-example.js
Last active September 2, 2020 12:48
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Hammster/5ea6ec71fef5cc16f7834f79130ea3a0 to your computer and use it in GitHub Desktop.
Save Hammster/5ea6ec71fef5cc16f7834f79130ea3a0 to your computer and use it in GitHub Desktop.
Updated Example of using crypto.pbkdf2 to hash and verify passwords asynchronously, while storing the hash and salt in a single combined buffer along with the original hash settings
const crypto = require('crypto')
// larger numbers mean better security, less
const config = {
// size of the generated hash
hashBytes: 32,
// larger salt means hashed passwords are more resistant to rainbow table, but
// you get diminishing returns pretty fast
saltBytes: 16,
// A selected HMAC digest algorithm specified by digest is applied to derive
// a key of the requested byte length (keylen) from the password, salt and
// iterations.
// - sha512, sha256
// - whirlpool
// and more.
digest: 'whirlpool',
// more iterations means an attacker has to take longer to brute force an
// individual password, so larger is better. however, larger also means longer
// to hash the password. tune so that hashing the password takes about a
// second
iterations: 65535
}
// Example user with a hased value, comment to the right contains the password string
const users = {
'hans': '00000010000bde31e9f9c2ec7d4fad27a5673a7db4cdeaf3957f007a4a0f5f5eb8271ac183fc15828db0f6b58cc91a990c4d737f8cf7300b'// 'pѬѬasѪ"§§)("!编/)$=?!°&%)?§"$(§sw汉字编码§"$(§sw汉字方法orФdpѬѬasѪ"§§)("!/)$=?!°&%)?编码方法orФd'
}
/**
* Hash a password using Node's asynchronous pbkdf2 (key derivation) function.
*
* Returns a self-contained buffer which can be arbitrarily encoded for storage
* that contains all the data needed to verify a password.
*
* @param {!String} password
* @param {!function(?Error, ?Buffer=)} callback
*/
function hashPassword (password, callback) {
// generate a salt for pbkdf2
crypto.randomBytes(config.saltBytes, function (err, salt) {
if (err) {
return callback(err)
}
crypto.pbkdf2(password, salt, config.iterations, config.hashBytes, config.digest, function (err, hash) {
if (err) {
return callback(err)
}
let combined = Buffer.alloc(hash.length + salt.length + 8)
// include the size of the salt so that we can, during verification,
// figure out how much of the hash is salt
combined.writeUInt32BE(salt.length, 0, true)
// similarly, include the iteration count
combined.writeUInt32BE(config.iterations, 4, true)
salt.copy(combined, 8)
hash.copy(combined, salt.length + 8)
callback(null, combined)
})
})
}
/**
* Verify a password using Node's asynchronous pbkdf2 (key derivation) function.
*
* Accepts a hash and salt generated by hashPassword, and returns whether the
* hash matched the password (as a boolean).
*
* @param {!String} password
* @param {!Buffer} combined Buffer containing hash and salt as generated by
* hashPassword.
* @param {!function(?Error, !boolean)}
*/
function verifyPassword (password, combined, callback) {
let buffer = Buffer.from(combined, 'hex')
// extract the salt and hash from the combined buffer
let saltBytes = buffer.readUInt32BE(0)
let hashBytes = buffer.length - saltBytes - 8
let iterations = buffer.readUInt32BE(4)
let salt = buffer.slice(8, saltBytes + 8)
let hash = buffer.toString('binary', saltBytes + 8)
let digest = config.digest
// verify the salt and hash against the password
crypto.pbkdf2(password, salt, iterations, hashBytes, digest, function (err, verify) {
if (err) {
return callback(err, false)
}
callback(null, verify.toString('binary') === hash)
})
}
hashPassword('pѬѬasѪ"§§)("!编/)$=?!°&%)?§"$(§sw汉字编码§"$(§sw汉字方法orФdpѬѬasѪ"§§)("!/)$=?!°&%)?编码方法orФd', function (err, hash) {
if (err) {
return 1
}
// example hash that can be used to validate the password
console.log(hash)
})
verifyPassword('pѬѬasѪ"§§)("!编/)$=?!°&%)?§"$(§sw汉字编码§"$(§sw汉字方法orФdpѬѬasѪ"§§)("!/)$=?!°&%)?编码方法orФd', users.hans.toString(), function (err, correct) {
if (err) {
return 1
}
console.log(correct)
})
/*
exports.hashPassword = hashPassword;
exports.verifyPassword = verifyPassword;
*/
@Hammster
Copy link
Author

Basically, like the original version just updated to current nodeJs API changes and in standardjs feel free to suggest additions and stuff.

@iverenshaguy
Copy link

iverenshaguy commented Oct 7, 2017

@Hammster Hi. I'm new to NodeJs and Buffers. I've tried to understand what you have written so far but I fail to understand why you chose the different positions for adding the Salt Length, Iterations, Salt and Hash to the combined buffer. Why does the "salt length" unsigned-integer start at 0, the iteration count at 4, the salt itself at 8 and the hash at "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);

Thank you so much.

@Hammster
Copy link
Author

Hammster commented Nov 20, 2017

@iverenshaguy hi you can think of a buffer as row of bits grouped in bytes, you can actually console log them to see what is happening ;)

Buffer.alloc(hash.length + salt.length + 8)
<Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... >

salt.length: 16 (0x10)
combined.writeUInt32BE(salt.length, 0, true)
<Buffer 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... >

config.iterations: 65535 (0xffff)
combined.writeUInt32BE(config.iterations, 4, true)
<Buffer 00 00 00 10 00 00 ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... >

salt.copy(combined, 8) (0xf9b28637206c2b555befd5f73ec3ee5c)
<Buffer 00 00 00 10 00 00 ff ff f9 b2 86 37 20 6c 2b 55 5b ef d5 f7 3e c3 ee 5c 00 00 00 00 00 00 00 ... >

hash.copy(combined, salt.length + 8) (0x1bd3a7eac6d18d...)
<Buffer 00 00 00 10 00 00 ff ff f9 b2 86 37 20 6c 2b 55 5b ef d5 f7 3e c3 ee 5c 1b d3 a7 ea c6 d1 8d ... >

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment