A password meter is six lines of arithmetic. Almost every one you have used runs a variant of this:
let poolSize = 0; if (/[a-z]/.test(pw)) poolSize += 26; if (/[A-Z]/.test(pw)) poolSize += 26; if (/[0-9]/.test(pw)) poolSize += 10; if (/[^a-zA-Z0-9]/.test(pw)) poolSize += 32; const bits = pw.length * Math.log2(poolSize || 1); Then a band — under 28 bits is "weak", under 50 "fair", under 70 "good", above "strong".
There is nothing dishonest in that code. The problem is what the number means: it is a statement about a process (draw each character uniformly from a poolSize-symbol alphabet) applied to an output (a string). When the string really was produced that way, the arithmetic is exact. When a human typed it, the same arithmetic produces numbers that are wrong in a specific direction, and the direction is always "too optimistic".
Four outputs from the same function
| Password | Chars | Pool | Bits | Band |
|---|---|---|---|---|
hunter2 | 7 | 36 | 36.19 | fair |
P@ssw0rd1 | 9 | 94 | 58.99 | good |
Tr0ub4dor&3 | 11 | 94 | 72.10 | strong |
aaaaaaaaaaaaaaaaaaaaaaaa | 24 | 26 | 112.81 | strong |
The last row is the whole problem in one line. Twenty-four copies of the same letter outrank an eleven-character password that has upper case, digits and a symbol — because the formula has no term for repetition, and no term for anything a human does.
1. The meter's pool is bigger than the generator's alphabet
The generator on the same page defines its pools literally:
const POOLS = { uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', // 26 lowercase: 'abcdefghijklmnopqrstuvwxyz', // 26 digits: '0123456789', // 10 symbols: '!@#$%^&*()-_=+[]{}|;:,.<>?', // 26 }; All four together are 88 symbols, so a generated character carries log2(88) = 6.45943 bits. The meter, reading the same string, awards 32 for "any non-alphanumeric" — which makes the pool 94 and the per-character value log2(94) = 6.55459.
A 16-character password drawn from all four pools therefore contains 103.35 bits and is displayed as 104.87. It is one and a half bits of flattery, from the same function that decides whether to call it "strong". The direction is harmless here; the lesson is that "pool size" is defined twice, in two places, and the two definitions are not the same number.
2. Uniformity is the assumption, and it is the part humans break
P@ssw0rd1 really does have 58.99 bits if each of its nine characters was chosen uniformly from 94 symbols. At 10^10 guesses per second, half of 2^58.99 is about 2.9e17 guesses — just under a year.
But nobody guesses uniformly. A rule-based cracker tries dictionary words first, then capitalisation, then leetspeak (a→@, o→0, s→$), then a short digit suffix, and P@ssw0rd1 falls out of that schedule in the first few minutes. The entropy number was never wrong; the sample space it describes was never the one being searched.
This is why xxxxxxxxxxxxxxxxxxxxxxxx and P@ssw0rd1 both show a green bar, and only one of them survives contact with a real attacker.
3. Even "random" leaks, in two more places
Entropy is not the only quantity that gets overstated. Two examples that are measured rather than remembered:
A UUID is not 128 random bits. A v4 UUID starts from 16 random bytes and then overwrites six bits — four for the version (0100) and two for the variant (10). What is left is 122 bits of entropy, about 5.3e36 values. Still far past guessing, but if you are budgeting bits, you are over by six.
Modulo bias depends on the size of the source. Reducing a random number with buf[0] % n is exact only when 2^32 is a multiple of n; otherwise the first 2^32 mod n values get one extra slot. At 32 bits the leftover is tiny: for n = 94, 2^32 mod 94 = 42, a relative bias of about 1e-8 — irrelevant. The same line with a single byte (0–255) and n = 100 is not irrelevant: 56 of the 100 values appear 1.5 times as often, because 256 = 2×100 + 56. The fix is rejection sampling — draw, discard anything at or above floor(max/n) * n, draw again — which is what the random number tool does.
4. What the number is actually good for
- If you generated it,
length × log2(alphabet)is the correct entropy. Pick length, not symbols: from an 88-symbol alphabet, 12 characters is 77.51 bits, 16 is 103.35, 20 is 129.19. Adding two characters buys more than adding a symbol class. - If a human typed it, the number is an upper bound that is never reached. Use a dictionary-and-pattern model (zxcvbn-style) or do not score at all — instruct people to generate instead.
- If it is a passphrase, count words, not letters. A 7,776-word diceware list gives
log2(7776) = 12.92bits per word, so six words is 77.55 bits. The letter-based meter scores that same 28-character string at 164.02 — roughly twice its real entropy, because it treats the letters as if they had been drawn independently.
The useful rule is blunt: entropy describes how a value was produced. If you know the process, the formula is exact and you should trust it. If you only have the string, any meter can be fooled by a pattern, and the one thing you must not do is build a policy around the bar turning green.
If you want to watch the numbers above come out of the actual code, the password generator shows the character pools and the entropy it assigns, the UUID generator produces v4 values (version and variant bits included), and the random number generator uses rejection sampling rather than a modulo. All three run in the page — the values are generated by your browser's crypto API and nothing you type or generate is sent anywhere.

