HMAC Python — hmac.new() SHA-256 руководство + примеры кода

·DevOps Engineer & Python Automation Specialist·ПровереноMaria Santos·Опубликовано

Используйте бесплатный HMAC Generator прямо в браузере — установка не требуется.

Попробовать HMAC Generator онлайн →

Каждый вебхук-колбэк, каждый подписанный API-запрос, каждое уведомление от Stripe или GitHub использует HMAC-подпись, чтобы подтвердить неизменность полезной нагрузки. Модуль hmac из стандартной библиотеки Python реализует HMAC-SHA256 в Python одним вызовом: hmac.new(key, msg, hashlib.sha256). Никакого pip install, никаких C-расширений, никаких сторонних зависимостей. Для быстрой одноразовой проверки подписи без написания кода воспользуйтесь онлайн-генератором HMAC — результат появится мгновенно. Это руководство охватывает hmac.new(), hmac.digest(), hmac.compare_digest(), кодирование в Base64, верификацию вебхуков, подпись API-запросов и все алгоритмы от SHA-1 до SHA-512. Все примеры рассчитаны на Python 3.7+.

  • hmac.new(key, msg, hashlib.sha256) — стандартная точка входа: key и msg должны быть bytes, digestmod обязателен начиная с Python 3.4.
  • hmac.digest(key, msg, "sha256") — более быстрая одиночная альтернатива, добавленная в Python 3.7: возвращает сырые байты без промежуточного объекта.
  • Всегда верифицируйте подписи через hmac.compare_digest() для защиты от атак по времени — никогда не используйте == для сравнения HMAC.
  • Кодируйте сырые байты .digest() в Base64 для HTTP-заголовков и вебхук-подписей: base64.b64encode(h.digest()).
  • Модуль hmac принимает любой алгоритм hashlib: sha1, sha256, sha384, sha512, md5, blake2b.

Что такое HMAC?

HMAC (Hash-based Message Authentication Code) — конструкция, определённая в RFC 2104, которая объединяет секретный ключ с хеш-функцией для получения тега аутентификации фиксированного размера. В отличие от обычного хеша (который может вычислить любой), HMAC требует знания секретного ключа. Это позволяет использовать его для проверки как целостности, так и подлинности сообщения. При изменении даже одного байта сообщения или ключа вывод полностью меняется. Конструкция работает путём хеширования ключа, подвергнутого XOR с двумя различными константами дополнения (ipad и opad), оборачивая сообщение двумя операциями хеширования. Модуль hmac Python реализует этот RFC напрямую.

Before · Python
After · Python
# Обычный SHA-256 хеш — без секретного ключа, может вычислить любой
hashlib.sha256(b"payment:9950:USD").hexdigest()
# "7a3b1c..."  (детерминированный, публичный)
# HMAC-SHA256 — требует секретного ключа для вычисления
hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest()
# "e4f2a8..."  (может вычислить только владелец ключа)

hmac.new() — точка входа стандартной библиотеки

Модуль hmac входит в стандартную библиотеку Python. Достаточно двух импортов: import hmac, hashlib. Три основные функции: hmac.new() (создаёт объект HMAC), hmac.digest() (одиночный вызов, Python 3.7+) и hmac.compare_digest() (сравнение за константное время). pip install не требуется.

hmac.new(key, msg, digestmod) принимает три аргумента. key и msg должны быть байтоподобными объектами ( bytes, bytearray или memoryview). Аргумент digestmod обязателен начиная с Python 3.4 и принимает любой конструктор hashlib (например, hashlib.sha256) или строковое имя вроде "sha256".

Python 3.7+ — минимальный пример HMAC-SHA256
import hmac
import hashlib

key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'

# Создаём объект HMAC и получаем hex-подпись
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"

Объект HMAC предоставляет два метода вывода. .digest() возвращает сырые байты (32 байта для SHA-256, 64 для SHA-512). .hexdigest() возвращает строку в нижнем регистре hex. Это обычная Python-строка str — шаг декодирования не нужен.

Python 3.7+ — .digest() vs .hexdigest()
import hmac
import hashlib

key = b"service_auth_key"
msg = b"GET /api/v2/orders 2026-03-28T14:30:00Z"

h = hmac.new(key, msg, hashlib.sha256)

raw_bytes = h.digest()
print(type(raw_bytes), len(raw_bytes))
# <class 'bytes'> 32

hex_string = h.hexdigest()
print(type(hex_string), len(hex_string))
# <class 'str'> 64

# Они представляют одни и те же данные — hex это просто строковое кодирование байтов
assert raw_bytes.hex() == hex_string

Если ключ или сообщение являются строкой Python, вызовите .encode() для преобразования в байты перед передачей в hmac.new(). Это спотыкает почти всех в первый раз — строки Python 3 являются Unicode, а не байтами, и модуль hmac их не принимает.

Python 3.7+ — кодирование строк в байты
import hmac
import hashlib

# Строковые ключ и сообщение — .encode() преобразует в байты UTF-8
api_key = "sk_live_9f3a2b7c4d8e"
request_body = '{"customer_id":"cust_4421","plan":"enterprise"}'

signature = hmac.new(
    api_key.encode(),
    request_body.encode(),
    hashlib.sha256
).hexdigest()

print(signature)
# "3a9f1b..."  — стабильный hex-вывод
Примечание:Параметр digestmod не имеет значения по умолчанию начиная с Python 3.4. Вызов hmac.new(key, msg) без него вызывает TypeError. До 3.4 по умолчанию использовался MD5, именно поэтому разработчики Python убрали значение по умолчанию — чтобы вынудить делать явный, безопасный выбор.

HMAC-SHA256 Base64, SHA-1, SHA-512 и MD5

Функция hmac.new() работает с любым алгоритмом хеширования, доступным в hashlib. Большинство провайдеров вебхуков и API-шлюзов используют HMAC-SHA256, однако вы встретите SHA-1 в OAuth 1.0a, SHA-512 в протоколах, где он обязателен, и MD5 в устаревших системах, которые не были обновлены.

HMAC-SHA256 с Base64-выводом

Многие провайдеры вебхуков отправляют подпись в виде строки в кодировке Base64 в HTTP-заголовке. Для получения такого же формата передайте сырые байты .digest() в base64.b64encode().

Python 3.7+ — HMAC-SHA256 кодирование в Base64
import hmac
import hashlib
import base64

key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'

# Сырой дайджест → Base64 (стандарт для заголовков Authorization и подписей вебхуков)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")

print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

# Это значение сравнивается с заголовком X-Signature
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

HMAC-SHA1 — совместимость с устаревшими протоколами

SHA-1 считается слабым для новых разработок, однако HMAC-SHA1 по-прежнему требуется в OAuth 1.0a и некоторых старых реализациях вебхуков. Код идентичен — нужно лишь заменить алгоритм.

Python 3.7+ — HMAC-SHA1
import hmac
import hashlib

consumer_secret = b"oauth_consumer_secret_2026"
token_secret = b"oauth_token_secret_2026"

# OAuth 1.0a использует consumer_secret&token_secret в качестве ключа подписи
signing_key = consumer_secret + b"&" + token_secret
base_string = b"GET&https%3A%2F%2Fapi.service.com%2Fv1%2Forders&oauth_nonce%3D7f3a91bc"

sig = hmac.new(signing_key, base_string, hashlib.sha1).digest()

import base64
oauth_signature = base64.b64encode(sig).decode("ascii")
print(oauth_signature)
# "Tza3R9sE..."  — URL-кодируйте это для заголовка Authorization

HMAC-SHA512 — более длинный вывод

Python 3.7+ — HMAC-SHA512
import hmac
import hashlib

key = b"high_security_signing_key_64_bytes_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
msg = b'{"transfer_id":"xfr_9c2e","amount":500000,"currency":"EUR"}'

h = hmac.new(key, msg, hashlib.sha512)

print(len(h.digest()))   # 64 байта (512 бит)
print(len(h.hexdigest()))  # 128 hex-символов
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."

HMAC-MD5 — только для устаревших систем

Python 3.7+ — HMAC-MD5
import hmac
import hashlib

# MD5 криптографически взломан — используйте только для совместимости с устаревшими протоколами
key = b"legacy_api_key"
msg = b"action=charge&amount=1500&merchant=store_42"

sig = hmac.new(key, msg, hashlib.md5).hexdigest()
print(sig)  # 32-символьная hex-строка
# "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Предупреждение:HMAC-MD5 допустим только для обратной совместимости с системами, которые невозможно перевести на новый стандарт. Для любого нового проекта используйте минимум HMAC-SHA256. MD5 имеет известные уязвимости к коллизиям, которые, хотя и менее напрямую применимы в режиме HMAC, делают его плохим выбором по умолчанию.

Справочник параметров hmac.new()

Сигнатура конструктора: hmac.new(key, msg=None, digestmod). Все три функции модуля используют одинаковый шаблон для аргументов ключа и алгоритма.

Конструктор hmac.new()

Параметр
Тип
По умолчанию
Описание
key
bytes | bytearray
(обязательный)
Секретный ключ — должен быть bytes, не строка
msg
bytes | None
None
Начальное сообщение для хеширования; дополнительные данные можно передавать через .update()
digestmod
str | callable
(обязательный)
Алгоритм хеширования — например, hashlib.sha256 или строка "sha256"

Одиночный вызов hmac.digest() (Python 3.7+)

