DEV Community

EvvyTools
EvvyTools

Posted on

How to Build a Password Entropy Calculator in JavaScript

Password strength meters are notoriously unreliable. Many just check for the presence of uppercase letters, numbers, and symbols, then map that to a color. A better approach is to calculate entropy directly and translate that to a realistic crack-time estimate. Here is how to build one in browser-native JavaScript, with no dependencies required.

What You Are Building

A function that takes a password string and returns an entropy estimate in bits, plus a rough crack-time estimate based on a realistic offline attack scenario. You will also add a generator function that uses crypto.getRandomValues() for cryptographically secure random output. The complete implementation is under 100 lines of vanilla JavaScript.

Step 1: Determine the Character Set

To calculate entropy, you need to know the size of the character set the password could plausibly use. Analyze the actual characters in the password:

function getCharsetSize(password) {
  let size = 0;
  if (/[a-z]/.test(password)) size += 26;
  if (/[A-Z]/.test(password)) size += 26;
  if (/[0-9]/.test(password)) size += 10;
  if (/[^a-zA-Z0-9]/.test(password)) size += 32; // common symbols
  return size;
}
Enter fullscreen mode Exit fullscreen mode

This is a lower-bound estimate based on observed character types. It assumes the password was drawn from only the character classes present, which slightly underestimates entropy for passwords that happen to omit one class even though they were generated from a larger set. For display purposes, this is accurate enough. If you need a more conservative estimate, you can instead infer the full generation charset from context (e.g., the user set uppercase on, so count uppercase even if the generated password did not include one).

Step 2: Calculate Entropy

Entropy in bits is log2(charsetSize ^ passwordLength). Mathematically this simplifies to passwordLength * log2(charsetSize):

function calculateEntropy(password) {
  if (!password || password.length === 0) return 0;
  const charsetSize = getCharsetSize(password);
  if (charsetSize === 0) return 0;
  const entropyBits = password.length * Math.log2(charsetSize);
  return Math.round(entropyBits * 10) / 10; // one decimal place
}
Enter fullscreen mode Exit fullscreen mode

The Math.log2() function is available in all modern browsers. For environments that require broader compatibility, Math.log(charsetSize) / Math.log(2) is equivalent.

Step 3: Estimate Crack Time

Crack time depends on attack scenario. Use a conservative offline attack rate against bcrypt with a cost factor of 12, which is approximately 100,000 hashes per second on consumer GPU hardware. This is the relevant scenario if a site's password database is ever compromised and the attacker is running hashes offline without any rate limiting:

function estimateCrackTime(entropyBits) {
  const BCRYPT_GUESSES_PER_SECOND = 100_000;
  const totalGuesses = Math.pow(2, entropyBits);
  const secondsOnAverage = totalGuesses / (2 * BCRYPT_GUESSES_PER_SECOND);

  if (secondsOnAverage < 60) return 'less than a minute';
  if (secondsOnAverage < 3600) return `${Math.round(secondsOnAverage / 60)} minutes`;
  if (secondsOnAverage < 86400) return `${Math.round(secondsOnAverage / 3600)} hours`;
  if (secondsOnAverage < 31536000) return `${Math.round(secondsOnAverage / 86400)} days`;
  if (secondsOnAverage < 3.15e9) return `${Math.round(secondsOnAverage / 31536000)} years`;
  return 'centuries';
}
Enter fullscreen mode Exit fullscreen mode

The / 2 in the denominator accounts for the expected value of a brute-force search -- on average you find the target halfway through the search space.

For context, a 60-bit entropy password against bcrypt-12 at 100,000 guesses/second has an average crack time of about 2^59 / 100,000 seconds -- roughly 1.8 * 10^12 seconds, or tens of thousands of years. A 20-character random password from a 95-character set lands at about 131 bits, which moves the average crack time to a number with more digits than are practical to express.

Server security cabinet lock close
Photo by slightly_different on Pixabay

Step 4: Add Secure Random Generation

Wire up a generator that produces passwords from a specified character set using crypto.getRandomValues():

