What the Password Generator and the Strength Checker Actually Compute
Two pages sit next to each other in the site's crypto section and people read them as one verdict: the password generator prints a strength line when you generate, and the password strength checker prints a longer report when you type. They are measuring different things. The generator never looks at the characters it handed you; the checker never sees your settings. Everything below is read out of the code on those two pages and, where a number matters, confirmed by running the page's own functions in the browser.
The four alphabets, and what exclusion really removes
The generator draws from four fixed strings:
| Option | Characters | Size | Size with exclusion on |
|---|---|---|---|
| Uppercase | A–Z |
26 | 24 (O, I removed) |
| Lowercase | a–z |
26 | 25 (l removed) |
| Digits | 0–9 |
10 | 8 (0, 1 removed) |
| Symbols | a fixed 27-character list | 27 | 27 (nothing removed) |
The symbol set is exactly !@#$%^&*()_+-=[]{}|;:,.<>?~ — 27 characters.
All four boxes ticked gives a pool of 89; the default state on page load — uppercase, lowercase and digits on, symbols and exclusion off — gives 62. The similar-character switch removes exactly the five glyphs the label promises (0/O、1/l/I), and none of them appear in the symbol string, so turning it on with symbols selected changes only 89 → 84.
Two consequences of that fixed symbol list are worth knowing before you trust a site's "special character" requirement. The list has no quote, no backslash, no forward slash, no backtick and no whitespace — passwords from this page can never contain them, which is convenient for shells and JSON, but also means you cannot ask for a symbol the list does not have. And there is no custom-charset field: the only dials are the four checkboxes plus the length slider, which runs 4 to 64 and starts at 16.
One from each box, then a shuffle
Generation is not "pick N characters and hope". The code picks one character from every selected set first, fills the remaining slots from the union of all selected sets, then shuffles the array with Fisher-Yates. Every pull, shuffle step and fill goes through one helper:
function secureRand(max) {
var limit = Math.floor(4294967296 / max) * max;
var buf = new Uint32Array(1);
do { crypto.getRandomValues(buf); } while (buf[0] >= limit);
return buf[0] % max;
}
That is rejection sampling over crypto.getRandomValues, so the modulo bias you get from a naive % max on a raw 32-bit draw is discarded rather than redistributed — values at or above limit are thrown away and drawn again. It is a CSPRNG-backed uniform draw, not Math.random().
Running genOne() 300 times with all four sets, exclusion on and length 16 returned 300 passwords of exactly 16 characters, every one of them containing at least one of each of the four classes, none containing 0, O, 1, l or I, and no value appearing twice. The forced first four slots are the point: with all four sets on, exclusion ticked and the slider at 4, 50 consecutive samples were all 4 characters long and each held exactly one uppercase, one lowercase, one digit and one symbol. At that length the shape of the password is fixed and only the individual glyphs are random — which is why a 4-character password from this tool is still 4 characters' worth of secrets, not 16.
One button press costs seven draws: generatePassword() calls genOne() once for the main field and six more times for the grid underneath it. Those six are independent generations, not variants of the first. If you untick all four character boxes, genOne() returns null and the button raises Please select at least one character type instead of producing a blank result.
The strength bar describes your settings
Entropy on the generator is length × log2(pool size), computed from the slider and the checkboxes — the password in the output box is never parsed. The bands are hard-coded:
| Entropy | Label (EN / 中文) | Bar width |
|---|---|---|
| under 40 | Weak / 弱 | 15% |
| 40–59 | Fair / 一般 | 35% |
| 60–79 | Strong / 强 | 65% |
| 80 or more | Very strong / 非常强 | 100% |
Measured straight from the page: the default 3-set / 16-character state reads Strength: Very strong (entropy 95.3 bits) / 强度:非常强(熵值 95.3 位); adding symbols takes it to 103.6; adding symbols and exclusion drops it to 102.3 because the pool shrank from 89 to 84 (16 × log2 84 = 102.28); symbols alone at 16 characters reads 76.1 and drops you a band to Strong; all four sets with exclusion at length 4 reads 25.6, Weak.
Because the bar and the box are decoupled, changing the slider after generating re-labels a password that has not changed. Generate at 16, drag the slider to 8, and the label drops while the 16-character string is still sitting in the field. Copy what is in the field, not what the bar says.
The checker scores the string, on a different scale
The checker's headline number is not entropy. It is a 0–120 score assembled from fixed bonuses, and entropy only feeds in as one of several additions:
+15once the length reaches 8,+10more once it reaches 12, plus+min(length − 12, 8)for anything past 12+10each for containing a lowercase, an uppercase and a digit;+15for containing a non-alphanumeric- entropy bonuses
+5 / +8 / +8 / +8at 40 / 60 / 80 / 100 bits +5for using at least three character classes, another+5for all four
Then the label comes from the score, not the entropy: ≤20 Weak, ≤40 Fair, ≤60 Good, ≤80 Strong, above that Very strong. Sampled values: password → 25 pts, 🟡 Fair; Abcdefghijk1 → 73 pts, 🟢 Strong; Aa1!Aa1! → 75 pts, 🟢 Strong; Tr0ub4dor&3 → 83 pts, 🟢 Very strong; a 24-character string of nothing but a → 72 pts, 🟢 Strong. The last one is the clearest illustration of what a score built from character-class bonuses cannot see.
Worth pinning down before you compare the two pages: the checker has five bands keyed to a 0–120 score, the generator has four bands keyed to entropy in bits, and both call the top-ish rung "Strong". On the checker that word means a score of 61–80; on the generator it means 60–79 bits. Same label, different quantities, and nothing on either page reconciles them.
Its entropy uses the same length × log2(size) shape but a different size model: lowercase 26, uppercase 26, digits 10, and 32 for "special", where special is simply [^a-zA-Z0-9]. Anything that is not ASCII alphanumerics counts as one of those 32 possibilities. Measured: 密码测试 registers as four special characters and nothing else, 20.0 bits; a single cat emoji counts as length 2 — the page measures UTF-16 code units, so astral-plane characters are counted twice and score 10.0 bits. Non-Latin passwords are therefore underrated against their real alphabets and overrated against the "special" bucket at the same time.
The six tick boxes on that page are exactly len8, lower, upper, number, special and len12, with len12 styled as a bonus rather than a requirement. They report presence, nothing else.
A 40-word list, matched exactly
The "common password" warning compares your input, lowercased, against a hard-coded array. That array holds 40 entries, all distinct: 123456, password, 12345678, qwerty, 123456789, 12345, 1234, 111111, 1234567, sunshine, qwerty123, admin, letmein, password1, 123123, iloveyou, monkey, dragon, master, 666666, abc123, 654321, 123321, 1234567890, welcome, princess, football, shadow, trustno1, superman, 1qaz2wsx, qwertyuiop, 123qwe, passw0rd, zaq1xsw2, 000000, login, starwars, pass123, batman.
Case is ignored, so PASSWORD trips the warning just as password does, and the message echoes back what you typed: This password appears in a common-password list (e.g. "PASSWORD") and is extremely insecure! Do not use it. Beyond that exact-match table there is no fuzzy logic: P@ssw0rd is not in the list and passes silently, Aa1!Aa1! is a four-character pattern repeated twice and is called Strong, Abcdefghijk1 is an alphabetical run and is called Strong. There is no breach-database lookup and no keyboard-pattern check. A 40-entry list is a useful floor, not a corpus.
Crack times, and the one label that is ten times off
The estimate is 2^(entropy − 1) average guesses, divided by 10 guesses per second for the online column and 1010 for the offline column; anything above 1000 bits, or any result over 1e15 seconds, prints Practically impossible (the Chinese page: 几乎不可能). Formatting switches unit at 1 second, 60, 3600, 86400, 2592000 (30-day months), 31536000 (365-day years) and 315360000, then again at 3.1536e12 seconds.
Read those outputs as orders of magnitude rather than dates. password gives 10s offline and 331 yr online; Aa1!Aa1! gives 4 days offline; Abcdefghijk1 gives 5115 yr offline. The rate assumptions are stated on the page itself and are the honest part of this section: 10 guesses/second assumes a login form that throttles you, 1010 assumes the attacker already has the hash file and it is a fast unsalted hash. Against bcrypt or Argon2 the offline column is wildly pessimistic, which is the direction you want a mistake to go in.
One unit label, though, is simply wrong. The largest band divides the seconds by 3.1536e10 — that is 1000 years, not a century — and appends the word centuries. So Abcdefghijkl1 reports 317 centuries: the underlying figure is 317,000 years, which is about 3,170 centuries, and the number printed is ten times smaller than the unit it is printed with. The online column reaches the same band much sooner — Aa1!Aa1! shows 9665 centuries where the computed value is roughly 9.7 million years. Treat every centuries / 世纪 reading on that page as thousands of years, and note that a value that has reached this band is strong enough that the exact figure carries no practical meaning.
What happens to the text you put in
The checker's input is type="password" with autocomplete="off" and spellcheck="false", and the eye button flips the same field to type="text" — masking is display-only, and the analysis runs on the raw value either way. The generator's output field is a plain read-only text input, so its result is always visible on screen, as are the six batch rows below it.
Neither page's own code contains a fetch, XMLHttpRequest or sendBeacon call, and the only storage access on either page is reading one localStorage key that holds the Live2D mascot choice. Your password is not posted anywhere by the tool. Both pages do still load third-party scripts — the ad frame and the mascot widget from a CDN — so "nothing leaves the page" is a claim about this page's logic, not about every script on it. Copying goes through the async Clipboard API with an execCommand fallback; when you click one of the six batch rows, the ✓ Copied confirmation appears on the Copy button rather than on the row you clicked, which is the one bit of feedback that can make you think nothing happened.
A workable order
Use the generator when you are creating a credential, and set it to all four boxes with the length at 20 or more: 20 characters from a pool of 89 is 129.5 bits, and length is the only lever this page gives you — the alphabet is fixed, so 103.6 bits at 16 characters is the ceiling you cannot raise by tweaking checkboxes, only by sliding. Leave the exclusion switch off unless you actually transcribe passwords by hand; it costs you 5 of 89 possibilities and buys readability.
Do not paste a real password into the checker to find out whether it is strong. Test the shape instead — same length, same mix of classes, different characters — because the score is derived almost entirely from length and class presence, so a faithful fake scores the same way. And when you do type something, remember the two numbers on that screen are answers to different questions: the score is "does this look like a machine-generated string", the crack time is "what if that assumption is wrong".
Where both pages stop
Neither page knows anything about your risk: no dictionary or name/dob patterns, no keyboard walks beyond the two already in the list, no breach checks, no per-site uniqueness reminder. The generator has no pronounceable or passphrase mode, no custom symbol list, no copy history and no way to require two symbols instead of one. The checker's a-repeated-24-times result is the standing warning about the whole genre: any tool that measures strength as length × log2(alphabet) will rate a string with no entropy in it as unbreakable.