Модуль hashlib: хеш-функции в Python
Python hashlib - модуль стандартной библиотеки, который вычисляет хеши данных алгоритмами SHA-256, SHA-512, SHA-3, BLAKE2, MD5 и другими. В статье показано, как получить хеш строки и большого файла, чем отличаются алгоритмы, как правильно хранить пароли и проверять подлинность сообщений через HMAC.

Содержание статьи
Что такое хеш и зачем он нужен
Хеш-функция превращает данные любого размера в строку фиксированной длины. Одинаковые данные всегда дают одинаковый хеш, а изменение даже одного байта меняет результат до неузнаваемости. Восстановить исходные данные по хешу нельзя.
На практике хеши используют для нескольких задач:
- проверка целостности: совпадает ли скачанный файл с оригиналом;
- поиск дубликатов файлов по содержимому;
- ключи кеша и идентификаторы для больших объектов;
- хранение паролей, но только специальными медленными функциями, о них ниже.
Хеш строки
Хеш-функции работают с байтами, а не со строками. Строку сначала кодируют методом encode(), по умолчанию в UTF-8. Если передать str напрямую, Python выбросит TypeError: Strings must be encoded before hashing.
import hashlib
data = "Hello, World!".encode("utf-8")
h = hashlib.sha256(data)
print(h.hexdigest())
# dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f
print(len(h.digest())) # 32 байта
print(hashlib.md5("привет".encode()).hexdigest())
# 608333adc72f545078ede3aad71bfe74hexdigest() возвращает хеш строкой из шестнадцатеричных символов, digest() возвращает сырые байты. Кодировку важно выбирать одинаковую при каждом вычислении: одна и та же кириллическая строка в UTF-8 и cp1251 даст разные хеши. Подробнее о кодировках рассказано в статье про кодировку строк.
Пошаговое хеширование через update
Объект хеша можно наполнять частями методом update(). Результат будет таким же, как при передаче всех данных сразу. Метод copy() сохраняет промежуточное состояние, если нужно получить хеш нескольких вариантов с общим началом.
h = hashlib.sha256()
h.update(b"Hello, ")
h.update(b"World!")
print(h.hexdigest() == hashlib.sha256(b"Hello, World!").hexdigest()) # TrueХеш файла
Файл открывают в двоичном режиме "rb". В Python 3.11 появилась функция hashlib.file_digest(), которая сама читает файл блоками и не загружает его в память целиком.
import hashlib
with open("python-3.12.exe", "rb") as f:
print(hashlib.file_digest(f, "sha256").hexdigest())Для старых версий Python тот же результат дает цикл с update(). Размер блока 64-1024 КБ хорошо подходит для большинства дисков, а приемы чтения больших файлов разобраны в отдельной статье о чтении больших файлов.
def sha256_of(path, chunk=1024 * 1024):
h = hashlib.sha256()
with open(path, "rb") as f:
while block := f.read(chunk):
h.update(block)
return h.hexdigest()
expected = "..." # контрольная сумма с сайта загрузки
print(sha256_of("python-3.12.exe") == expected)Какой алгоритм выбрать
Набор алгоритмов, доступных на любой платформе, хранится в hashlib.algorithms_guaranteed. Дополнительные алгоритмы из OpenSSL перечислены в algorithms_available, их создают по имени через hashlib.new("имя").
| Алгоритм | Длина хеша, бит | Применение |
|---|---|---|
md5 | 128 | только контрольные суммы и дубликаты, для защиты непригоден |
sha1 | 160 | устарел, найдены коллизии; встречается в старых системах и Git |
sha256 | 256 | универсальный выбор: подписи, контрольные суммы, токены |
sha512 | 512 | то же, на 64-битных процессорах часто быстрее sha256 |
sha3_256 | 256 | стандарт SHA-3 на другой математической основе |
blake2b | до 512 | быстрый, поддерживает ключ и произвольную длину результата |
MD5 и SHA-1 нельзя использовать там, где важна защита от подделки: для них умеют подбирать разные данные с одинаковым хешем. Для поиска дубликатов они подходят, а в сборках Python с режимом FIPS их вызывают с аргументом usedforsecurity=False.
short = hashlib.blake2b(b"data", digest_size=8).hexdigest()
print(short) # 2e6c49b7ae486fa8, короткий идентификаторХранение паролей
Обычный SHA-256 для паролей не годится. Он считается очень быстро, и перебор миллиардов вариантов на видеокарте занимает минуты. Кроме того, без соли одинаковые пароли дают одинаковые хеши. Для паролей в hashlib есть медленные функции с солью: scrypt() и pbkdf2_hmac().
import hashlib, hmac, os
def hash_password(password: str) -> tuple[bytes, bytes]:
salt = os.urandom(16)
key = hashlib.scrypt(password.encode(), salt=salt, n=2**14, r=8, p=1)
return salt, key
def check_password(password: str, salt: bytes, key: bytes) -> bool:
new = hashlib.scrypt(password.encode(), salt=salt, n=2**14, r=8, p=1)
return hmac.compare_digest(new, key)
salt, key = hash_password("qwerty123")
print(check_password("qwerty123", salt, key)) # TrueСоль генерируется случайно для каждого пароля и хранится рядом с хешем в открытом виде. Сравнение через hmac.compare_digest() занимает одинаковое время независимо от того, где найдено различие, это защищает от атак по времени. Для pbkdf2_hmac("sha256", ...) рекомендуют сотни тысяч итераций.
Подпись сообщений через HMAC
Хеш подтверждает, что данные не повреждены, но не доказывает, кто их отправил: злоумышленник может изменить сообщение и пересчитать хеш. HMAC добавляет секретный ключ, известный только отправителю и получателю. Так подписывают вебхуки платежных систем и API-запросы.
import hmac
secret = b"server-secret"
body = b'{"order": 42, "status": "paid"}'
signature = hmac.new(secret, body, "sha256").hexdigest()
received = signature # подпись из заголовка запроса
print(hmac.compare_digest(signature, received)) # TrueМодуль hmac тоже входит в стандартную библиотеку. Полный список функций hashlib и их параметров есть в документации Python.
Частые вопросы
Ответы на вопросы, которые чаще всего задают о hashlib.
Можно ли расшифровать хеш SHA-256 или MD5?
Нет, хеширование необратимо. Онлайн-сервисы "расшифровки" ищут хеш в базах заранее посчитанных значений популярных строк, поэтому простые пароли находятся, а случайные данные нет.
Почему хеш одной строки отличается в разных программах?
Обычно виновата кодировка или невидимые символы: перевод строки в конце, пробел, BOM. Хеш считается от байтов, и любое расхождение в них дает другой результат.
Подходит ли встроенная функция hash() вместо hashlib?
Нет. hash() нужна для словарей и множеств, для строк ее значение меняется при каждом запуске интерпретатора из-за случайной соли. Для сохранения и сравнения между запусками используйте hashlib.
Чем хеширование отличается от шифрования?
Зашифрованные данные можно вернуть в исходный вид, зная ключ. Хеш вернуть нельзя, его можно только посчитать заново и сравнить.