function generatePassword(length, charset) {
  if (!charset || charset.length === 0) {
    throw new Error('charset cannot be empty');
  }
  const array = new Uint32Array(length);
  crypto.getRandomValues(array);
  return Array.from(array)
    .map(n => charset[n % charset.length])
    .join('');
}

const LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
const UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const DIGITS = '0123456789';
const SYMBOLS = '!@#$%^&*()-_=+[]{}|;:,.<>?';

const charset = LOWERCASE + UPPERCASE + DIGITS + SYMBOLS;
const password = generatePassword(20, charset);
console.log(password, calculateEntropy(password), 'bits');
Enter fullscreen mode Exit fullscreen mode

Note: n % charset.length introduces a small modulo bias when charset.length is not a power of 2. For a 95-character set, the bias per character is well under 0.001% -- negligible for passwords. For higher-stakes applications, use rejection sampling:

function generatePasswordSafe(length, charset) {
  const output = [];
  while (output.length < length) {
    const buf = new Uint8Array(1);
    crypto.getRandomValues(buf);
    if (buf[0] < Math.floor(256 / charset.length) * charset.length) {
      output.push(charset[buf[0] % charset.length]);
    }
  }
  return output.join('');
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Build a Minimal UI

Wire the calculator to a text input to update in real time:

const input = document.getElementById('password-input');
const entropyDisplay = document.getElementById('entropy');
const crackDisplay = document.getElementById('crack-time');

input.addEventListener('input', () => {
  const entropy = calculateEntropy(input.value);
  const crackTime = estimateCrackTime(entropy);
  entropyDisplay.textContent = `${entropy} bits`;
  crackDisplay.textContent = `Estimated offline crack time (bcrypt-12): ${crackTime}`;
});
Enter fullscreen mode Exit fullscreen mode

This is the core of what the EvvyTools Password Generator does -- combined with a more complete character set analysis and polished UI. If you want to compare your implementation's output against a production version, you can inspect the behavior there directly.

Step 6: Validate Against Edge Cases

Before shipping, verify your implementation handles these edge cases cleanly:

Empty string: should return 0 bits and not throw. The guard at the start of calculateEntropy handles this, but test it explicitly.

Single character: a one-character password with one character class has log2(26) ≈ 4.7 bits. Your function should return that, not throw on a charset size of 1.

Non-ASCII input: passwords containing Unicode characters will not match any of the regex classes, returning 0 for charset size. Decide whether to add a Unicode symbol category or to sanitize input before scoring.

Very long passwords: Math.pow(2, entropyBits) overflows to Infinity at about 1024 bits. The crack time function returns 'centuries' before reaching that threshold, but you should verify your entropy display handles large values gracefully rather than showing Infinity bits.

The OWASP Password Storage Cheat Sheet covers what happens on the server side after a password is created -- the hashing algorithm and parameters that determine whether your entropy calculation translates into actual protection. Reading it alongside this guide gives a complete picture of the client-server credential security chain.

What This Does Not Measure

Entropy only models brute-force resistance against randomly generated passwords. If a password is human-chosen, the effective entropy is much lower than the formula suggests because human selections are not uniformly random. A password like "Summer2024!" scores about 57 bits by the character-type formula but is far weaker in practice because it follows a recognizable seasonal-year-symbol pattern that attackers model explicitly.

Pattern-based attacks (dictionary attacks, rule-based mutation) exploit structure in human-chosen passwords. For an accurate estimate on non-random passwords, you would need a tool like Dropbox's zxcvbn library, which models attack strategies rather than calculating theoretical entropy. It estimates guessing difficulty based on known patterns, common words, keyboard walks, and substitution rules.

For the background math on why length typically beats complexity in entropy calculations, and how different hashing algorithms affect the crack-time picture, see How Password Entropy Works and Why It Matters for Account Security.

EvvyTools is a collection of developer and productivity tools. The full tools directory includes generators, formatters, and utilities across multiple categories.

The Web Crypto API documentation on MDN covers crypto.getRandomValues() and the broader set of cryptographic primitives available in modern browsers, including key derivation functions useful for more advanced credential scenarios.

Top comments (0)