import crypto from 'crypto' ; import pify from 'pify' ; export const HASH_PASSWORD_VERSION = 1 ; const DEFAULT_HASH_ITERATIONS = 100000 ; const DEFAULT_KEY_LENGTH = 64 ; const DEFAULT_COMPONENT_COUNT = 4 ; param password param iterations param salt returns async function generatePasswordHash ( password , iterations , salt , keylen ) { return ( await pify ( crypto . pbkdf2 ) ( password , salt , iterations , keylen , 'sha512' ) ) as Buffer ; } /**
 * stores and manages password hashes
 */ export class HashContainer { param password returns public static async create ( password , iterations = DEFAULT_HASH_ITERATIONS , salt = null , keylen = DEFAULT_KEY_LENGTH ) { if ( salt == null ) { salt = crypto . randomBytes ( 16 ) . toString ( 'hex' ) ; } const hash = await generatePasswordHash ( password , iterations , salt , keylen ) ; return new HashContainer ( salt , iterations , hash ) ; } param composedHash returns public static from ( composedHash ) { const hashComponents = composedHash . split ( ':' ) ; if ( hashComponents . length !== DEFAULT_COMPONENT_COUNT ) { throw new Error ( `The target's hash length ${ hashComponents . length } is invalid` ) ; } const passwordVersion = Number ( hashComponents [ 0 ] ) ; if ( passwordVersion !== HASH_PASSWORD_VERSION ) { throw new Error ( `The target's hash version ${ passwordVersion } is different from ours ${ HASH_PASSWORD_VERSION }` ) ; } const salt = hashComponents [ 1 ] ; const iterations = Number ( hashComponents [ 2 ] ) ; const hash = Buffer . from ( hashComponents [ 3 ] , 'hex' ) ; return new HashContainer ( salt , iterations , hash ) ; } private salt : string ; private iterations : number ; private hash : Buffer ; protected constructor ( salt , iterations , hash ) { this . salt = salt ; this . iterations = iterations ; this . hash = hash ; } param right returns public compare ( right ) { if ( this . salt !== right . salt || this . iterations !== right . iterations ) { return false ; } return crypto . timingSafeEqual ( this . hash , right . hash ) ; } param password returns public async cloneSettings ( password ) { return await HashContainer . create ( password , this . iterations , this . salt ) ; } returns public build ( ) { return `${ HASH_PASSWORD_VERSION }:${ this . salt }:${ this . iterations }:${ this . hash . toString ( 'hex' ) }` ; } }