2023-06-25 10:26:29 +02:00
|
|
|
import _ from 'lodash';
|
|
|
|
|
|
|
|
export { getPasswordCrackTimeEstimation, getCharsetLength };
|
|
|
|
|
|
|
|
function prettifyExponentialNotation(exponentialNotation: number) {
|
|
|
|
const [base, exponent] = exponentialNotation.toString().split('e');
|
2023-08-21 18:27:08 +00:00
|
|
|
const baseAsNumber = Number.parseFloat(base);
|
2023-06-25 10:26:29 +02:00
|
|
|
const prettyBase = baseAsNumber % 1 === 0 ? baseAsNumber.toLocaleString() : baseAsNumber.toFixed(2);
|
|
|
|
return exponent ? `${prettyBase}e${exponent}` : prettyBase;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getHumanFriendlyDuration({ seconds }: { seconds: number }) {
|
|
|
|
if (seconds <= 0.001) {
|
|
|
|
return 'Instantly';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (seconds <= 1) {
|
|
|
|
return 'Less than a second';
|
|
|
|
}
|
|
|
|
|
|
|
|
const timeUnits = [
|
2023-08-30 07:50:09 +01:00
|
|
|
{ unit: 'millenium', secondsInUnit: 31536000000, format: prettifyExponentialNotation, plural: 'millenia' },
|
|
|
|
{ unit: 'century', secondsInUnit: 3153600000, plural: 'centuries' },
|
|
|
|
{ unit: 'decade', secondsInUnit: 315360000, plural: 'decades' },
|
|
|
|
{ unit: 'year', secondsInUnit: 31536000, plural: 'years' },
|
|
|
|
{ unit: 'month', secondsInUnit: 2592000, plural: 'months' },
|
|
|
|
{ unit: 'week', secondsInUnit: 604800, plural: 'weeks' },
|
|
|
|
{ unit: 'day', secondsInUnit: 86400, plural: 'days' },
|
|
|
|
{ unit: 'hour', secondsInUnit: 3600, plural: 'hours' },
|
|
|
|
{ unit: 'minute', secondsInUnit: 60, plural: 'minutes' },
|
|
|
|
{ unit: 'second', secondsInUnit: 1, plural: 'seconds' },
|
2023-06-25 10:26:29 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
return _.chain(timeUnits)
|
2023-08-30 07:50:09 +01:00
|
|
|
.map(({ unit, secondsInUnit, plural, format = _.identity }) => {
|
2023-06-25 10:26:29 +02:00
|
|
|
const quantity = Math.floor(seconds / secondsInUnit);
|
|
|
|
seconds %= secondsInUnit;
|
|
|
|
|
|
|
|
if (quantity <= 0) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const formattedQuantity = format(quantity);
|
2023-08-30 07:50:09 +01:00
|
|
|
return `${formattedQuantity} ${quantity > 1 ? plural : unit}`;
|
2023-06-25 10:26:29 +02:00
|
|
|
})
|
|
|
|
.compact()
|
|
|
|
.take(2)
|
|
|
|
.join(', ')
|
|
|
|
.value();
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPasswordCrackTimeEstimation({ password, guessesPerSecond = 1e9 }: { password: string; guessesPerSecond?: number }) {
|
|
|
|
const charsetLength = getCharsetLength({ password });
|
|
|
|
const passwordLength = password.length;
|
|
|
|
|
|
|
|
const entropy = password === '' ? 0 : Math.log2(charsetLength) * passwordLength;
|
|
|
|
|
|
|
|
const secondsToCrack = 2 ** entropy / guessesPerSecond;
|
|
|
|
|
|
|
|
const crackDurationFormatted = getHumanFriendlyDuration({ seconds: secondsToCrack });
|
|
|
|
|
|
|
|
const score = Math.min(entropy / 128, 1);
|
|
|
|
|
|
|
|
return {
|
|
|
|
entropy,
|
|
|
|
charsetLength,
|
|
|
|
passwordLength,
|
|
|
|
crackDurationFormatted,
|
|
|
|
secondsToCrack,
|
|
|
|
score,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCharsetLength({ password }: { password: string }) {
|
|
|
|
const hasLowercase = /[a-z]/.test(password);
|
|
|
|
const hasUppercase = /[A-Z]/.test(password);
|
|
|
|
const hasDigits = /\d/.test(password);
|
|
|
|
const hasSpecialChars = /\W|_/.test(password);
|
|
|
|
|
|
|
|
let charsetLength = 0;
|
|
|
|
|
|
|
|
if (hasLowercase) {
|
|
|
|
charsetLength += 26;
|
|
|
|
}
|
|
|
|
if (hasUppercase) {
|
|
|
|
charsetLength += 26;
|
|
|
|
}
|
|
|
|
if (hasDigits) {
|
|
|
|
charsetLength += 10;
|
|
|
|
}
|
|
|
|
if (hasSpecialChars) {
|
|
|
|
charsetLength += 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
return charsetLength;
|
|
|
|
}
|