Cryptography › Module 1 › Lesson 3
Password Hashing & Salting
Store passwords with salts and slow hashes so stolen databases are harder to crack
Opening
Databases leak. Plan for that day — unique salt and a slow KDF, not SHA-256(password) and not a cracking shop.
If an app stores passwords as plaintext, a stolen database is a gift list. If it stores unsalted SHA-256 of the password alone, attackers who steal the table can try billions of guesses offline — especially against short common words — because SHA-256 is fast on purpose. You just learned that speed is a feature for hashing an ISO. It is a feature for an offline guesser too. Salting and slow password hashes raise the cost of every guess and stop identical passwords from looking identical in the dump. That is defender design for storage. It is not permission to steal databases or to run hashcat against other people’s hashes. This lesson is conceptual: unique per-user salt, why bcrypt / Argon2 / scrypt (and PBKDF2 as a dated but named KDF) beat MD5(password) and raw SHA-256, and pepper as an extra secret not stored in the same row. You will not implement a production hasher from scratch. You will not copy a blog’s “SHA-256 plus salt” and ship it. You will write notes in $HOME/cyberlium-lab (chmod 600) that explain the design, plus a clearly labeled demo that is not a login system. Next is a lab where you hash a toy word you chose and look it up in a tiny list you wrote — so you feel why unsalted fast hashes of short words are guessable. Never dumps. Never /etc/shadow. Never HaveIBeenPwned. Never hashcat against victims.
1. Why raw SHA-256 (and MD5) fail as password storage: speed is the attacker’s friend
General-purpose hashes like SHA-256 and MD5 are optimized to chew bytes quickly. File integrity wants that. An offline guesser who stole a table of unsalted SHA-256(password) also wants that: each guess is one fast digest, compare to the stolen hex, next guess. GPUs and rented hardware make enormous dictionaries cheap against fast hashes of short, common words. MD5(password) is worse in the museum-piece sense: broken for collisions and still fast. “We hash passwords, so we are safe” is false if the hash is fast, unsalted, and the password is in a tiny guessable set. Defenders assume the database will leak someday. The stored verifier should make each guess expensive and make precomputed tables fail across users. That is the job of a password hashing algorithm / KDF, not of hashlib.sha256 alone.
Password hashing algorithms (bcrypt, scrypt, Argon2, PBKDF2) are intentionally slow and, for scrypt/Argon2, memory-hard so that throwing GPUs at every guess hurts. Work factor / cost / time and memory parameters are how you tune “slow enough on our hardware, painful for bulk guessing.” You pick a library that implements a modern choice (Argon2id or bcrypt are the names you should recognize; scrypt is in the same slow family). You do not write Argon2 from a Wikipedia pseudocode. You do not loop SHA-256 a thousand times and invent “SHA-256-1000” as a product. You do not store MD5(password). You do not store SHA-256(password). You do not store plaintext “for debugging” in production. This course will not give you a production hasher to paste into a startup. It will give you the reasons to call one.
2. Unique salt: same password, different stored verifier, rainbow tables stall
A salt is random data mixed into the password hashing input, unique per user (per credential). Alice and Bob both choose the toy word “correct-horse” — their stored hashes must still differ because their salts differ. Without a salt, identical passwords produce identical hashes, so one guess that hits Alice also hits every other row with the same password, and a precomputed map of common words → SHA-256 hex (a rainbow table in the popular telling) works across the whole stolen table. With a unique salt, the attacker must redo work per row. Salts are not secret like encryption keys. They are stored beside the hash (often in the same string encoding bcrypt uses). They must be unique and unpredictable enough (CSPRNG, not user id, not “alice”, not a timestamp alone). Reusing one global salt for every user is a common failure: it looks like salting and still lets one precomputation hit everyone.
This course does not download rainbow tables. Understanding why salts exist does not require a 30 GB table on your disk. The next lab uses a tiny wordlist you write (five to ten words, including a toy password you chose) to show that unsalted fast SHA-256 of a short word is instantly findable in that list — lookup, not a cracking product, not other people’s hashes. Do not import dumped hashes to “make the salt lesson real.” Do not pull HaveIBeenPwned. Do not read /etc/shadow. A salt is a storage design. A wordlist against a dump is an attack. We teach the first. We only demo the second on a digest you created from a toy you put in a list you authored.
3. Pepper (concept) and libraries: extra secret, still not a homemade hasher
A pepper is an additional secret mixed into password hashing, stored not in the database row but in a secrets manager or app config — so a stolen SQL dump lacks the pepper and each guess needs that extra unknown. If the pepper leaks with the code or env, it stops helping. If you hardcode it in git, it is not a pepper. Treat pepper as an optional hardening on top of unique salts and a slow KDF, not as a substitute. You will not implement pepper in the lab. You will write one sentence in notes: pepper is a secret apart from the DB; salt is per-user and stored with the hash. Confusing salt and pepper is a common quiz miss: salts are unique and not secret; peppers are secret and usually global to the app.
In production you call a vetted password hasher via your language’s supported library (passlib, argon2-cffi, bcrypt packages, language-standard APIs — names for literacy, not a paste-this-into-prod kit from this lesson). The library generates the salt, encodes the algorithm and cost in the stored string, and offers a verify(password, stored) that is comparison-safe. You never invent the stored format. You never compare hex with == in a way that leaks timing if you are writing verify yourself — another reason to use the library. The demo code in this lesson shows unsalted-vs-salted SHA-256 only so you see salt change the hex. It is labeled demo only. It is not bcrypt. It is not Argon2. Shipping it would repeat the fast-hash mistake with extra bytes.
4. Wrong vs right: MD5(password) museum vs salt + slow KDF (library, not a blog)
Worked failure — “we hash passwords” is not a design. Right is unique salt, slow KDF, library verify — never a dump, never hashcat homework.
Wrong
Store plaintext, MD5(password), or unsalted SHA-256(password). Reuse one salt for every user. Download rainbow tables or HaveIBeenPwned dumps to “test.” Hash /etc/shadow. Implement Argon2 from a forum. Skip the library and ship hashlib loops. Treat the next lab as hashcat against victim hashes. Put live user passwords in cyberlium-lab notes.
Right
Write that stolen databases are assumed: unique per-user salt (stored, not secret), slow KDF (bcrypt / Argon2 / scrypt), pepper optional and not in the same row, never MD5(password). Demo salt+SHA-256 is labeled not-production. Notes in $HOME/cyberlium-lab chmod 600. Next: Lab — Hash Cracking Basics — YOUR toy word, YOUR tiny list, no dumps.
5. Hands-on: conceptual salted SHA-256 demo — labeled not a production hasher
Run a small script that hashes a toy passphrase you typed for the lab (not a live account password) twice: once as raw SHA-256, once with a random 16-byte salt. See that the salted hex changes when the salt changes, and that the salt must be stored to verify later. Write password-hashing-notes.txt: why fast hashes fail, what unique salt does, name bcrypt/Argon2/scrypt, pepper one-liner, “do not implement production from this file.” chmod 600. Do not point the script at a dump. Do not use your banking password as the toy.
Command guide
Conceptual salted SHA-256 (DEMO ONLY — use bcrypt/Argon2 in real apps)
CONCEPTUAL DEMO. NOT a production password hasher. NEVER MD5(password). NEVER hash dumps, /etc/shadow, or anyone else's secrets. Next lab: YOUR toy word in YOUR tiny wordlist — still not hashcat.
Command — copy this
mkdir -p "$HOME/cyberlium-lab" cd "$HOME/cyberlium-lab"
Command — copy this
cat > password_hashing_demo.py << 'PY'
import hashlib
import os
# Toy lab passphrase — NOT a live account password, NOT from a dump.
password = b"correct-horse-lab-toy"
print("unsalted SHA-256 (TOO FAST for password storage):")
print(hashlib.sha256(password).hexdigest())
salt_alice = os.urandom(16)
salt_bob = os.urandom(16)
print("same toy, unique salts -> different stored verifiers:")
print("alice", salt_alice.hex(), hashlib.sha256(salt_alice + password).hexdigest())
print("bob ", salt_bob.hex(), hashlib.sha256(salt_bob + password).hexdigest())
print("salt is stored with the hash; it is NOT a secret like a pepper")
print("production: Argon2id/bcrypt/scrypt via a library — do not ship this SHA-256 demo")
print("never: MD5(password); never: hashcat vs dumped hashes")
PYCommand — copy this
python3 password_hashing_demo.py || python password_hashing_demo.py
Command — copy this
NOTES="$HOME/cyberlium-lab/password-hashing-notes.txt"
{
echo "=== PASSWORD HASHING (storage design, not a cracking shop) ==="
echo "never_store: plaintext, MD5(password), unsalted SHA-256(password)"
echo "salt: unique per user, random, stored with hash, not secret like a key"
echo "slow_kdf: bcrypt / Argon2 / scrypt (PBKDF2 named as older KDF)"
echo "pepper: optional app secret NOT in the DB row — conceptual only here"
echo "this_demo: salted SHA-256 is NOT production"
echo "next_lab: hash a toy I chose; look up in a 5-10 word list I wrote"
echo "ethics: no dumps; no /etc/shadow; no HIBP; no rainbow tables; no hashcat vs victims"
} > "$NOTES"Command — copy this
chmod 600 "$HOME/cyberlium-lab/password_hashing_demo.py" \
"$NOTES"Windows without chmod: WSL/Git Bash, or restrict files in your profile.
NEVER: hash a live user database NEVER: curl a rainbow table or HaveIBeenPwned dump NEVER: python3 -c hashlib on /etc/shadow
Mission: password-hashing-notes.txt (mode 600) — design, not a homemade hasher
1) Write why unique per-user salt makes identical passwords hash differently and stalls naive precomputed tables — without downloading rainbow tables. 2) Name slow KDFs (bcrypt, Argon2, scrypt) versus fast SHA-256; note pepper as a secret not stored in the same row; write never MD5(password) and never ship the SHA-256 demo as production. 3) chmod 600 notes under $HOME/cyberlium-lab. No dumps, no /etc/shadow, no live account passwords in the file.
Stuck? Ask Cyberlium AI Mentor
If “just SHA-256 the password” still feels modern, ask for a hint — not a production hasher to paste. Try: "Hint only: why unique salts and slow KDFs (bcrypt/Argon2/scrypt) beat unsalted SHA-256, what a pepper is, and why I must not download dumps or implement Argon2 from a blog?" You still fill the notes. No hashcat. No /etc/shadow. No other people’s hashes.
You now treat password storage as a leak-assumed design: unique salt, slow memory-hard or slow KDF from a library, pepper only as a concept, never MD5(password), never homemade SHA-256 loops as a product. Fast hashes stay for integrity of files, not for login tables. Next — Lab — Hash Cracking Basics — you hash a toy password you chose, put it in an 8-word list you wrote, and look up the unsalted SHA-256 inside that list so you see why short words fall instantly. You will not import dumped hashes. You will not run hashcat. You will not touch /etc/shadow or HaveIBeenPwned. Notes stay chmod 600 in $HOME/cyberlium-lab.
Knowledge Check
APPLY: A startup stores SHA-256(password) with no salt “because Lesson 2 said SHA-256 is modern,” and a intern wants to test it against a HaveIBeenPwned dump. What is the salt’s job, and what do you refuse?
Multiple choice
Knowledge Check
APPLY: True or False: Fast general hashes alone are ideal for storing passwords, and you should implement Argon2 from scratch this week plus hash /etc/shadow to verify.
True or False
Knowledge Check
APPLY: You are writing password-hashing-notes.txt before the lookup lab. Which storage baseline, and what is the next lab allowed to hash?
Multiple choice