HMAC Python — hmac.new() SHA-256 посібник + приклади коду

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

Використовуйте безкоштовний 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 безпосередньо.

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() проти .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 приймає або callable (наприклад hashlib.sha256), або рядкову назву (наприклад "sha256"). Форма callable є кращою, оскільки перевіряється під час імпорту — помилка в рядковій формі виявляється лише під час виконання.

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) підписує payload за допомогою 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 демонструє повну схему. Він надсилає заголовок X-Hub-Signature-256, що містить sha256= з подальшим hex-закодованим HMAC-SHA256 сирого тіла запиту, підписаним секретом вебхука, який ви налаштовуєте в налаштуваннях репозиторію. Ключова відмінність від загальної верифікації вебхука полягає в тому, що потрібно видалити префікс sha256= перед порівнянням, і необхідно читати сирі байти тіла запиту — попереднє розбирання JSON змінює байтове представлення і ламає верифікацію.

Python 3.7+ — верифікація GitHub X-Hub-Signature-256
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...

# Передаємо файл через openssl HMAC
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 — це заснований на винятках метод .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 використовує ГПВЧ 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 є операцією без копіювання: він відкриває вікно до оригінального буфера без виділення нової пам'яті. При десятках тисяч повідомлень на секунду усунення цих виділень суттєво впливає на затримку і пропускну здатність.

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]}...")

Поширені помилки

Перші дві помилки трапляються майже в кожному перегляді коду, що стосується обробників вебхуків. Їх легко допустити під тиском часу і важко помітити без знання того, що шукати.

Порівняння підписів 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..."  проти  "o/G5wE59..."  — завжди розходження
import base64
# Відповідаємо формату постачальника: сирі байти → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..."  — збігається з заголовком

stdlib hmac проти cryptography — Швидке порівняння

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

Використовуйте 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-вивід.

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 видалили значення за замовчуванням, щоб змусити явно вибирати алгоритм і запобігти мовчазному використанню MAC на основі MD5. Якщо викликати 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 Generator — він підтримує 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.