Параметр
Тип
Описание
key
bytes
Секретный ключ
msg
bytes
Сообщение для аутентификации
digest
str | callable
Алгоритм хеширования — аналогично digestmod в hmac.new()

Параметр digestmod принимает как вызываемый объект (например, hashlib.sha256), так и строковое имя (например, "sha256"). Форма с вызываемым объектом предпочтительна, так как проверяется при импорте — опечатка в строковой форме обнаруживается только во время выполнения.

hmac.digest() — быстрый одиночный HMAC (Python 3.7+)

В Python 3.7 была добавлена функция уровня модуля hmac.digest(key, msg, digest). Она вычисляет HMAC за один вызов без создания промежуточного объекта HMAC. Возвращаемое значение — сырые байты (эквивалент вызова .digest() на объекте). Функция использует оптимизированную реализацию на C в CPython и избегает накладных расходов на выделение памяти под объект, что делает её заметно быстрее в интенсивных циклах.

Python 3.7+ — hmac.digest() одиночный вызов
import hmac
import hashlib

key = b"batch_signing_key_2026"
messages = [
    b'{"order_id":"ord_001","total":4500}',
    b'{"order_id":"ord_002","total":8900}',
    b'{"order_id":"ord_003","total":2200}',
]

# Одиночный вызов — без промежуточного объекта HMAC
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]

# Преобразуем в hex для отображения
for msg, sig in zip(messages, signatures):
    print(f"{msg[:30]}... -> {sig.hex()[:24]}...")

Ограничение: hmac.digest() возвращает только сырые байты. Если нужна hex-строка напрямую, всё равно потребуется hmac.new() с его методом .hexdigest(), либо нужно вызвать .hex() на результирующих байтах.

Примечание:hmac.digest() не поддерживает инкрементальные вызовы .update(). Если вы читаете большой файл по частям и нужно вычислить HMAC его содержимого, используйте hmac.new() и вызывайте .update(chunk) в цикле.

Верификация HMAC-подписи вебхука и API-ответа

Наиболее распространённое применение HMAC в Python — верификация подписей вебхуков. Каждый крупный провайдер (Stripe, GitHub, Shopify, Twilio) подписывает полезные нагрузки с помощью HMAC-SHA256 и отправляет подпись в заголовке. Схема всегда одинакова: пересчитайте HMAC от сырого тела запроса, затем сравните с помощью hmac.compare_digest().

Верификация подписи вебхука

Python 3.7+ — верификация HMAC вебхука (Flask)
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WEBHOOK_SECRET = b"whsec_MbkP7x9yFqHGn3tRdWz5"

@app.route("/webhooks/payments", methods=["POST"])
def handle_payment_webhook():
    # Получаем сырое тело — должно точно совпадать с тем, что было подписано
    raw_body = request.get_data()

    # Получаем подпись из заголовка
    received_sig = request.headers.get("X-Signature-256", "")

    # Пересчитываем HMAC от сырого тела
    expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()

    # Сравнение за константное время — защита от атак по времени
    if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
        abort(403, "Invalid signature")

    # Подпись подтверждена — обрабатываем событие
    event = request.get_json()
    print(f"Verified event: {event['type']} for {event['data']['amount']}")
    return "", 200

Функция hmac.compare_digest() сравнивает две строки или последовательности байтов за константное время. Обычное сравнение == прерывается на первом несовпадающем байте. Злоумышленник может измерять время ответа при множестве запросов и постепенно восстановить ожидаемую подпись побайтово. Сравнение за константное время устраняет этот побочный канал.

Верификация вебхука GitHub

Формат вебхуков GitHub наглядно демонстрирует полную схему. GitHub отправляет заголовок X-Hub-Signature-256, содержащий sha256= с последующим hex-кодированным HMAC-SHA256 сырого тела запроса, подписанным секретом вебхука, настроенным в параметрах репозитория. Ключевое отличие от базовой верификации вебхука: нужно убрать префикс sha256= перед сравнением, и необходимо читать сырые байты тела запроса — предварительный разбор JSON меняет байтовое представление и нарушает верификацию.

Python 3.7+ — верификация X-Hub-Signature-256 GitHub
import hmac
import hashlib
from flask import Flask, request, abort

app = Flask(__name__)
GITHUB_WEBHOOK_SECRET = b"your_github_webhook_secret"

