HMAC Python — hmac.new() SHA-256 посібник + приклади коду
Використовуйте безкоштовний HMAC Generator прямо в браузері — без встановлення.
Спробувати HMAC Generator онлайн →Кожен виклик вебхука, кожен підписаний API-запит, кожне сповіщення від Stripe або GitHub використовує HMAC-підпис, щоб підтвердити, що payload не було підроблено. Модуль hmac Python обробляє HMAC-SHA256 у Python одним викликом функції: hmac.new(key, msg, hashlib.sha256). Без pip install, без C-розширень, без сторонніх залежностей. Для швидкої перевірки підписів без написання коду скористайтеся онлайн HMAC Generator , який видає результат миттєво. Цей посібник охоплює 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.
- ✓Для HTTP-заголовків та підписів вебхуків кодуйте сирий .digest() у Base64: 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 приймає або callable (наприклад hashlib.sha256), або рядкову назву (наприклад "sha256"). Форма callable є кращою, оскільки перевіряється під час імпорту — помилка в рядковій формі виявляється лише під час виконання.
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) підписує payload за допомогою 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 демонструє повну схему. Він надсилає заголовок 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... # Передаємо файл через openssl HMAC 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 — це заснований на винятках метод .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 використовує ГПВЧ Mersenne Twister, який є передбачуваним після спостереження 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]}...")Поширені помилки
Перші дві помилки трапляються майже в кожному перегляді коду, що стосується обробників вебхуків. Їх легко допустити під тиском часу і важко помітити без знання того, що шукати.
Проблема: Оператор == перериває виконання на першому невідповідному байті, розкриваючи інформацію про час, що дозволяє зловмиснику поступово відновити очікуваний підпис.
Рішення: Завжди використовуйте 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..." проти "o/G5wE59..." — завжди розходження
import base64
# Відповідаємо формату постачальника: сирі байти → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..." — збігається з заголовкомstdlib hmac проти cryptography — Швидке порівняння
Використовуйте stdlib-модуль hmac для верифікації вебхуків, підпису API та загальних операцій HMAC — він не потребує залежностей і охоплює кожен стандартний алгоритм. Використовуйте hmac.digest() для пакетних операцій, де важлива швидкість одноразового виклику. Звертайтеся до бібліотеки cryptography лише тоді, коли ви вже залежите від неї для інших операцій (TLS, X.509, симетричне шифрування) і хочете використовувати заснований на винятках метод .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 видалили значення за замовчуванням, щоб змусити явно вибирати алгоритм і запобігти мовчазному використанню MAC на основі MD5. Якщо викликати 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 Generator — він підтримує 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.