Hashing and salt · Cyber Security Basics
If the user database leaks, can attackers read everyone's password?
Course contentsWhy you need it
Databases leak: through bugs, stolen backups, careless employees. If passwords are stored as plain text, every account falls at once, and because people reuse passwords, their email and bank accounts fall too. Storing passwords correctly is the one security task almost every developer has to get right.
The intuition
A hash is a fingerprint of some text. The same text always gives the same fingerprint, a tiny change gives a completely different one, and you can't rebuild the text from the fingerprint.
So the server stores the hash, and at login it hashes what you typed and compares. But attackers can still guess: hash the most common passwords and look for matches. That's a dictionary attack.
A salt is a random string stored next to each user's hash and mixed into the password before hashing. Two users with the same password now get different hashes, so one guess no longer cracks everyone at once, and tables of pre-computed hashes become useless. Real systems also use a deliberately slow hash such as bcrypt, scrypt or Argon2, so each guess costs time.
Definition
Hash function
A function that turns any input into a fixed-length value. It is one-way: you can't compute the input from the output. SHA-256 always gives 64 hex characters.
Salt
A random value stored with each password hash and combined with the password before hashing, so equal passwords get different hashes.
Examples
Same input, same fingerprint
Try it yourselfWe print only the first 16 characters of each 64-character hash.
import hashlib
def h(text):
return hashlib.sha256(text.encode()).hexdigest()
print(h("sunshine")[:16])
print(h("sunshine")[:16])
print(h("Sunshine")[:16])- L4Hash the bytes of the text and show the result as hex digits.
- L7Same password, same hash. That's how login works.
- L8One capital letter changes the whole hash.
A dictionary attack
Try it yourselfThe attacker has the leaked hashes and a list of common passwords. Weak passwords fall; the random one survives.
import hashlib
def h(text):
return hashlib.sha256(text.encode()).hexdigest()
leaked = {"amy": h("dragon"), "bob": h("x9!Lq2#v")}
wordlist = ["123456", "password", "dragon", "qwerty"]
for user, stored in leaked.items():
found = None
for guess in wordlist:
if h(guess) == stored:
found = guess
print(user, "->", found)- L6What the attacker stole: users and hashes, no passwords.
- L7Real word lists hold millions of leaked passwords.
- L11Hash each guess and compare with the stored hash.
- L13dragon is in the list, so amy's password is found.
amy -> dragon - L13bob's random password isn't in any list.
bob -> None
Common mistakes
A fast hash alone
SHA-256 is built to be fast, which helps attackers try billions of guesses. For passwords use a slow, salted password hash (bcrypt, scrypt, Argon2) from a trusted library. This lesson uses SHA-256 only to show the idea.
One salt for everyone
If every user shares the same salt, equal passwords still get equal hashes and one guess still cracks them all. Each user needs their own random salt.
Quick quiz
1. What does this print?
import hashlib
def h(text):
return hashlib.sha256(text.encode()).hexdigest()
print(h("salt1" + "pw") == h("salt2" + "pw"))
print(len(h("pw")))Show the answer
Answer: A
Different salts give different hashes even for the same password, and SHA-256 hex is always 64 characters long.
2. Two users both chose the password `letmein`. With a separate random salt per user, what does the attacker see in the leaked table?
Show the answer
Answer: B
The salts differ, so the hashes differ. The attacker can't tell the passwords are equal and has to attack each user separately.
Practice
3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.
- PredictHash length
Type exactly what this program prints.
- ModifyAdd a salt
store(pw)saves an unsalted hash. Change both functions to use a salt:store(pw, salt)returnssalt + "$" + h(salt + pw)check(pw, record)splits the record at"$"into the salt and the hash, and returnsTrueonly ifh(salt + pw)equals the stored hash.
- WriteCrack the weak ones
Salts stop one guess from cracking everyone, but each weak password can still be guessed. Write
crack(leaked, wordlist):leakedis a dictionaryuser → "salt$hash"- return a dictionary
user → passwordfor every user whose password is inwordlist; users that can't be cracked are left out.
The
hfunction is already there.
Practise this lesson
Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.
Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.
Terms in this lesson
| Term | Chinese | Definition |
|---|---|---|
| Hash | 哈希 | A fixed-length, one-way fingerprint of some data. |
| Salt | 盐 | A random value mixed into each password before hashing so equal passwords get different hashes. |
| Dictionary attack | 字典攻击 | Guessing passwords by hashing a list of common passwords and comparing. |
| Password hash (bcrypt, Argon2) | 密码哈希(bcrypt、Argon2) | A deliberately slow, salted hash designed for storing passwords. |