@app.route("/webhooks/github", methods=["POST"])
def handle_github_webhook():
    # GitHub отправляет: X-Hub-Signature-256: sha256=<hex_digest>
    sig_header = request.headers.get("X-Hub-Signature-256", "")

    if not sig_header.startswith("sha256="):
        abort(403, "Missing or malformed signature header")

    received_hex = sig_header[len("sha256="):]
    raw_body = request.get_data()  # сырые байты — не разбирайте JSON до этого

    expected_hex = hmac.new(
        GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(expected_hex, received_hex):
        abort(403, "Signature mismatch — payload may have been tampered with")

    event_type = request.headers.get("X-GitHub-Event", "unknown")
    payload = request.get_json()
    print(f"Verified GitHub {event_type} event: {payload.get('action', '')}")
    return "", 200

Та же схема применяется для Shopify (X-Shopify-Hmac-SHA256) и Twilio (X-Twilio-Signature) — разница только в имени заголовка и в том, является ли подпись hex- или Base64-кодированной. Всегда проверяйте документацию провайдера для уточнения формата подписи — смешение hex и Base64 является наиболее распространённой причиной ошибок несовпадения подписи.

Аутентификация API-запросов с HMAC

Некоторые API требуют, чтобы клиент подписывал каждый запрос с помощью HMAC. Подписываемая строка обычно включает метод HTTP, путь, временную метку и тело запроса. Вот шаблон для межсервисной аутентификации.

Python 3.7+ — подпись API-запросов с HMAC-SHA256
import hmac
import hashlib
import time
import json

def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
    """Генерация HMAC-SHA256 подписи для API-запроса."""
    timestamp = str(int(time.time()))

    # Формируем строку для подписи — метод + путь + временная метка + тело
    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"

    signature = hmac.new(
        secret,
        signing_string.encode(),
        hashlib.sha256
    ).hexdigest()

    return {
        "X-Timestamp": timestamp,
        "X-Signature": signature,
    }

# Использование
api_secret = b"sk_hmac_9f3a2b7c4d8e1a6f"
body = json.dumps({"customer_id": "cust_4421", "action": "suspend_account"})

headers = sign_request(api_secret, "POST", "/api/v2/customers/actions", body)
print(headers)
# {"X-Timestamp": "1711612200", "X-Signature": "a3f1b9c0..."}

Подпись HTTP-запросов с библиотекой requests

Python 3.7+ — HMAC-подписанные запросы через библиотеку requests
import hmac
import hashlib
import time
import json
import requests

API_SECRET = b"sk_hmac_9f3a2b7c4d8e1a6f"
BASE_URL = "https://api.billing-service.internal"

def make_signed_request(method: str, path: str, payload: dict) -> requests.Response:
    body = json.dumps(payload, separators=(",", ":"))  # компактный JSON, детерминированный
    timestamp = str(int(time.time()))

    signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
    signature = hmac.new(API_SECRET, signing_string.encode(), hashlib.sha256).hexdigest()

    headers = {
        "Content-Type": "application/json",
        "X-Timestamp": timestamp,
        "X-Signature": f"hmac-sha256={signature}",
    }

    try:
        return requests.request(method, f"{BASE_URL}{path}", data=body, headers=headers)
    except requests.RequestException as e:
        raise RuntimeError(f"Signed request failed: {e}") from e

# Отправка подписанного POST-запроса
resp = make_signed_request("POST", "/api/v2/invoices", {
    "customer_id": "cust_4421",
    "line_items": [
        {"description": "Pro plan - March 2026", "amount": 4900},
        {"description": "Extra seats (3)", "amount": 2100},
    ],
})
print(resp.status_code, resp.json())

Важное замечание: используйте separators=(",", ":") при сериализации тела для подписи. По умолчанию json.dumps() добавляет пробелы после разделителей, что меняет байтовое представление и нарушает верификацию подписи, если сервер сериализует иначе. Компактный JSON обеспечивает каноническую форму.

Генерация HMAC из командной строки

Иногда нужно вычислить HMAC без написания скрипта. Флаг -c Python и openssl позволяют сделать это из терминала.

bash — HMAC-SHA256 через Python однострочник
python3 -c "
import hmac, hashlib
print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest())
"
# выводит: 64-символьная hex-строка
bash — HMAC-SHA256 через openssl (для сравнения)
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret"
# SHA2-256(stdin)= 7d11...

# Передать файл через HMAC openssl
openssl dgst -sha256 -hmac "my_secret" < payload.json
bash — HMAC из переменной окружения
# Храните ключ в переменной окружения, чтобы не показывать в истории оболочки
export HMAC_KEY="sk_live_9f3a2b"
echo -n '{"event":"test"}' | python3 -c "
import hmac, hashlib, sys, os
key = os.environ['HMAC_KEY'].encode()
msg = sys.stdin.buffer.read()
print(hmac.new(key, msg, hashlib.sha256).hexdigest())
"
Примечание:Флаг echo -n критически важен — без него echo добавляет символ новой строки к сообщению, что изменяет вывод HMAC. Это наиболее частая причина несовпадения подписи при отладке из терминала.

Высокопроизводительная альтернатива — библиотека cryptography

