Authentication factors

Authentication is proving that you are who you claim to be. The claim itself — typing a username — is identification; deciding what you may do afterward is authorization (see What Is Information Security? and Access Control). An authentication factor is a category of proof:

Factor Examples Typical weakness
Something you know (knowledge) Password, passphrase, personal identification number (PIN), security question Guessed, phished, reused, leaked in a breach
Something you have (possession) Phone with an authenticator app, hardware security key, smart card, badge Lost or stolen; codes can be phished
Something you are (inherence) Fingerprint, face, iris, voice Cannot be changed when compromised; false matches and false rejections
Somewhere you are (location) Company network, country, Global Positioning System (GPS) position Easy to spoof alone; used as an extra signal
Something you do (behavior) Typing rhythm, how you hold the phone Probabilistic; used as an extra signal

Multi-factor authentication (MFA; see Remote Access for the basic definition) means factors of different kinds. A password plus a security question is still one factor — two things you know — and can both be phished in the same fake form. Location and behavior are usually not counted as full factors; they feed risk-based decisions, such as asking for an extra check when someone signs in from a new country.

Why passwords fail

Passwords are cheap and familiar, which is why they will be around for years. But they fail in predictable ways:

  • Guessing: people choose patterns — a season and a year, a team name with an exclamation mark — that attackers try first. Password spraying tries a few such common passwords against many accounts, staying under lockout limits.
  • Phishing: a convincing fake login page collects the password directly (see Social Engineering and Phishing).
  • Breaches: a website is hacked and its password database is stolen. If the passwords were stored badly, they can be recovered offline at billions of guesses per second.
  • Reuse: the same password on many sites turns one breach into many.

Reuse and breaches together enable credential stuffing: attackers take username and password pairs leaked from one service and try them automatically on other services. No guessing is involved, so it works against any password, however strong, that is reused. Defenses are unique passwords per site, MFA, checks against breached-password lists, and detection of many logins from the same sources.

Good password practice

The United States National Institute of Standards and Technology (NIST) publishes guidance on digital identity in Special Publication (SP) 800-63B. It overturned many older habits, and much of the world follows it. Its main points for passwords:

  • Length over complexity: require a minimum length and allow long passwords (at least 64 characters), but do not demand a mix of uppercase, digits and symbols. Such rules produce Summer2025!, not security. The 2025 revision (SP 800-63B-4) asks for at least 15 characters when a password is the only factor, and at least 8 when it is combined with another factor.
  • No forced periodic changes: changing every 90 days makes people choose weaker passwords with a counter at the end. Force a change when there is evidence of compromise.
  • Check against a blocklist: reject passwords that appear in lists of breached passwords, dictionary words and passwords based on the service or username.
  • Allow paste and password managers, and allow all printable characters, including spaces.
  • No password hints and no security questions ("your mother's maiden name"): the answers can often be found online.
  • Limit failed attempts (rate limiting or lockout), so online guessing is slow.

Passphrases and entropy

The strength of a randomly generated secret is measured in bits of entropy: length × log2(size of the pool it is drawn from). Every extra bit doubles the number of guesses an attacker needs. A passphrase of random words — chosen by dice or a generator from a list such as the 7,776-word list of the Electronic Frontier Foundation (EFF) — is easy to type and remember, and each word adds almost 13 bits.

import math


def entropy_bits(pool_size, length):
    """Bits of entropy of a secret picked uniformly at random from the pool."""
    return length * math.log2(pool_size)


def readable(seconds):
    for unit, size in (("years", 365 * 24 * 3600), ("days", 24 * 3600), ("hours", 3600)):
        if seconds >= size:
            return f"{seconds / size:,.0f} {unit}"
    return f"{seconds:,.0f} seconds"


candidates = [
    ("8 random characters (94 symbols)", 94, 8),
    ("12 random characters (94 symbols)", 94, 12),
    ("16 random lowercase letters", 26, 16),
    ("4 random words (7,776-word list)", 7776, 4),
    ("6 random words (7,776-word list)", 7776, 6),
]

GUESSES_PER_SECOND = 10**10  # an offline attack on a fast, unsalted hash

