Хэш-функции: целостность, а не шифрование
Хэш — это односторонний отпечаток данных. Его нельзя «расшифровать», и в этом весь смысл.
Хэш-функция — функция, превращающая данные любого размера в строку фиксированной длины так, что обратное восстановление невозможно, а малейшее изменение входа полностью меняет результат.
Главное заблуждение: хэш ≠ шифрование
Шифрование обратимо: зашифровал ключом — расшифровал ключом. Хэширование необратимо: из хэша нельзя получить исходные данные в принципе, ключа здесь нет. Поэтому хэш не используют, чтобы «спрятать и потом достать» данные, — он нужен, чтобы проверять, не изменилось ли что-то.
Три свойства хорошей хэш-функции
- Детерминированность. Один и тот же вход всегда даёт один и тот же хэш.
- Лавинный эффект. Изменение одного бита входа меняет хэш до неузнаваемости.
- Стойкость к коллизиям. Практически невозможно найти два разных входа с одинаковым хэшем.
Запустите пример — посмотрите детерминированность и лавинный эффект:
import hashlib
def h(text):
return hashlib.sha256(text.encode()).hexdigest()
print("Одинаковый вход -> одинаковый хэш:", h("hello") == h("hello"))
print()
print("hello :", h("hello")[:32])
print("hellp :", h("hellp")[:32]) # отличие в одну букву
print("Хэши совсем разные:", h("hello") != h("hellp"))
Вывод:
Одинаковый вход -> одинаковый хэш: True hello : 2cf24dba5fb0a30e26e83b2ac5b9e29e hellp : fdd7585e08c4e2afd71dcabdb4636c89 Хэши совсем разные: True
Замена всего одной буквы дала абсолютно другой хэш — это лавинный эффект.
Где применяют хэши
| Задача | Как помогает хэш |
| Хранение паролей | хранят хэш (с солью), а не сам пароль |
| Проверка целостности | сверяют хэш файла до и после передачи |
| Цифровые подписи | подписывают хэш документа, а не весь документ |
| Дедупликация, индексы | быстро сравнивать большие данные по короткому отпечатку |
Не все хэши одинаково годны
Старые функции MD5 и SHA-1 признаны небезопасными: для них научились находить коллизии. Для проверки целостности и подписей используют семейство SHA-256 и новее. А для паролей, как мы уже знаем, нужны специальные медленные функции (bcrypt/argon2), потому что обычный SHA-256 слишком быстр для этой задачи.
HMAC: хэш с секретом
Иногда нужно не просто проверить, что данные не менялись, но и что их прислал тот, у кого есть секретный ключ. Для этого есть HMAC — хэш, «подсоленный» секретным ключом. Без ключа правильный HMAC не вычислить, поэтому подделка обнаруживается:
import hmac, hashlib
key = b"secret-key"
message = b"summa=100"
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print("Подпись сообщения:", signature[:24], "...")
# Подменим сообщение — подпись больше не совпадёт
tampered = b"summa=9999"
new_sig = hmac.new(key, tampered, hashlib.sha256).hexdigest()
print("Совпала ли подпись после подмены:", hmac.compare_digest(signature, new_sig))
Вывод:
Подпись сообщения: 5c10f44770da85a6c1367d6a ... Совпала ли подпись после подмены: False
Итог
- Хэш необратим — это не шифрование; его нельзя «расшифровать».
- Хорошая хэш-функция детерминирована, обладает лавинным эффектом и стойка к коллизиям.
- MD5 и SHA-1 устарели; используйте SHA-256 и новее, а для паролей — bcrypt/argon2.
- HMAC добавляет к хэшу секретный ключ и подтверждает не только целостность, но и источник.