Для большинства приложений стандартного модуля hmac достаточно по производительности. Если вы уже используете библиотеку cryptography для TLS или работы с сертификатами, она также предоставляет HMAC на основе OpenSSL. Главное практическое отличие от stdlib — API на основе исключений через метод .verify(), описанный ниже: он вызывает исключение при несовпадении, а не возвращает логическое значение, которое разработчик может забыть проверить.

bash — установка cryptography
pip install cryptography
Python 3.7+ — HMAC через библиотеку cryptography
from cryptography.hazmat.primitives import hashes, hmac as crypto_hmac

key = b"webhook_signing_key_2026"
message = b'{"event":"subscription.renewed","plan":"enterprise"}'

h = crypto_hmac.HMAC(key, hashes.SHA256())
h.update(message)
signature = h.finalize()  # сырые байты

print(signature.hex())
# "9c4e2a..."

# Режим верификации — вызывает InvalidSignature при несовпадении
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature)  # вызывает cryptography.exceptions.InvalidSignature при ошибке

Метод .verify() библиотеки cryptography особенно удобен: при несовпадении он вызывает исключение, а не возвращает логическое значение. Это затрудняет случайное игнорирование ошибки верификации. Метод hmac.compare_digest() стандартной библиотеки возвращает True/ False, что может быть незаметно проигнорировано, если разработчик забудет проверить возвращаемое значение.

Вывод в терминале с подсветкой синтаксиса

Если вы отлаживаете HMAC-подписи в терминале и хотите цветной вывод, rich отлично справляется с этой задачей.

bash — установка rich
pip install rich
Python 3.7+ — цветной вывод HMAC с rich
import hmac
import hashlib
from rich.console import Console
from rich.table import Table

console = Console()

key = b"debug_signing_key"
messages = {
    "/api/v2/orders": b'{"status":"active"}',
    "/api/v2/invoices": b'{"status":"pending"}',
    "/api/v2/customers": b'{"status":"verified"}',
}

table = Table(title="HMAC-SHA256 Signatures")
table.add_column("Endpoint", style="cyan")
table.add_column("Signature (first 32 chars)", style="green")

for endpoint, body in messages.items():
    sig = hmac.new(key, body, hashlib.sha256).hexdigest()
    table.add_row(endpoint, sig[:32] + "...")

console.print(table)
Примечание:Rich предназначен только для вывода в терминале. Не используйте его при записи HMAC-подписей в файлы, HTTP-заголовки или системы агрегации логов — ANSI-escape-коды испортят вывод.

Работа с большими файлами — инкрементальный HMAC

Для файлов размером более 50 МБ загружать всё содержимое в память только для вычисления HMAC расточительно. Метод .update() объекта HMAC позволяет передавать данные порциями. Это обеспечивает постоянное потребление памяти независимо от размера файла.

Python 3.7+ — HMAC большого файла по частям
import hmac
import hashlib

def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
    """Вычисление HMAC-SHA256 файла без полной загрузки в память."""
    h = hmac.new(key, digestmod=hashlib.sha256)

    try:
        with open(filepath, "rb") as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                h.update(chunk)
    except OSError as e:
        raise OSError(f"Cannot read file '{filepath}': {e}") from e

    return h.hexdigest()

# Подпись 2 ГБ экспорта базы данных
signing_key = b"backup_integrity_key_2026"
signature = hmac_file(signing_key, "/var/backups/db-export-2026-03.sql.gz")
print(f"HMAC-SHA256: {signature}")
Python 3.7+ — верификация HMAC-подписи скачанного файла
import hmac
import hashlib

def verify_file_hmac(key: bytes, filepath: str, expected_hex: str) -> bool:
    """Верификация HMAC-SHA256 подписи файла."""
    h = hmac.new(key, digestmod=hashlib.sha256)

    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            h.update(chunk)

    return hmac.compare_digest(h.hexdigest(), expected_hex)

# Верификация скачанного артефакта
is_valid = verify_file_hmac(
    key=b"release_signing_key",
    filepath="/tmp/release-v3.2.0.tar.gz",
    expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"File integrity: {'valid' if is_valid else 'CORRUPTED'}")
Примечание:Переходите к порционному HMAC, когда файл превышает 50–100 МБ или при обработке загрузок на веб-сервере, где важно потребление памяти на запрос. Подход с .update() использует фиксированный объём памяти chunk_size независимо от размера файла. Рекомендуется размер порции 64 КБ — достаточно большой, чтобы амортизировать накладные расходы системных вызовов, и достаточно малый, чтобы оставаться в кеше L2 на большинстве процессоров.

Генерация криптографически стойкого HMAC-ключа в Python

Слабый или предсказуемый ключ подрывает всю конструкцию HMAC. Модуль secrets (Python 3.6+) предоставляет криптографически стойкие случайные байты. Для HMAC-SHA256 используйте ключ длиной 32 байта. Для HMAC-SHA512 — 64 байта. Это соответствует внутреннему размеру блока соответствующих хеш-алгоритмов, что является оптимальной длиной ключа согласно RFC 2104.

