Everyone Can Learn Program
中文 Start learning
Specialty · SecurityLesson 3 of 9Free to read

Hashing and salt · Cyber Security Basics

If the user database leaks, can attackers read everyone's password?

Course contents

Why 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 yourself

We 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])
Step by step
  1. L4Hash the bytes of the text and show the result as hex digits.
  2. L7Same password, same hash. That's how login works.
  3. L8One capital letter changes the whole hash.

A dictionary attack

Try it yourself

The 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)
Step by step
  1. L6What the attacker stole: users and hashes, no passwords.
  2. L7Real word lists hold millions of leaked passwords.
  3. L11Hash each guess and compare with the stored hash.
  4. L13dragon is in the list, so amy's password is found.amy -> dragon
  5. 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")))
  1. A
    False
    64
  2. B
    True
    64
  3. C
    False
    2
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?

  1. ATwo identical hashes
  2. BTwo different hashes
  3. CThe password itself
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.

  1. PredictHash length

    Type exactly what this program prints.

  2. ModifyAdd a salt

    store(pw) saves an unsalted hash. Change both functions to use a salt:

    • store(pw, salt) returns salt + "$" + h(salt + pw)
    • check(pw, record) splits the record at "$" into the salt and the hash, and returns True only if h(salt + pw) equals the stored hash.
  3. WriteCrack the weak ones

    Salts stop one guess from cracking everyone, but each weak password can still be guessed. Write crack(leaked, wordlist):

    • leaked is a dictionary user → "salt$hash"
    • return a dictionary user → password for every user whose password is in wordlist; users that can't be cracked are left out.

    The h function 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

TermChineseDefinition
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.