HMAC Python — hmac.new() SHA-256 руководство + примеры кода
Используйте бесплатный 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 напрямую.
# Обычный 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".
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 — шаг декодирования не нужен.
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 их не принимает.
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().
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 и некоторых старых реализациях вебхуков. Код идентичен — нужно лишь заменить алгоритм.
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-кодируйте это для заголовка AuthorizationHMAC-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 — только для устаревших систем
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.new()
Сигнатура конструктора: hmac.new(key, msg=None, digestmod). Все три функции модуля используют одинаковый шаблон для аргументов ключа и алгоритма.
Конструктор hmac.new()
Одиночный вызов hmac.digest() (Python 3.7+)
Параметр digestmod принимает как вызываемый объект (например, hashlib.sha256), так и строковое имя (например, "sha256"). Форма с вызываемым объектом предпочтительна, так как проверяется при импорте — опечатка в строковой форме обнаруживается только во время выполнения.
hmac.digest() — быстрый одиночный HMAC (Python 3.7+)
В Python 3.7 была добавлена функция уровня модуля hmac.digest(key, msg, digest). Она вычисляет HMAC за один вызов без создания промежуточного объекта HMAC. Возвращаемое значение — сырые байты (эквивалент вызова .digest() на объекте). Функция использует оптимизированную реализацию на C в CPython и избегает накладных расходов на выделение памяти под объект, что делает её заметно быстрее в интенсивных циклах.
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().
Верификация подписи вебхука
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 меняет байтовое представление и нарушает верификацию.
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, путь, временную метку и тело запроса. Вот шаблон для межсервисной аутентификации.
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
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 позволяют сделать это из терминала.
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # выводит: 64-символьная hex-строка
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
# Храните ключ в переменной окружения, чтобы не показывать в истории оболочки
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(), описанный ниже: он вызывает исключение при несовпадении, а не возвращает логическое значение, которое разработчик может забыть проверить.
pip install 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 отлично справляется с этой задачей.
pip install 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)Работа с большими файлами — инкрементальный HMAC
Для файлов размером более 50 МБ загружать всё содержимое в память только для вычисления HMAC расточительно. Метод .update() объекта 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}")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'}").update() использует фиксированный объём памяти chunk_size независимо от размера файла. Рекомендуется размер порции 64 КБ — достаточно большой, чтобы амортизировать накладные расходы системных вызовов, и достаточно малый, чтобы оставаться в кеше L2 на большинстве процессоров.Генерация криптографически стойкого HMAC-ключа в Python
Слабый или предсказуемый ключ подрывает всю конструкцию HMAC. Модуль secrets (Python 3.6+) предоставляет криптографически стойкие случайные байты. Для HMAC-SHA256 используйте ключ длиной 32 байта. Для HMAC-SHA512 — 64 байта. Это соответствует внутреннему размеру блока соответствующих хеш-алгоритмов, что является оптимальной длиной ключа согласно RFC 2104.
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 не требует копирования: он открывает окно в исходный буфер без выделения новой памяти. При десятках тысяч сообщений в секунду отказ от таких выделений даёт ощутимое улучшение задержки и пропускной способности.
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.compare_digest() для сравнения подписей — функция выполняется за константное время независимо от места несовпадения.
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() требует байтоподобные объекты. Передача Python str вызывает TypeError: "key: expected bytes or bytearray, but got 'str'".
Решение: Вызовите .encode() для строковых ключей и сообщений перед передачей в hmac.new().
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)Проблема: Вызов hmac.new(key, msg) без третьего аргумента вызывает TypeError. До Python 3.4 по умолчанию использовался MD5, но значение по умолчанию было убрано по соображениям безопасности.
Решение: Всегда явно указывайте алгоритм: hashlib.sha256, hashlib.sha512 или тот, что требует ваш протокол.
# Отсутствует digestmod — вызывает TypeError в Python 3.4+ sig = hmac.new(key, msg).hexdigest()
# Всегда указывайте алгоритм хеширования явно sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
Проблема: Многие провайдеры вебхуков (Stripe, Shopify) отправляют подписи в кодировке Base64, а не hex. Сравнение hex-строки с Base64-значением всегда завершается неудачей, в результате все вебхуки отклоняются.
Решение: Проверьте документацию провайдера для выяснения формата подписи. Если используется Base64, кодируйте сырые байты .digest(), а не строку .hexdigest().
# Провайдер отправляет 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 из 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-вывод.
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(). Никогда не используйте == для сравнения подписей. Оператор == уязвим к атакам по времени: он прерывается на первом несовпадающем байте, раскрывая информацию о длине и содержимом ожидаемой подписи.
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 подтверждает и целостность, и подлинность.
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 для всех новых разработок.
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.
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 является безопасным выбором по умолчанию: не имеет известных уязвимостей и достаточно быстр для любой практической нагрузки.
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 с мгновенным результатом.
Связанные инструменты
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.
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.