Python 3.7+ — генерация HMAC-ключей с помощью secrets
import secrets

# Генерация ключей, соответствующих размеру блока хеш-алгоритма
sha256_key = secrets.token_bytes(32)   # 256 бит — для HMAC-SHA256
sha512_key = secrets.token_bytes(64)   # 512 бит — для HMAC-SHA512

# Hex-представление — безопасно для конфигурационных файлов и переменных окружения
print(f"HMAC-SHA256 key: {sha256_key.hex()}")
# например "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"

print(f"HMAC-SHA512 key: {sha512_key.hex()}")
# 128-символьная hex-строка

# URL-safe Base64 — компактный, безопасный для HTTP-заголовков
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Base64 key: {b64_key}")
# например "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="
Предупреждение:Никогда не используйте random.random() или random.randbytes() из модуля random для HMAC-ключей. Стандартный модуль random использует генератор псевдослучайных чисел Мерсенна, который предсказуем после наблюдения 624 выводов. Всегда используйте secrets.token_bytes() для криптографически стойкой случайности.

Длина ключа и требования RFC 2104

RFC 2104 указывает, что HMAC-ключ может быть любой длины, но рекомендует ключ не менее L байт, где L — длина вывода базовой хеш-функции. Для HMAC-SHA256 это 32 байта (256 бит). Ключи короче L бит пропорционально снижают запас безопасности. Ключи длиннее размера блока хеш-функции (64 байта для SHA-256, 128 байт для SHA-512) сначала хешируются до размера блока, поэтому использование ключей длиннее размера блока не даёт никакого преимущества. Придерживайтесь 32 байт для HMAC-SHA256 и 64 байт для HMAC-SHA512.

Безопасное хранение и ротация ключей

Никогда не встраивайте HMAC-ключи непосредственно в исходный код. Стандартный подход для производственных систем — загружать ключ из переменной окружения при запуске: os.environ["HMAC_SECRET"].encode(). Для окружений с повышенными требованиями к безопасности храните ключи в системе управления секретами, такой как AWS Secrets Manager, HashiCorp Vault или GCP Secret Manager, и получайте их во время выполнения. Эти системы ведут журналы аудита, управляют доступом и обеспечивают автоматическую ротацию без необходимости повторного развёртывания кода.

Планируйте ротацию ключей с самого начала. При смене ключа существует окно, в течение которого запросы в пути были подписаны старым ключом и не пройдут верификацию с новым. Стандартная защитная мера — краткий период перекрытия: принимать подписи как от старого, так и от нового ключа в течение короткого времени (от минут до часов), затем отозвать старый ключ. Если ключ скомпрометирован — обнаружен в логах, случайно попал в git-коммит или был раскрыт в результате инцидента — ротируйте немедленно и считайте все подписи, созданные скомпрометированным ключом, недоверенными. Повторно верифицируйте кешированные результаты верификации и уведомьте потребителей ключа об изменении.

Использование bytearray и memoryview с hmac.new()

Функция hmac.new() принимает любой байтоподобный объект в качестве параметров key и msg. Это означает, что можно передавать bytes, bytearray или memoryview напрямую, без копирования или преобразования. Это особенно важно в двух сценариях: реализации сетевых протоколов, где socket.recv_into() записывает данные в предварительно выделенный буфер bytearray, и высокопроизводительных системах, где отказ от промежуточных копий снижает нагрузку на сборщик мусора. Срез memoryview не требует копирования: он открывает окно в исходный буфер без выделения новой памяти. При десятках тысяч сообщений в секунду отказ от таких выделений даёт ощутимое улучшение задержки и пропускной способности.

Python 3.7+ — bytearray и memoryview с HMAC
import hmac
import hashlib

# bytearray — изменяемые байты, удобны для буферов двоичных протоколов
key = bytearray(b"protocol_signing_key")
frame = bytearray(b'\x01\x02\x03\x04payload_data_here')

sig = hmac.new(key, frame, hashlib.sha256).hexdigest()
print(f"Frame signature: {sig[:32]}...")

# memoryview — срез без копирования из большого буфера
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"

# HMAC только первых 20 байт без копирования
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")

Распространённые ошибки

Первые две ошибки встречаются практически в каждом code review, связанном с обработчиками вебхуков. Их легко допустить в спешке, и сложно заметить без понимания того, что именно нужно искать.

Сравнение HMAC-подписей через == вместо hmac.compare_digest()

Проблема: Оператор == прерывается на первом несовпадающем байте, раскрывая информацию о времени, что позволяет злоумышленнику восстановить ожидаемую подпись инкрементально.

Решение: Всегда используйте hmac.compare_digest() для сравнения подписей — функция выполняется за константное время независимо от места несовпадения.

Before · Python
After · Python
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if received_sig == expected_sig:  # УЯЗВИМО к атаке по времени
    process_webhook(body)
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if hmac.compare_digest(received_sig, expected_sig):  # константное время
    process_webhook(body)
Передача строки вместо байтов в hmac.new()

Проблема: hmac.new() требует байтоподобные объекты. Передача Python str вызывает TypeError: "key: expected bytes or bytearray, but got 'str'".

Решение: Вызовите .encode() для строковых ключей и сообщений перед передачей в hmac.new().

Before · Python
After · Python
key = "my_api_secret"  # str, не bytes
msg = '{"event":"test"}'  # str, не bytes
sig = hmac.new(key, msg, hashlib.sha256)  # TypeError!
key = "my_api_secret"
msg = '{"event":"test"}'
sig = hmac.new(key.encode(), msg.encode(), hashlib.sha256)
Забытый digestmod (Python 3.4+)

Проблема: Вызов hmac.new(key, msg) без третьего аргумента вызывает TypeError. До Python 3.4 по умолчанию использовался MD5, но значение по умолчанию было убрано по соображениям безопасности.

Решение: Всегда явно указывайте алгоритм: hashlib.sha256, hashlib.sha512 или тот, что требует ваш протокол.

Before · Python
After · Python
# Отсутствует digestmod — вызывает TypeError в Python 3.4+
sig = hmac.new(key, msg).hexdigest()
# Всегда указывайте алгоритм хеширования явно
sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
Использование .hexdigest() когда провайдер ожидает Base64

Проблема: Многие провайдеры вебхуков (Stripe, Shopify) отправляют подписи в кодировке Base64, а не hex. Сравнение hex-строки с Base64-значением всегда завершается неудачей, в результате все вебхуки отклоняются.

Решение: Проверьте документацию провайдера для выяснения формата подписи. Если используется Base64, кодируйте сырые байты .digest(), а не строку .hexdigest().

Before · Python
After · Python
# Провайдер отправляет Base64, а мы вычисляем hex — никогда не совпадёт
expected = hmac.new(key, body, hashlib.sha256).hexdigest()
# "a3f1b9c0..."  vs  "o/G5wE59..."  — всегда несовпадение
import base64
# Соответствуем формату провайдера: сырые байты → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..."  — совпадает с заголовком

stdlib hmac vs cryptography — краткое сравнение

Метод
Алгоритм
Вывод
Потоковый
Произвольные типы
Требует установки
hmac.new() + hexdigest()
Любой hashlib
Hex-строка
✓ через .update()
N/A
Нет (stdlib)
hmac.new() + digest()
Любой hashlib
Сырые байты
✓ через .update()
N/A
Нет (stdlib)
hmac.digest()
Любой hashlib
Сырые байты
✗ (одиночный вызов)
N/A
Нет (stdlib, 3.7+)
hashlib.sha256 (обычный хеш)
Только SHA-256
Hex или байты
✓ через .update()
N/A
Нет (stdlib)
cryptography (HMAC)
Любой
Сырые байты
✓ через .update()
N/A
pip install
pyca/cryptography + CMAC
AES-CMAC
Сырые байты
✓ через .update()
N/A
pip install

Используйте модуль hmac из stdlib для верификации вебхуков, подписи API-запросов и общих HMAC-операций — он не требует зависимостей и поддерживает все стандартные алгоритмы. Используйте hmac.digest() для пакетных операций, где важна скорость одиночного вызова. Обращайтесь к библиотеке cryptography только если уже зависите от неё в других операциях (TLS, X.509, симметричное шифрование) и хотите API на основе исключений через .verify(). Для быстрой проверки подписи без написания Python-кода воспользуйтесь инструментом HMAC Generator — вставьте ключ и сообщение и получите результат мгновенно.

Часто задаваемые вопросы

В чём разница между hmac.new() и hmac.digest() в Python?

hmac.new() возвращает объект HMAC, поддерживающий инкрементальные вызовы .update(), и предоставляет как .digest() (сырые байты), так и .hexdigest() (hex-строку). hmac.digest() — это одиночная функция, добавленная в Python 3.7, которая возвращает сырые байты напрямую без создания промежуточного объекта. Используйте hmac.digest(), когда у вас есть полное сообщение и нужен только результат. Используйте hmac.new(), когда данные поступают частями или нужен hex-вывод.

Python
import hmac, hashlib

key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'

# Одиночный вызов — возвращает сырые байты
raw = hmac.digest(key, body, hashlib.sha256)

# Объектный подход — поддерживает инкрементальные обновления и hex-вывод
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()

Как верифицировать HMAC-подпись в Python?

