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.
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:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- 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.
- 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.
- 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.
- 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.
- Compare. It compares in constant time. A wrong password is counted against the account and answered with the same message as an unknown account.
- 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:
"""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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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
Flashcards · 6 cards
Recall it, then check
Say the answer to yourself before you look: recalling it is what makes it stick.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
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
- SP 800-63B-4: Authentication and Authenticator Management (sections 3.1.1.2 and 3.2.2) (National Institute of Standards and Technology (NIST))
- OWASP Password Storage Cheat Sheet (OWASP Foundation)
- Have I Been Pwned API v3: searching by range with k-anonymity (Have I Been Pwned)
- Unicode Standard Annex #15: Unicode Normalization Forms (Unicode Consortium)
- RFC 9106: Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications (Internet Engineering Task Force (IETF))
- PHC string format (Argon2 settings keyid and data) (C2SP (Community Cryptography Specification Project))
- Node.js 24 documentation: Crypto (crypto.argon2 and its secret input) (OpenJS Foundation (Node.js))
- Python 3.14 documentation: unicodedata.normalize (Python Software Foundation)
- OWASP Application Security Verification Standard 5.0, V11 Cryptography (requirement 11.4.2) (OWASP Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress