143 lines
4.3 KiB
Plaintext
143 lines
4.3 KiB
Plaintext
'use strict';
|
|
|
|
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}`);
|
|
}
|
|
}
|
|
|
|
exports.TimeEstimates = TimeEstimates;
|
|
exports.checkTimeEstimationValues = checkTimeEstimationValues;
|
|
exports.timeEstimationValuesDefaults = timeEstimationValuesDefaults;
|
|
//# sourceMappingURL=TimeEstimates.cjs.map
|