|
import bcrypt from 'bcryptjs'; |
|
import chalk from 'chalk'; |
|
import { performance } from 'node:perf_hooks'; |
|
|
|
//NOTE: must be imported first to setup the environment |
|
import { txEnv, txHostConfig } from './globalData'; |
|
import consoleFactory from '@lib/console'; |
|
const console = consoleFactory('benchmark'); |
|
|
|
|
|
//MARK: Config |
|
const PASSWORD_COUNT = 100; |
|
const BCRYPT_ROUNDS = 11; //native uses 11 rounds |
|
|
|
|
|
//MARK: Helpers |
|
const generateRandomPassword = () => { |
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'; |
|
const length = 12 + Math.floor(Math.random() * 8); // 12-20 chars |
|
let password = ''; |
|
for (let i = 0; i < length; i++) { |
|
password += chars[Math.floor(Math.random() * chars.length)]; |
|
} |
|
return password; |
|
}; |
|
|
|
type BenchmarkResult = { name: string; elapsed: number; avgMs: number; iterations: number }; |
|
|
|
const formatResult = (r: BenchmarkResult) => { |
|
return `${r.name}: ${r.elapsed.toFixed(2)}ms total, ${r.avgMs.toFixed(3)}ms avg (${r.iterations} iterations)`; |
|
}; |
|
|
|
const formatRatio = (ratio: number) => { |
|
if (ratio > 1) return `Native is ${ratio.toFixed(2)}x slower than bcryptjs`; |
|
return `Native is ${(1 / ratio).toFixed(2)}x faster than bcryptjs`; |
|
}; |
|
|
|
|
|
//MARK: Benchmark |
|
const runAllBenchmarks = () => { |
|
// Generate random passwords |
|
const passwords: string[] = []; |
|
for (let i = 0; i < PASSWORD_COUNT; i++) { |
|
passwords.push(generateRandomPassword()); |
|
} |
|
|
|
console.multiline([ |
|
console.DIVIDER, |
|
'Starting Password Hashing Benchmark', |
|
`Password count: ${PASSWORD_COUNT}`, |
|
`bcrypt rounds: ${BCRYPT_ROUNDS}`, |
|
`Sample passwords: "${passwords[0]}", "${passwords[1]}", ...`, |
|
console.DIVIDER, |
|
], chalk.bgBlue); |
|
|
|
// Pre-generate hashes for verification tests |
|
console.log('Generating hashes for verification tests...'); |
|
const nativeHashes: string[] = []; |
|
const bcryptHashes: string[] = []; |
|
for (const password of passwords) { |
|
nativeHashes.push(GetPasswordHash(password)); |
|
bcryptHashes.push(bcrypt.hashSync(password, BCRYPT_ROUNDS)); |
|
} |
|
console.multiline([ |
|
'--- Sample hashes ---', |
|
`Native hash: ${nativeHashes[0]}`, |
|
`bcrypt hash: ${bcryptHashes[0]}`, |
|
], chalk.bgBlue); |
|
|
|
// Hashing benchmarks |
|
const hashStart = performance.now(); |
|
for (const password of passwords) { |
|
GetPasswordHash(password); |
|
} |
|
const nativeHashElapsed = performance.now() - hashStart; |
|
|
|
const bcryptHashStart = performance.now(); |
|
for (const password of passwords) { |
|
bcrypt.hashSync(password, BCRYPT_ROUNDS); |
|
} |
|
const bcryptHashElapsed = performance.now() - bcryptHashStart; |
|
|
|
const nativeHashResult: BenchmarkResult = { |
|
name: 'Native GetPasswordHash', |
|
elapsed: nativeHashElapsed, |
|
avgMs: nativeHashElapsed / PASSWORD_COUNT, |
|
iterations: PASSWORD_COUNT, |
|
}; |
|
const bcryptHashResult: BenchmarkResult = { |
|
name: 'bcryptjs hashSync', |
|
elapsed: bcryptHashElapsed, |
|
avgMs: bcryptHashElapsed / PASSWORD_COUNT, |
|
iterations: PASSWORD_COUNT, |
|
}; |
|
console.multiline([ |
|
'--- Hashing Benchmark ---', |
|
formatResult(nativeHashResult), |
|
formatResult(bcryptHashResult), |
|
], chalk.bgBlue); |
|
|
|
// Verification benchmarks |
|
const nativeVerifyStart = performance.now(); |
|
for (let i = 0; i < PASSWORD_COUNT; i++) { |
|
VerifyPasswordHash(passwords[i], nativeHashes[i]); |
|
} |
|
const nativeVerifyElapsed = performance.now() - nativeVerifyStart; |
|
|
|
const bcryptVerifyStart = performance.now(); |
|
for (let i = 0; i < PASSWORD_COUNT; i++) { |
|
bcrypt.compareSync(passwords[i], bcryptHashes[i]); |
|
} |
|
const bcryptVerifyElapsed = performance.now() - bcryptVerifyStart; |
|
|
|
const nativeVerifyResult: BenchmarkResult = { |
|
name: 'Native VerifyPasswordHash', |
|
elapsed: nativeVerifyElapsed, |
|
avgMs: nativeVerifyElapsed / PASSWORD_COUNT, |
|
iterations: PASSWORD_COUNT, |
|
}; |
|
const bcryptVerifyResult: BenchmarkResult = { |
|
name: 'bcryptjs compareSync', |
|
elapsed: bcryptVerifyElapsed, |
|
avgMs: bcryptVerifyElapsed / PASSWORD_COUNT, |
|
iterations: PASSWORD_COUNT, |
|
}; |
|
console.multiline([ |
|
'--- Verification Benchmark ---', |
|
formatResult(nativeVerifyResult), |
|
formatResult(bcryptVerifyResult), |
|
], chalk.bgBlue); |
|
|
|
// Summary |
|
const hashRatio = nativeHashResult.avgMs / bcryptHashResult.avgMs; |
|
const verifyRatio = nativeVerifyResult.avgMs / bcryptVerifyResult.avgMs; |
|
console.multiline([ |
|
console.DIVIDER, |
|
'Summary (Native vs bcryptjs)', |
|
console.DIVIDER, |
|
`Hashing: ${formatRatio(hashRatio)}`, |
|
`Verification: ${formatRatio(verifyRatio)}`, |
|
], chalk.bgGreen); |
|
|
|
// Cross-verification test (sanity check) |
|
const nativeVerifiesBcrypt = VerifyPasswordHash(passwords[0], bcryptHashes[0]); |
|
const bcryptVerifiesNative = bcrypt.compareSync(passwords[0], nativeHashes[0]); |
|
console.multiline([ |
|
'--- Cross-verification Test ---', |
|
`Native can verify bcrypt hash: ${nativeVerifiesBcrypt}`, |
|
`bcrypt can verify native hash: ${bcryptVerifiesNative}`, |
|
console.DIVIDER, |
|
'Benchmark complete!', |
|
console.DIVIDER, |
|
], chalk.bgGreen); |
|
}; |
|
|
|
// Wait 500ms after boot to avoid first tick and other boot overhead |
|
setTimeout(runAllBenchmarks, 500); |