Хеширование паролей и соль

Серьёзные сайты не хранят твой пароль. Они хранят его хеш — и обязательно с солью.

Почему пароли нельзя хранить как есть

Если сайт хранит пароли открытым текстом и базу украдут, все пароли утекут мгновенно. Поэтому хранят не пароль, а его хеш. При входе сайт хеширует введённый пароль и сравнивает с сохранённым хешем — сам пароль ему знать не нужно.

import hashlib

def hash_password(password):
    return hashlib.sha256(password.encode()).hexdigest()

stored = hash_password("qwerty123")
print("В базе хранится:", stored[:16], "...")

# при входе сравниваем хеши
attempt = "qwerty123"
print("Вход верный:", hash_password(attempt) == stored)

Вывод:

В базе хранится: 9adf90... ...
Вход верный: True

Проблема: одинаковые пароли — одинаковые хеши

Хеш детерминирован, поэтому два пользователя с паролем 123456 получат одинаковый хеш. Злоумышленник заранее считает хеши популярных паролей (это называется радужной таблицей) и просто ищет совпадения. Убедимся в уязвимости:

import hashlib

def h(p):
    return hashlib.sha256(p.encode()).hexdigest()[:12]

print("user1 (123456):", h("123456"))
print("user2 (123456):", h("123456"))  # тот же хеш!
print("Совпадают:", h("123456") == h("123456"))

Вывод:

user1 (123456): 8d969eef6ec...
user2 (123456): 8d969eef6ec...
Совпадают: True

Решение: соль

Соль — это случайная строка, которую добавляют к паролю перед хешированием. У каждого пользователя своя соль. Тогда даже одинаковые пароли дают разные хеши, и радужные таблицы бесполезны.

import hashlib, secrets

def hash_with_salt(password, salt=None):
    if salt is None:
        salt = secrets.token_hex(8)  # своя случайная соль
    digest = hashlib.sha256((salt + password).encode()).hexdigest()
    return salt, digest

salt1, h1 = hash_with_salt("123456")
salt2, h2 = hash_with_salt("123456")

print("Пароль одинаковый, а хеши разные:")
print("user1:", h1[:16], "соль:", salt1)
print("user2:", h2[:16], "соль:", salt2)
print("Хеши совпадают:", h1 == h2)

Вывод:

Пароль одинаковый, а хеши разные:
user1: 3f9c...  соль: a1b2c3d4...
user2: e72a...  соль: 9f8e7d6c...
Хеши совпадают: False

Соль хранят рядом с хешем (она не секретная — её задача только в том, чтобы хеши были разными). При проверке пароля берут ту же соль, хешируют введённый пароль и сравнивают.

В реальной жизни

Для паролей используют не просто SHA-256, а специально замедленные функции: bcrypt, scrypt, Argon2. Они намеренно работают медленно, чтобы перебор пароля занимал недопустимо много времени.
Проверьте себя
1. Зачем к паролю добавляют соль перед хешированием?
AЧтобы хеш стал короче
BЧтобы одинаковые пароли давали разные хеши и радужные таблицы не работали
CЧтобы можно было расшифровать пароль
DЧтобы ускорить вход
2. Почему для паролей рекомендуют bcrypt или Argon2, а не обычный SHA-256?
AОни длиннее
BОни намеренно медленные, что делает перебор пароля невыгодным
CОни обратимы
DОни не требуют соли
Поддержать проект