Пересчитайте HMAC от исходного сообщения с использованием общего секрета, затем сравните с помощью hmac.compare_digest(). Никогда не используйте == для сравнения подписей. Оператор == уязвим к атакам по времени: он прерывается на первом несовпадающем байте, раскрывая информацию о длине и содержимом ожидаемой подписи.

Python
import hmac, hashlib

def verify_signature(secret: bytes, message: bytes, received_sig: str) -> bool:
    expected = hmac.new(secret, message, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, received_sig)

Одинаковы ли Python hmac SHA-256 и хеширование через hashlib.sha256?

Нет. hashlib.sha256 вычисляет обычный SHA-256 хеш входных данных, который может воспроизвести любой желающий. HMAC-SHA256 примешивает секретный ключ в вычисление хеша согласно RFC 2104, поэтому только владелец ключа может создать или проверить корректный вывод. Обычный хеш подтверждает целостность данных. HMAC подтверждает и целостность, и подлинность.

Python
import hmac, hashlib

msg = b"transfer:9950:USD"
key = b"api_secret_k8x2"

plain_hash = hashlib.sha256(msg).hexdigest()  # может вычислить любой
hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest()  # требует знания ключа

Можно ли использовать HMAC-SHA1 в Python 3?

Да, передайте hashlib.sha1 в качестве аргумента digestmod. HMAC-SHA1 по-прежнему работает в Python 3, и модуль hmac не выдаёт предупреждений об устаревании для него. Тем не менее SHA-1 считается слабым для новых разработок — его устойчивость к коллизиям ниже 80 бит, и NIST запретил его для большинства применений цифровой подписи в 2015 году. Основная причина использовать HMAC-SHA1 сегодня — обратная совместимость с существующими протоколами, требующими его, например OAuth 1.0a или некоторые устаревшие системы вебхуков. Когда вы управляете обеими сторонами интеграции, предпочитайте HMAC-SHA256 или HMAC-SHA512 для всех новых разработок.

Python
import hmac, hashlib

key = b"oauth_consumer_secret"
base_string = b"GET&https%3A%2F%2Fapi.example.com&oauth_nonce%3Dabc123"
sig = hmac.new(key, base_string, hashlib.sha1).digest()

Как сгенерировать безопасный HMAC-ключ в Python?

Используйте secrets.token_bytes() из стандартной библиотеки. Для HMAC-SHA256 рекомендуется ключ длиной 32 байта, так как он соответствует размеру блока хеш-функции. Для HMAC-SHA512 используйте 64 байта. Не используйте random.random() или os.urandom() для генерации ключей в коде приложения — модуль secrets является правильным выбором для криптографически стойкой случайности начиная с Python 3.6.

Python
import secrets

hmac_sha256_key = secrets.token_bytes(32)  # 256 бит
hmac_sha512_key = secrets.token_bytes(64)  # 512 бит

# Сохранить как hex для конфигурационных файлов
print(hmac_sha256_key.hex())
# например "a3f1b9c04e..."

Почему hmac.new() требует digestmod в Python 3?

До Python 3.4 digestmod по умолчанию равнялся MD5, который криптографически взломан — MD5 имеет известные атаки на коллизии и никогда не должен использоваться в новом чувствительном к безопасности коде. Разработчики Python убрали значение по умолчанию, чтобы вынудить явно выбирать алгоритм, не допуская случайного использования MD5-основанных MAC. При вызове hmac.new(key, msg) без digestmod возникает TypeError. Всегда явно указывайте алгоритм: hashlib.sha256, hashlib.sha512 или любой другой конструктор hashlib. Если не уверены — hashlib.sha256 является безопасным выбором по умолчанию: не имеет известных уязвимостей и достаточно быстр для любой практической нагрузки.

Python
import hmac, hashlib

key = b"secret"
msg = b"data"

# Это вызывает TypeError в Python 3.4+
# hmac.new(key, msg)  # TypeError: missing required argument: 'digestmod'

# Всегда явно указывайте алгоритм
h = hmac.new(key, msg, hashlib.sha256)

Для быстрой проверки HMAC без запуска Python-скрипта вставьте ключ и сообщение в онлайн-генератор HMAC — он поддерживает SHA-256, SHA-384 и SHA-512 с мгновенным результатом.

Связанные инструменты

DV
Dmitri VolkovDevOps Engineer & Python Automation Specialist

Dmitri is a DevOps engineer who relies on Python as his primary scripting and automation language. He builds internal tooling, CI/CD pipelines, and infrastructure automation scripts that run in production across distributed teams. He writes about the Python standard library, subprocess management, file processing, encoding utilities, and the practical shell-adjacent Python that DevOps engineers use every day.

MS
Maria SantosТехнический рецензент

Maria is a backend developer specialising in Python and API integration. She has broad experience with data pipelines, serialisation formats, and building reliable server-side services. She is an active member of the Python community and enjoys writing practical, example-driven guides that help developers solve real problems without unnecessary theory.