const SECOND = 1; const MINUTE = SECOND * 60; const HOUR = MINUTE * 60; const DAY = HOUR * 24; const MONTH = DAY * 31; const YEAR = MONTH * 12; const CENTURY = YEAR * 100; const times = { second: SECOND, minute: MINUTE, hour: HOUR, day: DAY, month: MONTH, year: YEAR, century: CENTURY }; const timeEstimationValuesDefaults = { scoring: { 0: 1e3, 1: 1e6, 2: 1e8, 3: 1e10 }, attackTime: { onlineThrottlingXPerHour: 100, onlineNoThrottlingXPerSecond: 10, offlineSlowHashingXPerSecond: 1e4, offlineFastHashingXPerSecond: 1e10 } }; const checkTimeEstimationValues = timeEstimationValues => { Object.entries(timeEstimationValues).forEach(([key, data]) => { Object.entries(data).forEach(([subKey, value]) => { // @ts-expect-error for testing purposes if (value < timeEstimationValuesDefaults[key][subKey]) { throw new Error('Time estimation values are not to be allowed to be less than default'); } }); }); }; /* * ------------------------------------------------------------------------------- * Estimates time for an attacker --------------------------------------------------------------- * ------------------------------------------------------------------------------- */ class TimeEstimates { constructor(options) { this.options = options; } estimateAttackTimes(guesses) { const crackTimesSeconds = this.calculateCrackTimesSeconds(guesses); const crackTimes = {}; Object.keys(crackTimesSeconds).forEach(crackTime => { const usedScenario = crackTime; const seconds = crackTimesSeconds[usedScenario]; const { base, displayStr } = this.displayTime(seconds); crackTimes[usedScenario] = { base, seconds, display: this.translate(displayStr, base) }; }); return { crackTimes, score: this.guessesToScore(guesses) }; } calculateCrackTimesSeconds(guesses) { const attackTimesOptions = this.options.timeEstimationValues.attackTime; return { onlineThrottlingXPerHour: guesses / (attackTimesOptions.onlineThrottlingXPerHour / 3600), onlineNoThrottlingXPerSecond: guesses / attackTimesOptions.onlineNoThrottlingXPerSecond, offlineSlowHashingXPerSecond: guesses / attackTimesOptions.offlineSlowHashingXPerSecond, offlineFastHashingXPerSecond: guesses / attackTimesOptions.offlineFastHashingXPerSecond }; } guessesToScore(guesses) { const scoringOptions = this.options.timeEstimationValues.scoring; const DELTA = 5; if (guesses < scoringOptions[0] + DELTA) { // risky password: "too guessable" return 0; } if (guesses < scoringOptions[1] + DELTA) { // modest protection from throttled online attacks: "very guessable" return 1; } if (guesses < scoringOptions[2] + DELTA) { // modest protection from unthrottled online attacks: "somewhat guessable" return 2; } if (guesses < scoringOptions[3] + DELTA) { // modest protection from offline attacks: "safely unguessable" // assuming a salted, slow hash function like bcrypt, scrypt, PBKDF2, argon, etc return 3; } // strong protection from offline attacks under same scenario: "very unguessable" return 4; } displayTime(seconds) { let displayStr = 'centuries'; let base = null; const timeKeys = Object.keys(times); const foundIndex = timeKeys.findIndex(time => seconds < times[time]); if (foundIndex > -1) { displayStr = timeKeys[foundIndex - 1]; if (foundIndex !== 0) { base = Math.round(seconds / times[displayStr]); } else { displayStr = 'ltSecond'; } } return { base, displayStr }; } translate(displayStr, value) { let key = displayStr; if (value !== null && value !== 1) { key += 's'; } const { timeEstimation } = this.options.translations; const translation = timeEstimation[key]; if (typeof translation === 'function') { return translation(value); } return translation.replace('{base}', `${value}`); } } export { TimeEstimates, checkTimeEstimationValues, timeEstimationValuesDefaults }; //# sourceMappingURL=TimeEstimates.mjs.map