/**
* @file Key Derivation Function (KDF) Setup
* @copyright Multifactor 2022 All Rights Reserved
*
* @description
* Validate and setup a KDF configuration for a multi-factor derived key
*
* @author Vivek Nair (https://nair.me) <[email protected]>
*/
const defaults = require('../defaults')
/**
* Validate and setup a KDF configuration for a multi-factor derived key
*
* @example
* // setup kdf configuration
* const config = await mfkdf.setup.kdf({
* kdf: 'pbkdf2',
* pbkdf2rounds: 100000,
* pbkdf2digest: 'sha256'
* }); // -> { type: 'pbkdf2', params: { rounds: 100000, digest: 'sha256' } }
*
* // derive key
* const key = await mfkdf.kdf('password', 'salt', 8, config);
* key.toString('hex') // -> 0394a2ede332c9a1
*
* @param {Object} [options] - KDF configuration options
* @param {string} [options.kdf='argon2id'] - KDF algorithm to use; hkdf, pbkdf2, bcrypt, scrypt, argon2i, argon2d, or argon2id
* @param {string} [options.hkdfdigest='sha256'] - Hash function to use if using hkdf; sha1, sha256, sha384, or sha512
* @param {number} [options.pbkdf2rounds=310000] - Number of rounds to use if using pbkdf2
* @param {string} [options.pbkdf2digest='sha256'] - Hash function to use if using pbkdf2; sha1, sha256, sha384, or sha512
* @param {number} [options.bcryptrounds=10] - Number of rounds to use if using bcrypt
* @param {number} [options.scryptcost=16384] - Iterations count (N) to use if using scrypt
* @param {number} [options.scryptblocksize=8] - Block size (r) to use if using scrypt
* @param {number} [options.scryptparallelism=1] - Parallelism factor (p) to use if using scrypt
* @param {number} [options.argon2time=2] - Iterations to use if using argon2
* @param {number} [options.argon2mem=24576] - Memory to use if using argon2
* @param {number} [options.argon2parallelism=1] - Parallelism to use if using argon2
* @returns {object} A KDF configuration as a JSON object
* @author Vivek Nair (https://nair.me) <[email protected]>
* @since 0.7.0
* @memberOf setup
*/
function kdf (options) {
options = Object.assign(Object.assign({}, defaults.kdf), options)
if (typeof options.kdf !== 'string') throw new TypeError('kdf must be a string')
const config = {
type: options.kdf,
params: {}
}
if (options.kdf === 'hkdf') {
// hdkf digest
if (typeof options.hkdfdigest !== 'string') throw new TypeError('hkdfdigest must be a string')
if (!['sha1', 'sha256', 'sha384', 'sha512'].includes(options.hkdfdigest)) throw new RangeError('hkdfdigest must be one of sha1, sha256, sha384, or sha512')
config.params.digest = options.hkdfdigest
} else if (options.kdf === 'pbkdf2') {
// pbkdf2 rounds
if (!(Number.isInteger(options.pbkdf2rounds))) throw new TypeError('pbkdf2rounds must be an integer')
if (!(options.pbkdf2rounds > 0)) throw new RangeError('pbkdf2rounds must be positive')
config.params.rounds = options.pbkdf2rounds
// pbkdf2 digest
if (typeof options.pbkdf2digest !== 'string') throw new TypeError('pbkdf2digest must be a string')
if (!['sha1', 'sha256', 'sha384', 'sha512'].includes(options.pbkdf2digest)) throw new RangeError('pbkdf2digest must be one of sha1, sha256, sha384, or sha512')
config.params.digest = options.pbkdf2digest
} else if (options.kdf === 'bcrypt') {
// bcrypt rounds
if (!(Number.isInteger(options.bcryptrounds))) throw new TypeError('bcryptrounds must be an integer')
if (!(options.bcryptrounds > 0)) throw new RangeError('bcryptrounds must be positive')
config.params.rounds = options.bcryptrounds
} else if (options.kdf === 'scrypt') {
// scrypt rounds
if (!(Number.isInteger(options.scryptcost))) throw new TypeError('scryptcost must be a positive integer')
if (!(options.scryptcost > 0)) throw new RangeError('scryptcost must be positive')
config.params.rounds = options.scryptcost
// scrypt block size
if (!(Number.isInteger(options.scryptblocksize))) throw new TypeError('scryptblocksize must be an integer')
if (!(options.scryptblocksize > 0)) throw new RangeError('scryptblocksize must be positive')
config.params.blocksize = options.scryptblocksize
// scrypt parallelism
if (!(Number.isInteger(options.scryptparallelism))) throw new TypeError('scryptparallelism must be an integer')
if (!(options.scryptparallelism > 0)) throw new RangeError('scryptparallelism must be positive')
config.params.parallelism = options.scryptparallelism
} else if (options.kdf === 'argon2i' || options.kdf === 'argon2d' || options.kdf === 'argon2id') {
// argon2 rounds
if (!(Number.isInteger(options.argon2time))) throw new TypeError('argon2time must be an integer')
if (!(options.argon2time > 0)) throw new RangeError('argon2time must be positive')
config.params.rounds = options.argon2time
// argon2 memory
if (!(Number.isInteger(options.argon2mem))) throw new TypeError('argon2mem must be an integer')
if (!(options.argon2mem > 0)) throw new RangeError('argon2mem must be positive')
config.params.memory = options.argon2mem
// argon2 parallelism
if (!(Number.isInteger(options.argon2parallelism))) throw new TypeError('argon2parallelism must be an integer')
if (!(options.argon2parallelism > 0)) throw new RangeError('argon2parallelism must be positive')
config.params.parallelism = options.argon2parallelism
} else {
throw new RangeError('kdf must be one of pbkdf2, bcrypt, scrypt, argon2i, argon2d, or argon2id')
}
return config
}
module.exports.kdf = kdf