Created
March 15, 2021 09:18
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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