import * as crypto from 'crypto' import { IPasswordHasher } from './IPasswordHasher' export type Pbkdf2Algorithm = 'hmac-sha1' | 'hmac-sha256' | 'hmac-sha512' export class Pbkdf2PasswordHasher implements IPasswordHasher { constructor(private algorithm: Pbkdf2Algorithm, private iterations: number, private length: number) {} public async hash( password: string, algorithm?: Pbkdf2Algorithm, iterations?: number, length?: number, salt?: Buffer ): Promise { const algorithm2 = algorithm || this.algorithm const iterations2 = iterations || this.iterations const length2 = length || this.length const salt2 = salt || (await new Promise((resolve, reject) => crypto.randomBytes(32, (err, buf) => (err ? reject(err) : resolve(buf))) )) const hash = await new Promise((resolve, reject) => crypto.pbkdf2( password, salt2, iterations2, Math.ceil(length2 / 8), algorithm2.substr(5), (err, buf) => (err ? reject(err) : resolve(buf)) ) ) return ( algorithm2 + ':' + iterations2.toString() + ':' + length2.toString() + ':' + salt2.toString('base64') + ':' + hash.toString('base64') ) } public async verify(password: string, passwordHash: string): Promise { const match = passwordHash.match(/^([^:]+):([^:]+):([^:]+):([^:]+):([^:]+)$/) if (match) { const [_all, algorithm, iterationsString, lengthString, saltString, _hashString] = match return ( passwordHash === (await this.hash( password, algorithm as Pbkdf2Algorithm, parseInt(iterationsString, 10), parseInt(lengthString, 10), new Buffer(saltString, 'base64') )) ) } else { return false } } }