During an audit at a small business we found the NVR password on a sticky note: "P@ssw0rd!". The IT guy was relaxed — it had an uppercase letter, a number and a symbol, so it "met the policy". We ran it against a leaked-password dictionary and it fell before the coffee got cold: it's on every list on the planet. Meanwhile "correct horse battery staple" — which looks like a joke — would have outlived the company. The difference between the two isn't opinion or magic: it's arithmetic, and by the end of this guide you'll be able to do it in your head.
Entropy: strength is length × log₂(alphabet)
An attacker doesn't "read" your password: they guess it by trying combinations. A password's strength is, quite literally, the size of the haystack it's hiding in. That size is measured in bits of entropy, and the formula fits on a napkin: entropy = length × log₂(alphabet size). Every additional bit doubles the combinations an attacker has to try.
Think of a bike lock: adding weird symbols makes each dial slightly bigger; adding characters adds whole dials. A lowercase letter is worth log₂(26) = 4.70 bits; a character drawn from all four classes (94 printable ASCII symbols) is worth 6.55. The per-character difference is small — the difference from length is brutal, because length multiplies.
So what about "P@ssw0rd!"? On paper, 9 characters × 6.55 = 59 bits. But the formula assumes every character was chosen at random, and that's the catch: "P@ssw0rd!" is the word "password" with the a→@ and o→0 mutations that every cracking tool tries first, by rule. Its real entropy is a handful of bits: it sits a couple million guesses from the front of the line, not 2⁵⁹ deep. A passphrase does the opposite: it picks words at random from the EFF list of 7776 words, and each word is worth log₂(7776) = 12.92 bits even though it reads like a bedtime story.
| Alphabet | Size | Bits per unit |
|---|---|---|
| lowercase only | 26 | 4.70 / char |
| + uppercase | 52 | 5.70 / char |
| + digits | 62 | 5.95 / char |
| + symbols (printable ASCII) | 94 | 6.55 / char |
| random word (EFF list) | 7776 | 12.92 / word |
The worked example: your password vs a GPU
Realistic scenario: a service got its database leaked and the passwords were stored with a fast hash (SHA-256, no salt, no stretching — it happens more than you'd think). A single desktop GPU tries on the order of 10¹⁰ hashes per second, and on average finds the password after searching half the space. Let's run the whole thing:
- The formula: entropy = length × log₂(alphabet). For 8 lowercase letters: 8 × log₂(26) = 8 × 4.70 = 37.6 bits, i.e. 26⁸ ≈ 2.09 × 10¹¹ combinations.
- Average time = (2^bits ÷ 2) ÷ rate. At 10¹⁰ guesses/s: 2.09 × 10¹¹ ÷ 2 ÷ 10¹⁰ ≈ 10.4 seconds. Your 8-lowercase password lasts less than a coffee run.
- Move up to 12 characters with 4 classes (pool 94): 12 × 6.55 = 78.7 bits → 94¹² ≈ 4.76 × 10²³ combinations. Average: 2.38 × 10¹³ seconds ≈ 754,000 years. Same GPU, same attack.
- The passphrase: 4 random EFF words = 4 × 12.92 = 51.7 bits → ≈ 2.1 days against a fast hash. Borderline. With 6 words: 77.5 bits → ≈ 350,000 years, and you can memorize it on the walk to your car.
- The takeaway: going from 4 to 6 words multiplies the attacker's work by 7776² ≈ 60 million times. Entropy doesn't add — it multiplies.
Two honest caveats. First, this is an offline attack against a fast hash: if the service stored bcrypt or scrypt, the rate collapses to around 10⁴ guesses/s and those same 8 lowercase letters go from 10 seconds to about 4 months (121 days). Second, an online attack against a rate-limited login is orders of magnitude slower still. Play with policies, rates and scenarios in the entropy and crack-time calculator.
The password manager: the logical conclusion
Follow the math all the way. You need unique passwords per service — because when that RC-plane forum you signed up for in 2019 gets breached, the first thing attackers do is try that same email and password against your bank, your VPN and your work email (it's called credential stuffing and it's the most profitable attack there is). And each one needs 75+ bits. Nobody memorizes eighty random 12-character strings: the arithmetic leaves exactly one way out.
That way out is the password manager: one master passphrase of 6 words (77.5 bits, memorable, written down nowhere) and everything else machine-generated — 20 characters, unique, never meant to be remembered or typed by hand. The manager isn't a convenience: it's the only architecture where the formula balances. And wherever the service offers it, a second factor on top.
Hashes: a fingerprint, not encryption
New topic, same numeric spirit. A hash is a one-way function: anything goes in — a single letter, a 2 GB firmware — and a fixed-length fingerprint comes out. Like an actual fingerprint: it identifies you without letting anyone reconstruct you. Here lives the most repeated misconception in the trade: hashing is not encryption. Encrypted data can be decrypted with the key; a hash has no way back — there is no "unhash". And it has the avalanche effect: change one letter of the input and the whole fingerprint changes.
Use 1: verifying integrity. You downloaded a firmware and the vendor publishes its SHA-256: if your hash matches, the file arrived intact and nobody tampered with it along the way. It looks like this (and yes, one different letter would have produced an unrecognizable fingerprint):
$ echo -n "redzilla" | md5sum e9b5a2402e7ab94f63d139974709317a - $ echo -n "redzilla" | sha256sum bcff34d1effbe914707d30410323f9ead560f3f610c8aa6aa710f95993551d15 -
Use 2: storing passwords. A well-built service never stores your password: it stores its hash, under two conditions. A unique salt per user — a random value mixed with the password before hashing, so two users with the same password don't share a hash and precomputed tables become useless. And an algorithm that is slow on purpose: bcrypt, scrypt or Argon2 run thousands of iterations precisely so the GPU from our example drops from 10¹⁰ to 10⁴ guesses per second. MD5 and SHA-1 are the opposite: designed in the 90s to be fast, which is exactly what you don't want facing an attacker.
Field bonus: hashes can be identified by eye from length and format. Practice with the hash calculator and the hash identifier:
| Looks like | Format | Probably is |
|---|---|---|
| e9b5a2402e7ab94f… | 32 hex | MD5 |
| 8581bd83aafb4d6d… | 40 hex | SHA-1 |
| bcff34d1effbe914… | 64 hex | SHA-256 |
| $2b$12$N9qo8uLO… | starts with $2b$ | bcrypt (salt and cost built in) |
Inside a JWT: a laminated ID, not a sealed envelope
JWT tokens carry half the sessions on the internet and drag along the same misunderstanding as hashes. A JWT is three Base64URL blocks joined by dots: header (the algorithm), payload (the data: who you are, when it expires) and signature. The right analogy is a laminated ID card: anyone who picks it up can read your name and date of birth — the plastic hides nothing. What the hologram (the signature) does is expose whether someone altered it.
Try it yourself — decoding the header requires no secret whatsoever, just undoing the Base64:
$ echo 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9' | base64 -d
{"alg":"HS256","typ":"JWT"} Hence the two golden rules. One: never put secrets in the payload — no keys, no sensitive data, nothing you wouldn't show the token's holder, because they (and any proxy along the way) can read it with two lines of terminal. Two: the iat (issued at) and exp (expires) claims are Unix seconds, and a short exp isn't backend fussiness: a stolen JWT can't easily be "revoked", so its lifetime is your only brake. Paste any token into the JWT decoder — it runs 100% in your browser — and see what it's declaring inside.
Base64, UUIDs and other disguises
You've already met the reason the payload is readable: Base64 is encoding, not encryption. It turns arbitrary bytes into 64 harmless characters so they survive trips through email, JSON or URLs — and anyone can reverse it without a key, paying a 33% size tax along the way. Writing in Greek doesn't hide the message from someone who reads Greek. If you ever see data "protected" with Base64, run it through the encoder and say hi to the plaintext.
And to complete the kit, UUIDs in one sentence each. UUID v4: 122 bits of cryptographic randomness — impossible to guess, perfect as a public identifier. UUID v7: puts a millisecond timestamp up front, so IDs are born in chronological order and don't wreck your database indexes. Generate both (plus ULIDs) in the UUID generator — but remember an identifier is not a credential: the thing that guards access is called a token, and it gets signed.
That's practical security in three habits: a long passphrase for the one thing you memorize, a manager for everything else, and an instant reflex of distrust toward anything that "looks" encrypted but is merely encoded. Run your current policy through the entropy calculator and crack open one of your own tokens in the JWT decoder: numbers persuade better than any lecture.