The Secret Humans Actually Use#

No matter how strong your cryptography is, some person or service authenticates with a password somewhere. Password security is the discipline of making the secrets people and machines use expensive to guess, safe to store, and survivable when a database inevitably leaks. It spans three fronts: what makes a password strong, how to store it, and how the login flow itself resists attack.

The Threats, in Order of Importance#

Four attack styles drive every recommendation:

  • Credential stuffing is the biggest real-world threat. Attackers take username/password pairs leaked from other breaches and replay them against your site, automated across millions of accounts within hours of a dump. They are not guessing; they are reusing what people already exposed elsewhere. This is why password reuse is the single worst habit: one breach anywhere opens every account that shares the password.
  • Dictionary and hybrid attacks try known passwords and predictable patterns (Summer2024!, Password1, qwerty123) plus common mutations. Password "complexity rules" fail here because humans satisfy them predictably.
  • Brute force tries every combination. Length dominates its cost: each added character multiplies the search space, so a long passphrase beats a short complex string by orders of magnitude.
  • Phishing sidesteps strength entirely by getting the user to type the password into a fake page (see social engineering).

The ordering matters: defending only against brute force (by demanding symbols) while ignoring reuse and phishing protects against the least likely attack.

What Actually Makes a Password Strong#

Current guidance (reflected in NIST's recommendations) overturns the old advice:

  • Length over complexity. A four-word passphrase like correct-horse-battery-staple (over 30 characters) is easier to remember and vastly harder to brute force than P@ssw0rd!. Entropy comes from length and unpredictability, not symbol soup.
  • Screen against known-breached and common passwords at signup rather than imposing arbitrary composition rules. Rejecting the top few hundred thousand leaked passwords stops the guesses that actually work.
  • Do not force periodic rotation without cause. Mandatory 90-day changes push people toward Spring2024 then Summer2024, which is weaker, not stronger. Rotate on evidence of compromise instead.
  • Allow long inputs, all characters, and paste, so password managers work.

You can check a password against known breaches without ever sending it anywhere, using the Have I Been Pwned range API with k-anonymity: you send only the first five characters of the password's SHA-1 hash and match the rest locally.

import hashlib, requests

def breached_count(password: str) -> int:
    h = hashlib.sha1(password.encode()).hexdigest().upper()
    prefix, suffix = h[:5], h[5:]
    resp = requests.get(f"https://api.pwnedpasswords.com/range/{prefix}", timeout=10)
    for line in resp.text.splitlines():
        tail, count = line.split(":")
        if tail == suffix:
            return int(count)
    return 0

# breached_count("password") -> a very large number; reject it at signup

The server never learns which password you checked, only a five-character hash prefix shared by many passwords.

Storage: Hash, Salt, and Slow#

When you build authentication, the rules are non-negotiable and, in many jurisdictions, legally expected:

  • Never store plaintext or reversible ("encrypted then decryptable") passwords. If you can recover the original, so can whoever steals your database.
  • Store a salted, slow, memory-hard hash: Argon2id (preferred), scrypt, or bcrypt, or high-iteration PBKDF2 as a retrofit. The hashing lesson covers the algorithms and code in depth.
  • The salt (a random per-user value stored with the hash) ensures identical passwords produce different hashes and defeats precomputed lookup tables. The slowness ensures each guess costs real time even on a GPU farm.
  • Never invent your own scheme. Homegrown "salt + SHA-256 + Base64" is fast and often reversible in effect. Use a library.

A leaked hash database built this way forces attackers to grind each account individually, buying time for you to force a reset.

The Login Flow Is an Attack Surface#

Even perfect storage fails if the login endpoint invites abuse:

  • Rate limit and throttle. Slow or lock login after repeated failures to blunt online guessing and stuffing. Apply limits per account and per IP, since stuffing spreads across accounts.
  • Do not leak which part was wrong. Return one generic "invalid username or password" so an attacker cannot enumerate valid usernames. The same applies to password reset ("if that email exists, we sent a link").
  • Validate server-side, everywhere. Client-side strength meters are UX, not security.
  • Re-authenticate for sensitive actions. Require the password or an MFA prompt again before changing email, changing password, or moving money, so a hijacked session cannot pivot freely.
  • Detect and log anomalies. Many IPs on one account, or attempts trickling in over time, feed incident response.
import time

FAILS = {}      # user -> (count, first_fail_time); use a shared store in production
WINDOW = 300    # seconds

def _current(user: str):
    now = time.time()
    count, first = FAILS.get(user, (0, now))
    if now - first > WINDOW:
        count, first = 0, now                    # window elapsed: start a fresh one
        FAILS.pop(user, None)
    return count, first

def check_rate_limit(user: str, max_fails=5) -> bool:
    count, _ = _current(user)
    return count < max_fails                     # False = locked/slowed for this window

def record_failure(user: str):
    count, first = _current(user)
    FAILS[user] = (count + 1, first)             # stored, so the reset actually sticks

The Human Layer: Managers and MFA#

Hygiene is infrastructure, and two tools carry most of it:

  • Password managers (Bitwarden, 1Password, KeePass) generate a unique random password per site and store it encrypted, eliminating reuse entirely. The user remembers only one strong master passphrase, which should itself be long and protected with MFA. This is the most effective single change most people can make.
  • Multi-factor authentication is the wall behind the password. A TOTP app is good; a phishing-resistant passkey or FIDO2 hardware key (auth concepts) is better, because the credential is bound to the real site's origin and cannot be replayed on a look-alike domain. Passkeys are where consumer authentication is heading precisely because they defeat both reuse and phishing at once.

Machine and Service Credentials#

People are not the only ones with passwords. Databases, APIs and background jobs authenticate with credentials too, and these are often worse-managed than human passwords because they are set once and forgotten. The same principles apply with a few twists:

  • No default credentials. admin/admin, the database's out-of-the-box root password, and vendor default logins are among the most exploited weaknesses because scanners try them first. Change every default before a system is reachable.
  • Long, random, unique per service. A machine never has to remember its credential, so there is no excuse for a short or shared one. Generate a long random secret per service.
  • Store in a secrets manager, not in code or config. Hardcoded credentials in a repository are effectively published; see secrets management for vaults and injection at runtime.
  • Prefer short-lived, automatically rotated credentials (or better, identity-based access with no long-lived secret at all) so a leak has a limited lifetime.
  • Scope tightly. A service account gets only the permissions it needs, so a stolen credential cannot do everything.

The recurring theme across human and machine credentials is the same: unique, long, well-stored, and revocable.

Checklist#

  • Passwords are stored with Argon2id/scrypt/bcrypt and a per-user salt, never plaintext or reversible.
  • Signup screens against breached and common passwords instead of enforcing arbitrary symbol rules.
  • Length is encouraged; long passphrases and paste are allowed.
  • Login is rate-limited per account and per IP, with generic error messages.
  • Sensitive actions require re-authentication or MFA.
  • MFA is available, with passkeys or hardware keys offered for high-value accounts.