Хеширование и подписи в Python: что выбрать на практике
Когда нужен hashlib, когда hmac, а когда bcrypt или Argon2 — разбираем, чем эти инструменты отличаются и где ошибаются чаще всего.

Задача звучит одинаково — «захешировать данные», — но за ней прячутся три разные истории. Хеш файла для проверки целостности, хеш пароля для хранения в базе и подпись сообщения, чтобы получатель убедился в отправителе, требуют разных инструментов. Ставить hashlib.sha256 на пароль — ошибка, из-за которой утечка базы превращается в утечку всех паролей за часы. Разберём, что советуют использовать в 2024 году и почему.
Три задачи, которые путают
Прежде чем выбирать функцию, определите, что именно вы делаете. Для всех трёх задач в стандартной библиотеке или экосистеме Python есть свой инструмент.
| Задача | Что нужно | Инструмент |
|---|---|---|
| Целостность файла, контрольная сумма | Быстрый криптостойкий хеш | hashlib (SHA-256, BLAKE2) |
| Проверка подлинности сообщения с общим ключом | Хеш с секретным ключом | hmac |
| Хранение паролей | Медленный хеш с солью | Argon2, bcrypt, scrypt |
| Подпись без общего секрета | Асимметричная криптография | cryptography (Ed25519, RSA) |
Главная развилка: пароль — это не данные для хеширования обычным SHA-256. Быстрый хеш здесь работает против вас: чем быстрее считается, тем больше вариантов подберёт злоумышленник по украденной базе.
Целостность: hashlib
Для контрольных сумм файлов, идемпотентных ключей и дедупликации подходит hashlib из стандартной библиотеки. SHA-256 — рабочий выбор по умолчанию. MD5 и SHA-1 для проверки подлинности использовать нельзя: коллизии для них давно построены практически. Для контрольной суммы, где злоумышленник не участвует (например, кеш локальных файлов), MD5 формально ещё встречается, но привыкать к нему не стоит.
Файл читайте кусками, а не целиком в память — иначе на большом файле процесс съест всю оперативку. В Python 3.11+ есть готовый помощник:
# Python 3.11+
import hashlib
def file_sha256(path: str) -> str:
with open(path, "rb") as f:
# hashlib.file_digest читает файл потоково
digest = hashlib.file_digest(f, "sha256")
return digest.hexdigest()
print(file_sha256("archive.zip"))
На версиях до 3.11 file_digest нет — читайте вручную буфером:
import hashlib
def file_sha256(path: str) -> str:
h = hashlib.sha256()
with open(path, "rb") as f:
# читаем блоками по 64 КБ
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()
Если важна скорость на больших объёмах — посмотрите BLAKE2 (hashlib.blake2b). Он криптостойкий и на многих машинах быстрее SHA-256.
Пароли: bcrypt или Argon2, но не hashlib
Пароль нельзя хранить в открытом виде и нельзя хранить как sha256(password). Причины две. Первая — скорость: SHA-256 считает миллиарды хешей в секунду на GPU, значит перебор словаря по украденной базе занимает минуты. Вторая — соль: без уникальной соли одинаковые пароли дают одинаковые хеши, и злоумышленник видит совпадения плюс использует радужные таблицы.
Специальные функции для паролей решают обе проблемы: они намеренно медленные и настраиваемые по стоимости, а соль встроена в результат. На 2024 год рекомендации OWASP ставят Argon2id первым выбором, bcrypt — приемлемой альтернативой.
Правило простое: если результат хеширования пароля считается быстро, вы выбрали неправильную функцию. Медленность здесь — фича, а не проблема.
bcrypt
Библиотека bcrypt (устанавливается pip install bcrypt) проста и проверена временем. Соль генерируется автоматически и хранится внутри строки хеша.
import bcrypt
# при регистрации
password = b"correct horse battery staple"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# hashed кладём в базу как есть (там уже и соль, и параметры)
# при входе
if bcrypt.checkpw(password, hashed):
print("пароль верный")
else:
print("пароль неверный")
Параметр rounds задаёт стоимость: каждое +1 удваивает время. 12 — разумная отправная точка, замерьте на своём железе и подберите так, чтобы одна проверка занимала около 100–250 мс. Важное ограничение bcrypt: он обрезает пароль до 72 байт. Если у вас длинные парольные фразы, пропустите их предварительно через SHA-256 в base64 либо возьмите Argon2.
Argon2
Библиотека argon2-cffi (pip install argon2-cffi) даёт Argon2id — победителя конкурса Password Hashing Competition. Он настраивается по времени, памяти и параллелизму, что усложняет атаку на GPU.
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher() # параметры по умолчанию разумны для веба
# регистрация
hashed = ph.hash("correct horse battery staple")
# hashed — строка, кладём в базу
# вход
try:
ph.verify(hashed, "correct horse battery staple")
print("пароль верный")
except VerifyMismatchError:
print("пароль неверный")
У PasswordHasher есть метод check_needs_rehash(hashed) — он подскажет, что параметры устарели и пароль стоит перехешировать при следующем удачном входе. Это удобный способ поднимать стоимость без принудительной смены паролей.
Проверка подлинности: hmac и сравнение без утечки времени
Когда две стороны знают общий секрет и нужно убедиться, что сообщение не подменили, — это HMAC, а не голый хеш. Типичный случай: проверка подписи входящего вебхука.
import hmac
import hashlib
def verify_webhook(secret: bytes, payload: bytes, signature_hex: str) -> bool:
expected = hmac.new(secret, payload, hashlib.sha256).hexdigest()
# сравнение с защитой от тайминг-атаки
return hmac.compare_digest(expected, signature_hex)
Ключевая деталь — hmac.compare_digest. Обычное сравнение строк == прекращает работу на первом несовпавшем символе, и по времени ответа злоумышленник может подбирать подпись символ за символом. compare_digest сравнивает за постоянное время. Используйте его везде, где сверяете секреты, токены или подписи.
Асимметричные подписи: cryptography
Если получатель не должен знать секрет отправителя (публичный API, распространение обновлений, JWT с открытым ключом) — нужна асимметричная подпись. Стандартная библиотека этого не умеет, берите пакет cryptography (pip install cryptography). Для новых проектов удобен Ed25519 — короткие ключи, быстрая проверка.
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
from cryptography.exceptions import InvalidSignature
# генерация пары ключей (приватный храним в секрете)
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
message = b"release v1.4.2"
signature = private_key.sign(message)
# на стороне получателя есть только public_key и signature
try:
public_key.verify(signature, message)
print("подпись верна")
except InvalidSignature:
print("подпись недействительна")
Модуль называется hazmat (hazardous materials) не случайно: авторы предупреждают, что низкоуровневая криптография требует понимания. Не изобретайте свои схемы поверх примитивов — используйте готовые рецепты из документации.
Когда что выбирать
- Контрольная сумма, дедупликация, ETag —
hashlib.sha256илиblake2b. - Хранение паролей — Argon2id (
argon2-cffi), при ограничениях окружения — bcrypt. Никогда неhashlib. - Проверка вебхука или токена с общим секретом —
hmac+compare_digest. - Подпись без передачи секрета получателю — Ed25519 через
cryptography.
Частые ошибки
- Хеширование пароля через SHA-256 «с солью вручную» — быстрый хеш остаётся быстрым, перебор не замедляется.
- Сравнение подписей через
==вместоcompare_digest— открывает тайминг-атаку. - Одна общая соль на все пароли — сводит на нет защиту от радужных таблиц. У специализированных функций соль всегда уникальна и автоматическая.
- Использование MD5 или SHA-1 там, где важна стойкость к коллизиям.
- Забыли, что bcrypt режет пароль на 72 байтах — длинные фразы молча усекаются.
Что делать дальше
Замерьте время проверки пароля на вашем боевом железе и подберите стоимость bcrypt или параметры Argon2 так, чтобы одна проверка занимала около 100–250 мс — это баланс между удобством пользователя и стоимостью перебора. Настройте check_needs_rehash, чтобы поднимать стоимость со временем без принудительной смены паролей. Для подписей и ключей закрепите хранение приватных ключей в секрет-менеджере, а не в коде. И держите под рукой актуальную версию шпаргалки OWASP по хранению паролей — рекомендации по параметрам меняются вместе с ростом вычислительных мощностей.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: OWASP Password Storage Cheat Sheet, Python hashlib — документация стандартной библиотеки
Частые вопросы
Можно ли хешировать пароль просто через hashlib с солью?
У меня Python 3.9, а вы используете hashlib.file_digest. Что делать?
Argon2 или bcrypt — что лучше выбрать?
Почему нельзя сравнивать подписи через обычное ==?
Пароли уже хранятся как SHA-256, миграция обязательна?
Что использовать для проверки целостности загруженного файла?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.