for label, pool, length in candidates:
    bits = entropy_bits(pool, length)
    average = 2 ** (bits - 1) / GUESSES_PER_SECOND  # on average, half the space
    print(f"{label:<35}{bits:5.1f} bits  {readable(average)}")
8 random characters (94 symbols)    52.4 bits  4 days
12 random characters (94 symbols)   78.7 bits  754,567 years
16 random lowercase letters         75.2 bits  69,141 years
4 random words (7,776-word list)    51.7 bits  2 days
6 random words (7,776-word list)    77.5 bits  350,510 years

Two lessons. First, length wins: 16 lowercase letters beat 8 characters of every kind by far. Second, the formula only holds for secrets chosen at random. A human-chosen password such as Amsterdam2025! has 13 characters, but an attacker who tries city names with years and a symbol finds it in a tiny fraction of that time. The slow password hashing described below makes every guess thousands of times more expensive, which is why the time per guess in the example is a worst case.

Password managers

A password manager stores all of a person's passwords in an encrypted vault, unlocked with one strong master password (ideally plus MFA), and generates long, random, unique passwords for every site. It solves reuse, and it also helps against phishing: the browser extension fills in a password only on the real domain, so a lookalike site gets nothing — a useful warning sign in itself. Organizations should provide an approved manager rather than leave people to use notebooks and spreadsheets. The master password and the recovery method of the vault are now the crown jewels: protect them accordingly.

How systems store passwords

A system needs to check a password, not to know it. So it should never store passwords, and also not encrypt them: encryption can be reversed by anyone who obtains the key, and the key is usually on the same server. Instead, systems use password hashing: they store the output of a one-way function (see hash functions) that is deliberately slow and uses a unique salt per password. At login, the system hashes what was typed in the same way and compares the results.

A salt is a random value, unique for each stored password, that is combined with the password before hashing and stored next to the hash. It is not secret. It makes identical passwords produce different hashes, so an attacker cannot see which users share a password, and it makes precomputed tables of hashes (rainbow tables) useless — every password must be attacked separately.

Ordinary hash functions such as SHA-256 are designed to be fast, which is exactly wrong here: fast means billions of guesses per second on a graphics card. Password hashing uses slow key derivation functions with an adjustable cost:

  • Argon2 (Argon2id): winner of the Password Hashing Competition (2015), memory-hard; the first choice in current guidance from the Open Worldwide Application Security Project (OWASP).
  • scrypt: memory-hard, so attacks on graphics cards and special hardware are expensive.
  • bcrypt: widely used since 1999, still acceptable; note that it only uses the first 72 bytes of a password.
  • PBKDF2 (Password-Based Key Derivation Function 2): repeats a hash function many times; required where only Federal Information Processing Standards (FIPS) validated algorithms are allowed.

Python's standard library has PBKDF2 and scrypt (Argon2 and bcrypt need third-party packages). This example uses fixed salts so that its output is reproducible; real code generates a fresh random salt for every password.

import hashlib

password = b"correct horse battery staple"

# Fixed demo salts so the output is reproducible. In real code every user
# gets a fresh random salt: os.urandom(16) or secrets.token_bytes(16).
salt_ada = bytes.fromhex("8f1c2a7d4b6e9013a5c7e2f40d18b36c")
salt_bob = bytes.fromhex("2e9b51c0f7a34d86b1e0c95a73d26f18")

# A fast, unsalted hash: identical passwords give identical hashes.
print("SHA-256      ", hashlib.sha256(password).hexdigest()[:32])

# PBKDF2: a salted, deliberately slow key derivation function.
for name, salt in (("ada", salt_ada), ("bob", salt_bob)):
    digest = hashlib.pbkdf2_hmac("sha256", password, salt, 600_000)
    print(f"PBKDF2 {name}   ", digest.hex()[:32])

# scrypt: slow and memory-hard (n = CPU/memory cost, r = block size).
# demo cost; OWASP recommends n=2**17 (needs maxmem raised)
digest = hashlib.scrypt(password, salt=salt_ada, n=2**14, r=8, p=1, dklen=32)
print("scrypt ada   ", digest.hex()[:32])
SHA-256       c4bbcb1fbec99d65bf59d85c8cb62ee2
PBKDF2 ada    e9cdf5c7d3d7310355c58d296bdee319
PBKDF2 bob    f934f9c6733c906ca527d028a9d1da2b
scrypt ada    1ec881e6eabb08a57429759a3a5530a9

Ada and Bob chose the same password, yet their stored hashes are unrelated, because their salts differ. The 600,000 iterations make each PBKDF2 hash take a noticeable fraction of a second — nothing for one login, a huge obstacle for billions of guesses. (Outputs are shortened to 32 hexadecimal characters for the page.)

Verifying a password

The stored record contains everything needed to repeat the calculation: algorithm, cost, salt and hash. Verification compares the result with hmac.compare_digest, which takes the same time no matter where the first difference is, so the response time does not leak how much of the hash was correct.

import hashlib
import hmac

ITERATIONS = 600_000


def hash_password(password, salt):
    digest = hashlib.pbkdf2_hmac("sha256", password.encode(), salt, ITERATIONS)
    # Store algorithm, cost, salt and hash together, so you can raise the cost later.
    return f"pbkdf2_sha256${ITERATIONS}${salt.hex()}${digest.hex()}"


def verify_password(password, stored):
    _, iterations, salt_hex, hash_hex = stored.split("$")
    digest = hashlib.pbkdf2_hmac(
        "sha256", password.encode(), bytes.fromhex(salt_hex), int(iterations)
    )
    # Constant-time comparison: takes the same time wherever the first difference is.
    return hmac.compare_digest(digest.hex(), hash_hex)


demo_salt = bytes.fromhex("8f1c2a7d4b6e9013a5c7e2f40d18b36c")  # real code: os.urandom(16)
stored = hash_password("correct horse battery staple", demo_salt)
print(stored[:60] + "...")
print(verify_password("correct horse battery staple", stored))
print(verify_password("Correct horse battery staple", stored))
pbkdf2_sha256$600000$8f1c2a7d4b6e9013a5c7e2f40d18b36c$e9cdf5...
True
False

Multi-factor authentication

A second factor means a stolen password alone is no longer enough. But the methods differ a lot in what they stop:

Method How it works Main weakness Strength
Text message (Short Message Service, SMS) or voice code A code sent to your phone number SIM swapping (a criminal moves your number to their own subscriber identity module, or SIM), interception, phishing Weakest, still better than nothing
Authenticator app (TOTP) A six-digit code that changes every 30 seconds A fake site can ask for the code and use it at once Good
Push notification Approve the sign-in in an app MFA fatigue: repeated prompts until someone taps "approve" Good, with number matching
Hardware security key, passkey (FIDO2) The device proves itself with a key pair bound to the site Loss of the key; needs a backup method Strongest: phishing-resistant

How authenticator codes work

A time-based one-time password (TOTP, defined in Request for Comments (RFC) 6238) is a short code computed from a secret shared between the app and the server (exchanged once, through the QR code at enrollment) and the current time. Both sides count 30-second steps since 1 January 1970, compute a hash-based message authentication code (HMAC; see HMAC) of that step number with the secret, and turn it into six digits. No network connection is needed; the clocks only have to agree roughly. This example uses the test secret from the RFC and fixed times; for times 59 and 1111111109 the codes match the last six digits of the RFC's test values:

import hashlib
import hmac
import struct


def totp(secret, unix_time, step=30, digits=6):
    """Time-based one-time password (RFC 6238, HMAC-SHA-1 as most apps use)."""
    counter = unix_time // step                      # number of 30-second steps
    mac = hmac.new(secret, struct.pack(">Q", counter), hashlib.sha1).digest()
    offset = mac[-1] & 0x0F                          # "dynamic truncation"
    code = struct.unpack(">I", mac[offset:offset + 4])[0] & 0x7FFFFFFF
    return str(code % 10**digits).zfill(digits)


# The test secret from RFC 6238; a real secret comes from the QR code at enrollment.
secret = b"12345678901234567890"

for t in (31, 59, 61, 1111111109):
    print(f"time {t:>10}  step {t // 30:>8}  code {totp(secret, t)}")
time         31  step        1  code 287082
time         59  step        1  code 287082
time         61  step        2  code 359152
time 1111111109  step 37037036  code 081804

Times 31 and 59 fall in the same 30-second step and give the same code. Note what TOTP does not do: it does not check which website asks for the code. A user on a convincing fake login page types the code there, and an attacker who relays it in real time (an adversary-in-the-middle phishing kit) gets in. That is the gap phishing-resistant methods close.

Push notifications and MFA fatigue

With push MFA, the user only taps "approve". Attackers who already have the password abuse this with MFA fatigue (also called push bombing): they trigger prompt after prompt, sometimes at night, sometimes combined with a phone call from "IT support", until the user approves one to make it stop. Countermeasures are number matching (the user types the number shown on the sign-in screen into the authenticator app; it stops blind approvals, but not a real-time relay phishing site, which shows the number too), showing the location and application in the prompt, limiting the number of prompts, and a clear rule: an unexpected prompt is denied and reported. See Social Engineering and Phishing.

Phishing-resistant MFA and passkeys

FIDO2 is a set of open standards from the FIDO Alliance and the World Wide Web Consortium (W3C): WebAuthn (Web Authentication) in the browser and the Client to Authenticator Protocol (CTAP) between the browser and an authenticator such as a hardware security key. Instead of a shared secret, it uses public-key cryptography (see asymmetric encryption).

A passkey is a FIDO2 credential that replaces the password: a key pair created for one specific website. The private key stays on the user's device or in their password manager, unlocked locally with a fingerprint, face or PIN; the website stores only the public key.

Signing in with a passkey. The website sends a random challenge. The user's device, unlocked with a fingerprint, face or PIN, signs the challenge together with the website's domain using the private key, and returns the signature. The website checks the signature with the public key it stored at registration. Your device Private key never sent to the website unlocked by fingerprint or PIN Website (server) Public key stored at registration nothing secret to steal 1. random challenge 2. signature over challenge + domain 3. verify with public key A lookalike domain gets a signature it cannot use anywhere else
The private key signs a fresh challenge; the server checks it with the public key. There is no code to type and nothing reusable to phish. Synced passkeys are copied end-to-end encrypted between your own devices.

Three properties make passkeys and security keys strong:

  • Origin binding: the browser includes the real domain in what is signed, and the authenticator only uses a key for the domain it was created for. A phishing site on a lookalike domain cannot get a valid response.
  • Nothing reusable on the server: a breach of the website reveals only public keys, which are useless to an attacker.
  • Two factors in one gesture: possession of the device plus the fingerprint or PIN that unlocks it (when the site requires user verification).

Passkeys come in two kinds. Synced passkeys are backed up through the platform account or password manager (for example to all of someone's Apple, Google or Microsoft devices), so a lost phone does not lock the user out. Device-bound passkeys live in one piece of hardware — a security key, or a device's Trusted Platform Module (TPM) or secure enclave — and are designed so they cannot be exported or copied, which high-security environments and administrators often require. Smart cards with certificates, used widely in government, are also phishing-resistant.

Account recovery: the side door

Every strong login has a recovery path for people who lose their phone or forget their password, and attackers know it. If a help desk resets a password and MFA for anyone who calls with a name and an employee number, the strongest passkey is irrelevant: the attacker simply phones the help desk. Several well-known breaches started exactly like this.

  • Verify identity as strongly as practical — a callback to a number on file plus a manager's confirmation, or in-person ID; treat a video call alone as insufficient, since faces and voices can be faked. Never call back a number the caller provides.
  • Treat a request to reset MFA, especially for an administrator, as high risk; notify the user through a second channel when it happens.
  • Avoid security questions; give users backup methods (a second security key, recovery codes stored safely) instead.
  • Help desk staff must be allowed — and expected — to say no and escalate when a caller pushes or rushes.

Single sign-on

In organizations, users rarely have a separate password for every application. With single sign-on (SSO), they authenticate once at a central identity provider, which vouches for them to the applications through standards such as Security Assertion Markup Language (SAML) and OpenID Connect (OIDC). This is explained in Identity and Access Management. For security it has a clear trade-off: fewer passwords, one place to enforce strong MFA, and one switch to disable a leaver everywhere — but also one account that unlocks everything, so that identity provider and its administrator accounts need the strongest protection you have.