Files
SnowLuma/.pnpm-store/v11/files/e1/4a4ca9895aaf9c4093035afc40cca44a6045987ede8a85503714d0bf947b21926a5e59fd5568440069d0bc1e1539bca825039552ea5d5e995457ec7d87b7c0
T
TanTan 2a69b23b47
Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

159 lines
4.5 KiB
Plaintext

import Options from './Options'
import {
CrackTimes,
CrackTimesSeconds,
Score,
TimeEstimationValues,
} from './types'
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,
}
export const timeEstimationValuesDefaults: TimeEstimationValues = {
scoring: {
0: 1e3,
1: 1e6,
2: 1e8,
3: 1e10,
},
attackTime: {
onlineThrottlingXPerHour: 100,
onlineNoThrottlingXPerSecond: 10,
offlineSlowHashingXPerSecond: 1e4,
offlineFastHashingXPerSecond: 1e10,
},
}
export const checkTimeEstimationValues = (
timeEstimationValues: 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 ---------------------------------------------------------------
* -------------------------------------------------------------------------------
*/
export class TimeEstimates {
constructor(private options: Options) {}
public estimateAttackTimes(guesses: number) {
const crackTimesSeconds = this.calculateCrackTimesSeconds(guesses)
const crackTimes = {} as CrackTimes
Object.keys(crackTimesSeconds).forEach((crackTime) => {
const usedScenario = crackTime as keyof CrackTimesSeconds
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),
}
}
private calculateCrackTimesSeconds(guesses: number): CrackTimesSeconds {
const attackTimesOptions = this.options.timeEstimationValues.attackTime
return {
onlineThrottlingXPerHour:
guesses / (attackTimesOptions.onlineThrottlingXPerHour / 3600),
onlineNoThrottlingXPerSecond:
guesses / attackTimesOptions.onlineNoThrottlingXPerSecond,
offlineSlowHashingXPerSecond:
guesses / attackTimesOptions.offlineSlowHashingXPerSecond,
offlineFastHashingXPerSecond:
guesses / attackTimesOptions.offlineFastHashingXPerSecond,
}
}
private guessesToScore(guesses: number): Score {
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
}
private displayTime(seconds: number) {
let displayStr = 'centuries'
let base = null
const timeKeys = Object.keys(times)
const foundIndex = timeKeys.findIndex(
(time) => seconds < times[time as keyof typeof times],
)
if (foundIndex > -1) {
displayStr = timeKeys[foundIndex - 1]
if (foundIndex !== 0) {
base = Math.round(seconds / times[displayStr as keyof typeof times])
} else {
displayStr = 'ltSecond'
}
}
return {
base,
displayStr,
}
}
private translate(displayStr: string, value: number | null) {
let key = displayStr
if (value !== null && value !== 1) {
key += 's'
}
const { timeEstimation } = this.options.translations
const translation = timeEstimation[key as keyof typeof timeEstimation]
if (typeof translation === 'function') {
return translation(value!)
}
return translation.replace('{base}', `${value}`)
}
}