Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Applied Cryptography for Developers  Module 6 – Key derivation and password storage

Password storage and login security

Tune Argon2id on your own servers, rehash at login, add a pepper, follow the NIST SP 800-63B-4 password rules and check breached passwords without sending them.

  • Intermediate
  • 25 minutes
  • Examples run with Node.js 24.21.0, Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free intro, Pro: 5 of 25 minutes free
Free: A login system is six decisions around the password hash. With a pass: the other 9 sections and 3 interview answers, about 20 minutes.

What you will learn

  • Tune a password hash's cost on the target hardware and raise it at the next login
  • Add a pepper that is kept outside the database and state what it protects
  • Apply the rules of NIST SP 800-63B-4 for length, Unicode normalisation and blocklists
  • Check a password against breach data without sending the password anywhere

Before you start

What you’ll learn · Before you start

What you will learn

  • Tune a password hash's cost on the target hardware and raise it at the next login
  • Add a pepper that is kept outside the database and state what it protects
  • Apply the rules of NIST SP 800-63B-4 for length, Unicode normalisation and blocklists
  • Check a password against breach data without sending the password anywhere

Before you start

On this page

Choosing Argon2id settles how each record is hashed and leaves most of a login system still to design. NIST SP 800-63B-4 asks for passwords of at least 15 characters when they are the only factor, a blocklist (of passwords): A list of values a new password may not take: passwords from breach corpora, common and dictionary words, and words of the context such as the service's name and the user name, as NIST SP 800-63B-4 asks. of compromised values and at most 100 consecutive failed attempts on an account; OWASP asks for a hash that takes less than a second on your own servers and grows stronger as they do.

This lesson builds the rest around the hash: tuning its cost on your hardware and raising it at login, a pepper that a stolen database does not contain, the NIST rules for length, Unicode and blocklists, breached-password checks that never send the password, and limits on online guessing.

A login system is six decisions around the password hash

Password storage covers the first three decisions below, what a stolen table holds; login security covers the other three, what each request may try. Each has a short answer, and the sections that follow build each one in code:

  • Cost: measure the hash on the hardware that runs it, and keep a check well under a second.
  • Upgrades: rehash on login: Rewriting a password record with the current settings right after its owner signs in successfully, the one moment the server holds the verified password. a record made under older settings right after a successful login, the one moment the server holds the password.
  • Pepper: a secret key kept outside the database and mixed into every hash, for example as Argon2’s secret input, so that a stolen table alone cannot be checked against guesses.
  • Policy: length in code points after Unicode normalisation, long passwords allowed, a blocklist of breached, common and context words such as the service’s name, and no rules on kinds of characters.
  • Breach checks: compare a new password with breach data through a range query that sends only five characters of its hash.
  • Online guessing: limits per account, with growing waits rather than quick lockouts, checked before the slow hash runs.

Step through the order in which one login meets them:

Interactive · stage by stage One login, in the order the checks run Step through what a server does between the moment a password arrives and the moment it answers.
One login, in the order the checks run, step 6 of 6. Showing Normalise the password, Check the account's limit, Read the stored record, Hash with the pepper, Compare in constant time, Upgrade if needed. In focus: Upgrade if needed.wrong: count a failureNormalise the passwordUnicode NFC, then UTF-8Check the account's limitbefore any slow hashingRead the stored recordsettings, salt, key ID, hashHash with the pepperthe record's own settingsCompare in constant timeone answer for every failureUpgrade if neededold settings or old pepper

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Normalise. The same password can arrive as different code points from different keyboards, so the server normalises it to NFC before it counts or hashes anything.
  2. The limit first. It checks how many logins have failed for this account. Checking first means a flood of guesses is refused before it costs the server a slow hash each.
  3. The record. It reads the stored record: the function, its settings, the salt and the key ID of the pepper. A missing account still goes through the same steps.
  4. Hash, with the pepper. It hashes the password with the record's settings and with the pepper, a secret key kept outside the database, so a copy of the table alone cannot test guesses.
  5. Compare. It compares in constant time. A wrong password is counted against the account and answered with the same message as an unknown account.
  6. Upgrade on success. After a right password, and only then, the server holds the password, so this is the moment to rehash a record made with older settings or an older pepper.

Normalising comes first for a reason the program shows. The same password typed on a laptop and on a phone can arrive as different code points: é as one character, or as e followed by a combining accent. NIST SP 800-63B-4 asks verifiers to apply Unicode normalisation, form NFC, before hashing:

One password, two keyboards, two hashes until NFC Python · nfc-py/nfc.py
"""Two passwords that look identical, typed on two devices: "café" with é as one code point, and with e followed by a
combining accent. Their bytes differ, so their hashes differ, until both are normalised to NFC before hashing."""
import hashlib
import unicodedata

typed_on_laptop = "café-monsoon-42"  # é as one code point, U+00E9
typed_on_phone = "café-monsoon-42"  # e, then U+0301 COMBINING ACUTE ACCENT

for label, p in [("laptop", typed_on_laptop), ("phone", typed_on_phone)]:
    print(f"{label:<6} {p}  {len(p)} code points, {len(p.encode())} bytes, SHA-256 {hashlib.sha256(p.encode()).hexdigest()[:12]}…")
