Skip to content

Instantly share code, notes, and snippets.

@garbados
Last active May 7, 2021 01:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save garbados/29ca945d5964ef85e7936804c23edb9d to your computer and use it in GitHub Desktop.
Save garbados/29ca945d5964ef85e7936804c23edb9d to your computer and use it in GitHub Desktop.
How to store passwords in a database, with PouchDB and native crypto.
/*
How to securely store a password: by salting and hashing!
Obviously it's risky to store passwords in plaintext. It's even risky to store
them encrypted, because then someone might decrypt them. What you really want to
do is hash the password so that even if someone gets the hash, they can't get
the password.
If you just hash the raw password, an attacker could use a rainbow table to
crack the hash instantly. So you want to modify the hash using some random
values called a *salt*, so an attacker would have to regenerate their rainbow
table using these random values in order to try brute-forcing your password. As
this regeneration process is very, very expensive, this is considered a good way
to obfuscate passwords.
However, regenerating rainbow tables is less expensive than you might imagine.
So, it pays to hash something many many times, which is what `crypto.pbkdf2`
does. In this example code, a password is hashed some hundred thousand times in
order to derive a suitably obscured value -- a *key*. By using this key and its
associated salt, a password can be easily verified without having the password
stored anywhere.
Lastly, it's worth noting you can't just compare these keys with a normal
equality operator like `===`. You need to use a timing-safe equality comparison,
like `crypto.timingSafeEqual`. This prevents what are called *timing attacks*.
*/
const PouchDB = require('pouchdb')
const crypto = require('crypto')
const { promisify } = require('util')
const SALT_LENGTH = 32
const ITERATIONS = 1e5 // 1 and 5 zeroes aka 100,000
const HASH_ALGO = 'sha512'
const KEY_LENGTH = 64
// good ol' pouchdb
const db = new PouchDB('pass-hash-example')
// we need the salt to be VERY RANDOM so we use crypto.randomBytes
const randomBytes = promisify(crypto.randomBytes)
// PBKDF2 turns a password into a cryptographic key for encryption and decryption
// remember: password-based key derivation function! just what it says on the tin.
async function pbkdf2 (password, salt) {
return new Promise((resolve, reject) => {
crypto.pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, HASH_ALGO, (err, key) => {
if (err) { return reject(err) } else { return resolve(key) }
})
})
}
// converts a password into a random salt and a secure, derived key
async function obfuscate (password) {
const salt = await randomBytes(SALT_LENGTH)
const key = await pbkdf2(password, salt, ITERATIONS, KEY_LENGTH, HASH_ALGO)
// convert values from buffers to utf8 hex strings for portability
return { salt: salt.toString('hex'), key: key.toString('hex') }
}
// verify that a password matches the key associated with a given salt
async function verify (password, salt, key) {
// cryptography functions work on bytes. remember to decode from hex!
if (typeof key === 'string') { key = Buffer.from(key, 'hex') }
if (typeof salt === 'string') { salt = Buffer.from(salt, 'hex') }
const derivedKey = await pbkdf2(password, salt)
return crypto.timingSafeEqual(key, derivedKey)
}
Promise.resolve().then(async () => {
// let's mimic a sign-in process!
// garbados creates an account using a very secure password:
const username = 'garbados'
const password = 'horses resources and grundlepick pie'
const { salt, key } = await obfuscate(password)
// now we store the salt and the derived key in our db:
await db.put({ _id: username, salt, key })
// then garbados logs out.
// later, she tries to log back in, so we verify her password using the
// stored salt and key:
const doc = await db.get(username)
const ok = await verify(password, doc.salt, doc.key)
console.log(ok)
}).catch((err) => {
console.trace(err)
}).then(() => {
return db.destroy()
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment