Your Password Generator Is Only as Good as crypto.getRandomValues

Written by

in

A few weeks ago I watched a teammate generate a “secure” password with a little snippet he’d written years ago. It looked fine — 16 characters, mixed case, symbols. Then I asked him what was seeding it. He shrugged: Math.random(). That password had far less real randomness than it looked like, and neither of us could tell by staring at it. That’s the whole problem with password strength — the weakness is invisible.

So I want to walk through what actually makes a generated password strong, using PassForge, a browser-only password generator I use. It runs entirely client-side, and because the whole thing is a single HTML file with the logic in plain view, it’s a good way to show the math instead of hand-waving about it.

Why Math.random() is quietly broken for passwords

JavaScript’s Math.random() is a pseudo-random number generator. It’s fast, it’s fine for shuffling a card animation, and it is not cryptographically secure. The output is deterministic given the internal state, and in V8 that state is only 128 bits seeded in a way that was never meant to resist an attacker. If someone can observe a few outputs, they can predict the rest.

The fix is the Web Crypto API. Here’s exactly how PassForge pulls a random integer — no library, just the browser’s CSPRNG:

function cryptoRandInt(max) {
  const arr = new Uint32Array(1);
  crypto.getRandomValues(arr);
  return arr[0] % max;
}

crypto.getRandomValues is backed by the operating system’s secure random source. That’s the same class of entropy your TLS handshakes use. The % max introduces a tiny modulo bias when max doesn’t evenly divide 2³², but for a 26- or 33-character pool that bias is negligible — we’re talking a fraction of a fraction of a bit. For a password generator, this is the correct baseline, and it’s the first thing I check before trusting any generator.

Entropy is the only number that matters

“Strong password” is a marketing phrase. Entropy in bits is the actual measurement. It answers one question: how many guesses, on average, before an attacker lands on your password?

For a random character password, entropy is length × log2(poolSize). PassForge computes the pool the honest way — by inspecting which character classes are actually present:

function calcEntropy(password) {
  let poolSize = 0;
  if (/[a-z]/.test(password)) poolSize += 26;
  if (/[A-Z]/.test(password)) poolSize += 26;
  if (/[0-9]/.test(password)) poolSize += 10;
  if (/[^a-zA-Z0-9]/.test(password)) poolSize += 33;
  return Math.floor(password.length * Math.log2(poolSize));
}

So a 16-character password using all four classes has a pool of 95, and log2(95) ≈ 6.57 bits per character — about 105 bits total. Drop the symbols and you’re at a pool of 62, roughly 95 bits. That 10-bit gap is a factor of 1,024 in guessing difficulty, from one design choice most people never think about.

Passphrases: fewer characters, more entropy per word

Here’s the part that surprises people. A four-word passphrase like correct-horse-battery-staple feels weaker than Kx9$mQ2!, but it usually isn’t. PassForge uses the EFF short wordlist — 1,296 words — and each randomly chosen word contributes log2(1296) ≈ 10.34 bits:

function passphraseEntropy(wordCount, addNumber, addSymbol) {
  let bits = wordCount * Math.log2(WORDS.length);
  if (addNumber) bits += Math.log2(100);
  if (addSymbol) bits += Math.log2(8);
  return Math.floor(bits);
}

Six words gives you about 62 bits before any decoration — and it’s a string you can actually retype from memory when a password manager isn’t handy. The key word is randomly. If you pick the words yourself, the entropy math collapses, because human word choice is predictable. The security comes entirely from crypto.getRandomValues picking the index for you.

The crack-time number, and its honest assumptions

PassForge turns entropy into a human-readable estimate, and I like that it states its threat model in the code instead of hiding it:

function crackTimeStr(bits) {
  // Assume 10 billion guesses/sec (modern GPU cluster)
  const guesses = Math.pow(2, bits);
  const seconds = guesses / 1e10;
  ...
}

Ten billion guesses per second is a reasonable stand-in for an offline attack against a fast hash. That assumption matters. Against a properly slow hash like bcrypt or Argon2, the attacker’s rate drops by orders of magnitude, so the tool is being conservative — it’s modeling the worst realistic case, not the best. I trust a calculator that’s pessimistic on my behalf more than one that shows “3 trillion years” for a mediocre password.

At 10 billion guesses/sec, a 60-bit password falls in a few years, an 80-bit password holds for millions of years, and once you clear 128 bits you’re past “longer than the age of the universe.” That’s why the target isn’t a length — it’s a bit count.

The small touches that show someone thought about it

Two details I appreciated. First, the ambiguous-character filter: O0lI1| get stripped when you ask for it, so you never squint at a password wondering if that’s a one or a lowercase L. Second, the generator guarantees at least one character from each selected class and then shuffles the result with a crypto-backed Fisher-Yates, so the guaranteed characters don’t always land in front. Skipping that shuffle is a classic subtle bug that leaks a little entropy and makes output patterns predictable.

Why browser-only is the right call here

A password generator that sends anything over the network is a contradiction. PassForge is one static HTML file — you can open the page, hit “save,” disconnect from Wi-Fi, and it keeps working. Nothing to install, nothing phoning home, and you can read the entire source before you trust it. That’s the same reason I use its siblings for other one-off tasks: HashForge for hashing and Base64Lab for encoding, all client-side. I wrote separately about why I stopped uploading files to free online tools — the logic applies double to secrets.

If you’d rather not trust any web page with generation at all, a hardware option is the cleanest boundary: I keep a YubiKey 5 NFC for the accounts that matter, and for storing what I generate a manager like 1Password is worth the price. Full disclosure: those are affiliate links.

The takeaway is simpler than the math looks: check that your generator uses crypto.getRandomValues, aim for 80+ bits of entropy, and let the tool pick the randomness — never your own brain. Try PassForge here and watch the entropy number move as you change the settings.


Join https://t.me/alphasignal822 for free market intelligence.

📧 Get weekly insights on security, trading, and tech. No spam, unsubscribe anytime.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Also by us: StartCaaS — AI Company OS · Hype2You — AI Tech Trends