flashman
← All posts

Password alphabet modulo bias debugging

Debug generated passwords by counting the 85-character alphabet, class checkboxes that only enlarge it, the 4 to 128 clamp, and modulo bias on each draw.

2026-10-19 · 6 min read · Rahul Chitturi

  • passwords
  • security
  • javascript

Flashman's password tool builds one alphabet from the checked classes and draws each character with crypto.getRandomValues. A checked box adds its characters. It does not require that class to appear in the result. A length of 4 with every box checked can still be 0000.

The draw is one unsigned 32-bit value modulo the alphabet length. That remainder is not rejection sampling, so some characters are one count more likely than others across the full range of that integer.

Count the alphabet in checkbox order

Lowercase is a through z, then uppercase A through Z, then the digits 0 through 9, then 23 symbols: !@#$%^&*()-_=+[]{};:,.? in that order. All four boxes produce 85 characters. There is no space, quote, apostrophe, slash, backslash, backtick, tilde, or pipe.

Uncheck every box and the alphabet is empty. The tool then shows Error: Alphabet is empty, and Copy stays disabled. A policy that says the password must contain a symbol is not what these checkboxes implement. The symbol box only means a symbol is allowed.

  • Write down which boxes were checked.
  • Count 26, 26, 10, and 23 before you add a custom alphabet in your head.
  • Do not expect a symbol just because the symbol box is on.
  • Reject the result yourself if a required class is missing.

See which characters get the extra draw

The generator takes one uint32 and returns that value modulo the alphabet length. Across every uint32, the first R characters occur once more than the rest, where R is 2^32 modulo the length. Characters are indexed in the order above.

With all four classes, the length is 85 and R is 1, so only a is the extra character. With digits alone, the length is 10 and R is 6, so 0 through 5 each occur once more than 6 through 9. The gap is one part in about 50 million on the full alphabet, and it is larger on a short alphabet. It is still a bias, and it is not a reason to treat the output as a hash.

Clamp the length, then truncate it

The length field is clamped to the range 4 through 128. Clearing the field runs the clamp on 0, so the length becomes 4. A value of 20.9 is clamped to 20.9 and then the string is built with a length of 20, because the array length truncates. You asked for a fraction and received 20 characters.

Regenerate changes a seed so the same length and alphabet produce a new string. The hash tool is a different page: it hashes the text you paste and its own note says a hash is not password storage. Use the password tool for a local draw, diff when a policy lists required classes the checkboxes do not enforce, and keep the generated value out of tickets.

Try these tools