print(f"same text on screen, same bytes: {typed_on_laptop.encode() == typed_on_phone.encode()}")

nfc = [unicodedata.normalize("NFC", p) for p in (typed_on_laptop, typed_on_phone)]
print(f"after NFC: {len(nfc[0])} and {len(nfc[1])} code points, same bytes: {nfc[0].encode() == nfc[1].encode()}")

Output

laptop café-monsoon-42  15 code points, 16 bytes, SHA-256 f992130d869d…
phone  café-monsoon-42  16 code points, 17 bytes, SHA-256 a1a348f41ec9…
same text on screen, same bytes: False
after NFC: 15 and 15 code points, same bytes: True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 nfc.py

The two strings look the same but have 15 and 16 code points and different hashes, so without NFC the person signs up on one device and is told their password is wrong on the other. After NFC both have 15 code points and the same bytes. Small as it is, the case shows why password storage and login security are designed together: the stored hash is only as consistent as the steps in front of it.

Checkpoint

Check: The decisions around the password hash

  1. Question 1 of 2 What does a login do with the typed password before it counts its length or hashes it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Normalise it to Unicode NFC, then encode it as UTF-8

    NIST SP 800-63B-4 asks verifiers to apply NFC before hashing. In the program, two strings that look the same had 15 and 16 code points and different hashes until both were normalised.

  2. Question 2 of 2 The policy now asks for stronger Argon2id settings than many stored records use. When can a server rewrite such a record as a direct hash with the new settings?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Right after its owner signs in successfully, when the server holds the password

    Each record carries its own settings, so it can be checked as it is and upgraded right after a successful login, with the password that has just been verified.

Flashcards · 6 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. Which login rules come from NIST SP 800-63B-4, and which from OWASP?

    NIST: passwords of at least 15 characters when they are the only factor, a blocklist of compromised values, and at most 100 consecutive failed attempts on an account. OWASP: a hash that takes less than a second on your own servers and grows stronger as they do.

  2. What are the six decisions around a password hash?

    Cost: measure the hash on your own hardware. Upgrades: rehash old records after a successful login. Pepper: a secret kept outside the database and mixed into every hash. Policy: length in code points after Unicode normalisation, a blocklist, no rules on kinds of characters. Breach checks: a range query that sends five characters of a hash. Online guessing: limits per account, with growing waits rather than quick lockouts, checked before the slow hash runs.

  3. Why upgrade a stored password hash at login?

    A successful login is the one moment the server holds the password, so a record made under older settings can be rehashed with the current ones right then.

  4. What is a pepper, and what does it add?

    A secret key kept outside the database and mixed into every hash, for example as Argon2's secret input. A stolen table alone then cannot be checked against guesses.

  5. How can a service check a new password against breach data without sending the password?

    With a range query that sends only five characters of the password's hash, never the password itself.

  6. What goes wrong when passwords are hashed without NFC normalisation?

    The same password typed on a laptop and a phone can arrive as different code points: é as a single character, or as e with a combining accent after it. In the lesson's run the two strings had 15 and 16 code points and different hashes, so the person signed up on one device and was told the password was wrong on the other. NIST SP 800-63B-4 asks verifiers to apply NFC before hashing.

Tuning the cost on the servers that will run it Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • Rehash at login, and wrap legacy hashes that cannot wait
  • A pepper: a key the database never holds
  • NIST SP 800-63B-4: length, Unicode and blocklists
  • Breached passwords, checked with five characters of a hash
  • Online guessing: limits per account that do not lock people out
  • Practise: a password policy and a login module
    • New-password rules from NIST SP 800-63B-4
    • A login module with a pepper, rehashing and a limit
  • Interview questions
    • Your 2 million Argon2id records were made at m=8 MiB and t=1, below OWASP's floors, and about half of the users sign in during a year. Plan the move to a compliant setting, including the users who never come back.
    • Product wants to warn users at sign-up when their chosen password appears in breach data, but your privacy team will not let passwords, or their full hashes, leave your servers. Design the check.
    • After you add a 10-minute lockout after 5 failed logins, support reports customers who are locked out every Monday morning by failures they did not make. Diagnose it and redesign the limits within NIST SP 800-63B-4.
  • Key takeaways

1 diagram · 6 runnable examples (JavaScript, Python) · 2 exercises with sample tests · 3 interview answers · about 38 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Terms in this lesson

blocklist (of passwords)
A list of values a new password may not take: passwords from breach corpora, common and dictionary words, and words of the context such as the service's name and the user name, as NIST SP 800-63B-4 asks.
k-anonymity
Hiding one item among many that look the same to the other side, as a breached-password range query does by sending only the first five hex characters of a hash, which very many passwords share.
pepper
A secret key mixed into every password hash and kept outside the database, in a secrets manager or a hardware module, so that a copy of the table alone cannot be checked against guesses. Argon2 takes it as its secret input.
rehash on login
Rewriting a password record with the current settings right after its owner signs in successfully, the one moment the server holds the verified password.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.