Skip to content

Instantly share code, notes, and snippets.

@numberoverzero
Created March 15, 2021 09:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save numberoverzero/370c591ade108a65b16691287afdef15 to your computer and use it in GitHub Desktop.
Save numberoverzero/370c591ade108a65b16691287afdef15 to your computer and use it in GitHub Desktop.
compute an optimal bcrypt costfactor for a target hash time on the current machine
import assert from 'assert'
import bcrypt from 'bcrypt'
import crypto from 'crypto'
import { PerformanceObserver, performance } from 'perf_hooks'
const MINIMUM_BCRYPT_COST_FACTOR = 10
const RECOMMENDED_BCRYPT_COST_FACTOR = costFactorForDuration(250)
/**
* Calculate a cost factor based on a target duration (in milliseconds) for hashing.
* Pass this value to {@link bcrypt.genSalt} or {@link bcrypt.hash}
* @param targetDurationMs The time you would like hashing to take on this system.
* @param nTrials Number of trial runs. Minimum duration (highest cost factor) will be selected.
* @returns The minimum cost factor that will consume at least the target duration of milliseconds to compute
* on this machine, based on a number of trials
*/
function costFactorForDuration(targetDurationMs: number, nTrials = 100): number {
let cost = 5 //
let duration: number = Number.MAX_VALUE
const data = crypto.randomBytes(72)
const salt = bcrypt.genSaltSync(cost)
function runTrial(): number {
let trialDuration: number = Number.MAX_VALUE
const obs = new PerformanceObserver((items) => {
assert(items.getEntries().length === 1)
const entry = items.getEntries()[0]
assert(entry.name === '.trialDuration')
trialDuration = entry.duration
})
obs.observe({ entryTypes: ['measure'] })
performance.clearMarks()
performance.mark('.trialStart')
bcrypt.hashSync(data, salt)
performance.mark('.trialEnd')
performance.measure('.trialDuration', '.trialStart', '.trialEnd')
obs.disconnect()
return trialDuration
}
for (let i = 0; i < nTrials; i++) {
duration = Math.min(duration, runTrial())
}
assert(duration > 0, 'error collecting trial data')
assert(duration < Number.MAX_VALUE, 'failed to record timing information')
// Math.ceil so we spend at least targetdurationMs
// Math.max so we never drop below MINIMUM_BCRYPT_COST_FACTOR
cost += Math.ceil(Math.log2(targetDurationMs / duration))
return Math.max(cost, MINIMUM_BCRYPT_COST_FACTOR)
}
export {
costFactorForDuration,
RECOMMENDED_BCRYPT_COST_FACTOR
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment