The slider below controls a bcrypt-style work factor (called cost in bcrypt). Each increment doubles the number of internal rounds. Observe how a modest change in the slider explodes the time an attacker would need to check every possible short password.
<div class="demo">
<div class="controls">
<label for="cost">Work factor (bcrypt cost): <strong id="cost-val">10</strong></label>
<input type="range" id="cost" min="4" max="20" value="10" step="1">
</div>
<div class="controls">
<label for="gpu">Attacker speed: <strong id="gpu-val">10,000</strong> hashes/sec</label>
<input type="range" id="gpu" min="1" max="6" value="4" step="1">
</div>
<div class="result-box" id="result">
<div class="row"><span class="label">Hash rounds per attempt</span><span class="value" id="rounds"></span></div>
<div class="row"><span class="label">Attacker hashes/second</span><span class="value" id="speed"></span></div>
<div class="row"><span class="label">8-char password space (a-z)</span><span class="value">208,827,064,576</span></div>
<div class="row highlight"><span class="label">Time to exhaust space</span><span class="value" id="time"></span></div>
<div class="bar-wrap"><div id="bar" class="bar"></div></div>
</div>
<div class="note" id="note"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; background: #f7f8fa; }
.demo { max-width: 520px; margin: 0 auto; padding: 1rem; }
.controls { margin-bottom: 1rem; }
.controls label { display: block; font-size: .9rem; margin-bottom: .3rem; color: #555; }
.controls input[type=range] { width: 100%; accent-color: #1d3557; }
.result-box { background: #fff; border: 1px solid #dde3ea; border-radius: 10px; padding: 1rem; margin-top: .5rem; }
.row { display: flex; justify-content: space-between; align-items: baseline; padding: .3rem 0; border-bottom: 1px solid #f0f0f0; font-size: .88rem; }
.row:last-of-type { border-bottom: none; }
.label { color: #555; }
.value { font-family: ui-monospace, monospace; font-weight: 700; color: #1d3557; }
.row.highlight .label { color: #1d3557; font-weight: 700; }
.row.highlight .value { color: #0a7d33; font-size: 1rem; }
.bar-wrap { background: #e8eef3; border-radius: 6px; height: 12px; margin-top: .8rem; overflow: hidden; }
.bar { height: 100%; background: linear-gradient(90deg, #0a7d33, #e63946); border-radius: 6px; transition: width .4s; }
.note { margin-top: .7rem; font-size: .83rem; color: #444; line-height: 1.45; min-height: 2.5em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Password-hashing demo: bcrypt-style work factor exponentially scales cost.
// bcrypt cost C means 2^C internal rounds per hash attempt.
// We compute how long an attacker needs to exhaust an 8-char a-z password space.
const SPACE = Math.pow(26, 8); // 26^8 ≈ 208.8 billion — all lowercase 8-char passwords
const costSlider = document.getElementById('cost');
const gpuSlider = document.getElementById('gpu');
const costVal = document.getElementById('cost-val');
const gpuVal = document.getElementById('gpu-val');
const roundsEl = document.getElementById('rounds');
const speedEl = document.getElementById('speed');
const timeEl = document.getElementById('time');
const barEl = document.getElementById('bar');
const noteEl = document.getElementById('note');
// GPU tier labels and hash rates (bcrypt hashes per second for that tier)
// At cost 10, a high-end GPU does ~10k bcrypt/s; lower cost = more, higher cost = fewer.
// We model base rate at cost 10, then scale by 2^(10-C) for the actual cost.
const GPU_TIERS = [
{ label: '100', base: 100 }, // old CPU, cost 10
{ label: '1,000', base: 1000 }, // modern CPU
{ label: '10,000', base: 10000 }, // entry GPU
{ label: '100,000', base: 100000 }, // high-end GPU cluster
{ label: '1,000,000', base: 1e6 }, // dedicated cracking rig
{ label: '10,000,000',base: 1e7 }, // nation-state ASICs (hypothetical — memory-hard would stop this)
];
function fmt(n) {
if (n < 60) return i18nFmt('%n% seconds', { n: n.toFixed(1) });
if (n < 3600) return i18nFmt('%v% minutes', { v: (n / 60).toFixed(1) });
if (n < 86400) return i18nFmt('%v% hours', { v: (n / 3600).toFixed(1) });
if (n < 31536000) return i18nFmt('%v% days', { v: (n / 86400).toFixed(1) });
const yrs = n / 31536000;
if (yrs < 1e6) return i18nFmt('%yrs% years', { yrs: yrs.toFixed(0) });
if (yrs < 1e9) return i18nFmt('%v% million years', { v: (yrs / 1e6).toFixed(2) });
if (yrs < 1e12) return i18nFmt('%v% billion years', { v: (yrs / 1e9).toFixed(2) });
return i18nFmt('%v% trillion years', { v: (yrs / 1e12).toFixed(2) });
}
function fmtNum(n) {
return n.toLocaleString('en-US');
}
function update() {
const C = parseInt(costSlider.value, 10);
const tier = parseInt(gpuSlider.value, 10) - 1;
const gpu = GPU_TIERS[tier];
costVal.textContent = C;
gpuVal.textContent = gpu.label;
// Rounds = 2^C (the core bcrypt iteration count)
const rounds = Math.pow(2, C);
// Effective hashes/sec: base is at cost 10; scale by 2^(10-C)
const effectiveRate = gpu.base * Math.pow(2, 10 - C);
// Time to exhaust the space
const seconds = SPACE / effectiveRate;
roundsEl.textContent = fmtNum(rounds);
speedEl.textContent = fmtNum(Math.round(effectiveRate)) + '/s';
timeEl.textContent = fmt(seconds);
// Bar: map log10(seconds) from 0..log10(1e18) → 0..100%
const logS = Math.log10(Math.max(seconds, 0.001));
const logMax = Math.log10(1e18);
const pct = Math.min(100, Math.max(1, (logS / logMax) * 100));
barEl.style.width = pct + '%';
// Contextual note
const AGE_UNIVERSE_S = 4.3e17;
let note = '';
if (seconds < 3600) {
note = '⚠️ Cracked in under an hour — this work factor is dangerously low for a real application.';
} else if (seconds < 86400 * 7) {
note = '⚠️ Cracked in under a week — suitable only for low-stakes throwaway passwords.';
} else if (seconds < 31536000 * 100) {
note = '✓ Cracking takes years — acceptable for many applications, but consider raising the cost as hardware improves.';
} else if (seconds < AGE_UNIVERSE_S) {
note = '✓✓ Cracking would take longer than human civilization — comfortable for most real-world deployments.';
} else {
note = '✓✓✓ Cracking would take longer than the age of the universe — practically unbreakable for any foreseeable hardware.';
}
noteEl.textContent = note;
// Console log for node verification
console.log('cost=' + C + ' rounds=' + rounds + ' rate=' + Math.round(effectiveRate) + '/s time=' + fmt(seconds));
}
costSlider.addEventListener('input', update);
gpuSlider.addEventListener('input', update);
update();
<div class="demo">
<div class="controls">
<label for="cost">Work factor (bcrypt cost): <strong id="cost-val">10</strong></label>
<input type="range" id="cost" min="4" max="20" value="10" step="1">
</div>
<div class="controls">
<label for="gpu">Attacker speed: <strong id="gpu-val">10,000</strong> hashes/sec</label>
<input type="range" id="gpu" min="1" max="6" value="4" step="1">
</div>
<div class="result-box" id="result">
<div class="row"><span class="label">Hash rounds per attempt</span><span class="value" id="rounds"></span></div>
<div class="row"><span class="label">Attacker hashes/second</span><span class="value" id="speed"></span></div>
<div class="row"><span class="label">8-char password space (a-z)</span><span class="value">208,827,064,576</span></div>
<div class="row highlight"><span class="label">Time to exhaust space</span><span class="value" id="time"></span></div>
<div class="bar-wrap"><div id="bar" class="bar"></div></div>
</div>
<div class="note" id="note"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; background: #f7f8fa; }
.demo { max-width: 520px; margin: 0 auto; padding: 1rem; }
.controls { margin-bottom: 1rem; }
.controls label { display: block; font-size: .9rem; margin-bottom: .3rem; color: #555; }
.controls input[type=range] { width: 100%; accent-color: #1d3557; }
.result-box { background: #fff; border: 1px solid #dde3ea; border-radius: 10px; padding: 1rem; margin-top: .5rem; }
.row { display: flex; justify-content: space-between; align-items: baseline; padding: .3rem 0; border-bottom: 1px solid #f0f0f0; font-size: .88rem; }
.row:last-of-type { border-bottom: none; }
.label { color: #555; }
.value { font-family: ui-monospace, monospace; font-weight: 700; color: #1d3557; }
.row.highlight .label { color: #1d3557; font-weight: 700; }
.row.highlight .value { color: #0a7d33; font-size: 1rem; }
.bar-wrap { background: #e8eef3; border-radius: 6px; height: 12px; margin-top: .8rem; overflow: hidden; }
.bar { height: 100%; background: linear-gradient(90deg, #0a7d33, #e63946); border-radius: 6px; transition: width .4s; }
.note { margin-top: .7rem; font-size: .83rem; color: #444; line-height: 1.45; min-height: 2.5em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Password-hashing demo: bcrypt-style work factor exponentially scales cost.
// bcrypt cost C means 2^C internal rounds per hash attempt.
// We compute how long an attacker needs to exhaust an 8-char a-z password space.
const SPACE = Math.pow(26, 8); // 26^8 ≈ 208.8 billion — all lowercase 8-char passwords
const costSlider = document.getElementById('cost');
const gpuSlider = document.getElementById('gpu');
const costVal = document.getElementById('cost-val');
const gpuVal = document.getElementById('gpu-val');
const roundsEl = document.getElementById('rounds');
const speedEl = document.getElementById('speed');
const timeEl = document.getElementById('time');
const barEl = document.getElementById('bar');
const noteEl = document.getElementById('note');
// GPU tier labels and hash rates (bcrypt hashes per second for that tier)
// At cost 10, a high-end GPU does ~10k bcrypt/s; lower cost = more, higher cost = fewer.
// We model base rate at cost 10, then scale by 2^(10-C) for the actual cost.
const GPU_TIERS = [
{ label: '100', base: 100 }, // old CPU, cost 10
{ label: '1,000', base: 1000 }, // modern CPU
{ label: '10,000', base: 10000 }, // entry GPU
{ label: '100,000', base: 100000 }, // high-end GPU cluster
{ label: '1,000,000', base: 1e6 }, // dedicated cracking rig
{ label: '10,000,000',base: 1e7 }, // nation-state ASICs (hypothetical — memory-hard would stop this)
];
function fmt(n) {
if (n < 60) return i18nFmt('%n% seconds', { n: n.toFixed(1) });
if (n < 3600) return i18nFmt('%v% minutes', { v: (n / 60).toFixed(1) });
if (n < 86400) return i18nFmt('%v% hours', { v: (n / 3600).toFixed(1) });
if (n < 31536000) return i18nFmt('%v% days', { v: (n / 86400).toFixed(1) });
const yrs = n / 31536000;
if (yrs < 1e6) return i18nFmt('%yrs% years', { yrs: yrs.toFixed(0) });
if (yrs < 1e9) return i18nFmt('%v% million years', { v: (yrs / 1e6).toFixed(2) });
if (yrs < 1e12) return i18nFmt('%v% billion years', { v: (yrs / 1e9).toFixed(2) });
return i18nFmt('%v% trillion years', { v: (yrs / 1e12).toFixed(2) });
}
function fmtNum(n) {
return n.toLocaleString('en-US');
}
function update() {
const C = parseInt(costSlider.value, 10);
const tier = parseInt(gpuSlider.value, 10) - 1;
const gpu = GPU_TIERS[tier];
costVal.textContent = C;
gpuVal.textContent = gpu.label;
// Rounds = 2^C (the core bcrypt iteration count)
const rounds = Math.pow(2, C);
// Effective hashes/sec: base is at cost 10; scale by 2^(10-C)
const effectiveRate = gpu.base * Math.pow(2, 10 - C);
// Time to exhaust the space
const seconds = SPACE / effectiveRate;
roundsEl.textContent = fmtNum(rounds);
speedEl.textContent = fmtNum(Math.round(effectiveRate)) + '/s';
timeEl.textContent = fmt(seconds);
// Bar: map log10(seconds) from 0..log10(1e18) → 0..100%
const logS = Math.log10(Math.max(seconds, 0.001));
const logMax = Math.log10(1e18);
const pct = Math.min(100, Math.max(1, (logS / logMax) * 100));
barEl.style.width = pct + '%';
// Contextual note
const AGE_UNIVERSE_S = 4.3e17;
let note = '';
if (seconds < 3600) {
note = '⚠️ Cracked in under an hour — this work factor is dangerously low for a real application.';
} else if (seconds < 86400 * 7) {
note = '⚠️ Cracked in under a week — suitable only for low-stakes throwaway passwords.';
} else if (seconds < 31536000 * 100) {
note = '✓ Cracking takes years — acceptable for many applications, but consider raising the cost as hardware improves.';
} else if (seconds < AGE_UNIVERSE_S) {
note = '✓✓ Cracking would take longer than human civilization — comfortable for most real-world deployments.';
} else {
note = '✓✓✓ Cracking would take longer than the age of the universe — practically unbreakable for any foreseeable hardware.';
}
noteEl.textContent = note;
// Console log for node verification
console.log('cost=' + C + ' rounds=' + rounds + ' rate=' + Math.round(effectiveRate) + '/s time=' + fmt(seconds));
}
costSlider.addEventListener('input', update);
gpuSlider.addEventListener('input', update);
update();
Notice the asymmetry: your login performs exactly one hash — the cost is imperceptible. An attacker checking millions of candidates must pay that cost every single time. Raising the work factor by 4 steps multiplies the attacker's time by 16 while adding only ~100 ms to your login. That is the power of tunable hardness.
Comments
Loading comments...