HMAC w Pythonie — hmac.new() SHA-256 przewodnik + przykłady
Użyj darmowego Generator HMAC bezpośrednio w przeglądarce — bez instalacji.
Wypróbuj Generator HMAC online →Każde wywołanie zwrotne webhooka, każde podpisane żądanie API, każde zdarzenie Stripe lub GitHub używa podpisu HMAC, aby udowodnić, że ładunek nie został zmodyfikowany. Moduł hmac Pythona obsługuje HMAC-SHA256 w Pythonie jednym wywołaniem funkcji: hmac.new(key, msg, hashlib.sha256). Żadnego pip install, żadnego rozszerzenia C, żadnej zależności zewnętrznej. Do szybkich jednorazowych sprawdzeń podpisów bez pisania kodu, internetowy Generator HMAC daje wynik natychmiast. Ten przewodnik omawia hmac.new(), hmac.digest(), hmac.compare_digest(), kodowanie Base64, weryfikację webhooków, podpisywanie żądań API oraz każdy algorytm skrótu od SHA-1 do SHA-512. Wszystkie przykłady dotyczą Pythona 3.7+.
- ✓hmac.new(key, msg, hashlib.sha256) to standardowy punkt wejścia — key i msg muszą być bajtami, digestmod jest obowiązkowy od Pythona 3.4.
- ✓hmac.digest(key, msg, "sha256") to szybsza jednorazowa alternatywa dodana w Pythonie 3.7 — zwraca surowe bajty bez obiektu pośredniego.
- ✓Zawsze weryfikuj podpisy za pomocą hmac.compare_digest(), aby zapobiec atakom czasowym — nigdy nie używaj == do porównywania HMAC.
- ✓Koduj surowe bajty .digest() do Base64 dla nagłówków HTTP i podpisów webhooków: base64.b64encode(h.digest()).
- ✓Moduł hmac akceptuje każdy algorytm hashlib: sha1, sha256, sha384, sha512, md5, blake2b.
Czym jest HMAC?
HMAC (Hash-based Message Authentication Code) to konstrukcja zdefiniowana w RFC 2104 łącząca tajny klucz z funkcją skrótu, aby wygenerować znacznik uwierzytelniający o stałym rozmiarze. W przeciwieństwie do zwykłego skrótu (który każdy może obliczyć), HMAC wymaga znajomości tajnego klucza. Oznacza to, że można go używać do weryfikacji zarówno integralności, jak i autentyczności wiadomości. Nawet zmiana jednego bajtu wiadomości lub klucza całkowicie zmienia wynik. Konstrukcja działa poprzez haszowanie klucza poddanego operacji XOR z dwiema różnymi stałymi dopełnienia (ipad i opad), owijając wiadomość między dwiema operacjami skrótu. Moduł hmac Pythona implementuje to RFC bezpośrednio.
# Zwykły skrót SHA-256 — bez tajnego klucza, każdy może obliczyć hashlib.sha256(b"payment:9950:USD").hexdigest() # "7a3b1c..." (deterministyczny, publiczny)
# HMAC-SHA256 — wymaga tajnego klucza do wygenerowania hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest() # "e4f2a8..." (tylko posiadacz klucza może obliczyć)
hmac.new() — Standardowy punkt wejścia biblioteki
Moduł hmac jest częścią biblioteki standardowej Pythona. Dwa importy i jesteś gotowy: import hmac, hashlib. Trzy główne funkcje to hmac.new() (tworzy obiekt HMAC), hmac.digest() (jednorazowe, Python 3.7+) i hmac.compare_digest() (porównanie w stałym czasie). Żadnego pip install nie potrzeba.
hmac.new(key, msg, digestmod) przyjmuje trzy argumenty. Zarówno key, jak i msg muszą być obiektami bajtopodobnymi ( bytes, bytearray lub memoryview). Argument digestmod jest obowiązkowy od Pythona 3.4 i akceptuje dowolny konstruktor hashlib (jak hashlib.sha256) lub ciąg nazwy jak "sha256".
import hmac
import hashlib
key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'
# Utwórz obiekt HMAC i pobierz podpis hex
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"Obiekt HMAC udostępnia dwie metody wyjściowe. .digest() zwraca surowe bajty (32 bajty dla SHA-256, 64 dla SHA-512). .hexdigest() zwraca ciąg hex zapisany małymi literami. Ciąg hex to zwykły typ str w Pythonie — żaden krok dekodowania nie jest potrzebny.
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 # Reprezentują te same dane — hex to po prostu kodowanie bajtów jako ciąg assert raw_bytes.hex() == hex_string
Jeśli klucz lub wiadomość to ciąg znaków w Pythonie, wywołaj .encode(), aby przekonwertować go na bajty przed przekazaniem do hmac.new(). To potknięcie dotyka niemal każdego za pierwszym razem — ciągi Pythona 3 to Unicode, nie bajty, a moduł hmac ich nie akceptuje.
import hmac
import hashlib
# Klucz i wiadomość jako ciąg — .encode() konwertuje do bajtów 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..." — spójne wyjście hexdigestmod nie ma wartości domyślnej od Pythona 3.4. Wywołanie hmac.new(key, msg) bez niego zgłasza TypeError. Przed wersją 3.4 domyślnie używał MD5, dlatego twórcy Pythona usunęli tę wartość domyślną — wymuszając jawny, bezpieczny wybór.HMAC-SHA256 Base64, SHA-1, SHA-512 i MD5
Funkcja hmac.new() działa z każdym algorytmem skrótu dostępnym w hashlib. Większość dostawców webhooków i bramek API używa HMAC-SHA256, ale spotkasz SHA-1 w OAuth 1.0a, SHA-512 w protokołach, które tego wymagają, oraz MD5 w starszych systemach, które nie zostały zaktualizowane.
HMAC-SHA256 z wyjściem Base64
Wielu dostawców webhooków wysyła podpis jako ciąg zakodowany w Base64 w nagłówku HTTP. Aby uzyskać ten sam format, przekaż surowe bajty .digest() do base64.b64encode().
import hmac
import hashlib
import base64
key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'
# Surowy skrót → Base64 (typowe dla nagłówków Authorization i podpisów webhooków)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")
print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="
# Ta wartość jest porównywana z nagłówkiem X-Signature
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="HMAC-SHA1 — zgodność ze starszymi protokołami
SHA-1 jest uważany za słaby dla nowych rozwiązań, ale HMAC-SHA1 jest nadal wymagany przez OAuth 1.0a i niektóre starsze implementacje webhooków. Kod jest identyczny — wystarczy zamienić algorytm.
import hmac
import hashlib
consumer_secret = b"oauth_consumer_secret_2026"
token_secret = b"oauth_token_secret_2026"
# OAuth 1.0a używa consumer_secret&token_secret jako klucza podpisującego
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..." — zakoduj URL dla nagłówka AuthorizationHMAC-SHA512 — dłuższe wyjście
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 bajty (512 bitów)
print(len(h.hexdigest())) # 128 znaków hex
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."HMAC-MD5 — tylko dla starszych systemów
import hmac import hashlib # MD5 jest kryptograficznie złamany — używaj tylko dla zgodności ze starszymi protokołami 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-znakowy ciąg hex # "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Dokumentacja parametrów hmac.new()
Sygnatura konstruktora to hmac.new(key, msg=None, digestmod). Wszystkie trzy funkcje modułu stosują ten sam wzorzec dla argumentów klucza i algorytmu.
konstruktor hmac.new()
hmac.digest() jednorazowe (Python 3.7+)
Parametr digestmod akceptuje zarówno callable (jak hashlib.sha256), jak i ciąg nazwy (jak "sha256"). Forma callable jest preferowana, ponieważ jest sprawdzana w czasie importu — literówka w ciągu tekstowym jest wykrywana dopiero podczas wykonania.
hmac.digest() — szybkie jednorazowe HMAC (Python 3.7+)
Python 3.7 dodał hmac.digest(key, msg, digest) jako funkcję na poziomie modułu. Oblicza HMAC w jednym wywołaniu bez tworzenia pośredniego obiektu HMAC. Zwracana wartość to surowe bajty (równoważne wywołaniu .digest() na obiekcie). Funkcja ta używa zoptymalizowanej implementacji C w CPython i unika narzutu tworzenia obiektu, co czyni ją mierzalnie szybszą w sędziwych pętlach.
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}',
]
# Jednorazowy skrót — bez pośredniego obiektu HMAC
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]
# Konwertuj do hex do wyświetlenia
for msg, sig in zip(messages, signatures):
print(f"{msg[:30]}... -> {sig.hex()[:24]}...")Ograniczenie: hmac.digest() zwraca tylko surowe bajty. Jeśli potrzebujesz ciągu hex bezpośrednio, nadal potrzebujesz hmac.new() z metodą .hexdigest(), lub dołącz .hex() do wyniku bajtowego.
hmac.digest() nie obsługuje przyrostowych wywołań .update(). Jeśli odczytujesz duży plik fragmentami i potrzebujesz obliczyć HMAC jego zawartości, użyj hmac.new() i wywołuj .update(chunk) w pętli.Weryfikacja podpisu HMAC z webhooka i odpowiedzi API
Najczęstszym zastosowaniem HMAC w Pythonie jest weryfikacja podpisów webhooków. Każdy główny dostawca (Stripe, GitHub, Shopify, Twilio) podpisuje ładunki z HMAC-SHA256 i wysyła podpis w nagłówku. Wzorzec jest zawsze taki sam: oblicz ponownie HMAC dla surowego ciała żądania, a następnie porównaj za pomocą hmac.compare_digest().
Weryfikacja podpisu webhooka
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():
# Pobierz surowe ciało — musi dokładnie odpowiadać temu, co zostało podpisane
raw_body = request.get_data()
# Pobierz podpis z nagłówka
received_sig = request.headers.get("X-Signature-256", "")
# Oblicz ponownie HMAC dla surowego ciała
expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()
# Porównanie w stałym czasie — zapobiega atakom czasowym
if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
abort(403, "Invalid signature")
# Podpis zweryfikowany — przetwórz zdarzenie
event = request.get_json()
print(f"Verified event: {event['type']} for {event['data']['amount']}")
return "", 200Funkcja hmac.compare_digest() porównuje dwa ciągi lub sekwencje bajtów w stałym czasie. Zwykłe porównanie == przerywa działanie przy pierwszym niezgodnym bajcie. Atakujący może mierzyć czas odpowiedzi na wiele żądań i stopniowo odtworzyć oczekiwany podpis bajt po bajcie. Porównanie w stałym czasie eliminuje ten kanał boczny.
Weryfikacja webhooka GitHub
Format webhooka GitHub ilustruje pełny wzorzec. Wysyła nagłówek X-Hub-Signature-256 zawierający sha256= po którym następuje hex-zakodowany HMAC-SHA256 surowego ciała żądania, podpisany sekretem webhooka skonfigurowanym w ustawieniach repozytorium. Kluczowa różnica w stosunku do ogólnej weryfikacji webhooków polega na tym, że musisz odciąć prefiks sha256= przed porównaniem oraz odczytać surowe bajty ciała żądania — wcześniejsze parsowanie JSON zmienia reprezentację bajtową i psuje weryfikację.
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 wysyła: 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() # surowe bajty — nie parsuj JSON przed tym
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 "", 200Ten sam wzorzec obowiązuje dla Shopify (X-Shopify-Hmac-SHA256) i Twilio (X-Twilio-Signature), z jedyną różnicą w nazwie nagłówka i tym, czy podpis jest zakodowany w hex czy Base64. Zawsze sprawdzaj dokumentację dostawcy, aby potwierdzić format kodowania — pomylenie hex i Base64 to najczęstsza przyczyna błędów niezgodności podpisów.
Uwierzytelnianie żądań API z HMAC
Niektóre API wymagają od klienta podpisywania każdego żądania za pomocą HMAC. Podpisywany ciąg zazwyczaj zawiera metodę HTTP, ścieżkę, znacznik czasu i ciało żądania. Poniżej wzorzec, który stosuję do wewnętrznego uwierzytelniania serwis-do-serwisu.
import hmac
import hashlib
import time
import json
def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
"""Generuj podpis HMAC-SHA256 dla żądania API."""
timestamp = str(int(time.time()))
# Zbuduj ciąg podpisujący — metoda + ścieżka + znacznik czasu + ciało
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,
}
# Użycie
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..."}Podpisywanie żądań HTTP z biblioteką 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=(",", ":")) # zwarty JSON, deterministyczny
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
# Wyślij podpisane żądanie 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())Ważna uwaga: używaj separators=(",", ":") przy serializacji ciała do podpisania. Domyślne json.dumps() dodaje spacje po separatorach, co zmienia reprezentację bajtową i psuje weryfikację podpisu, jeśli serwer serializuje inaczej. Zwarty JSON daje kanoniczną postać.
Generowanie HMAC z wiersza poleceń
Czasem trzeba obliczyć HMAC bez pisania skryptu. Flaga -c Pythona i openssl obsługują to z terminala.
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # wypisuje: 64-znakowy ciąg hex
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret" # SHA2-256(stdin)= 7d11... # Przekaż plik przez HMAC openssl openssl dgst -sha256 -hmac "my_secret" < payload.json
# Przechowuj klucz w zmiennej środowiskowej, aby uniknąć jego zapisu w historii powłoki
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 jest kluczowa — bez niej echo dołącza znak nowej linii do wiadomości, co zmienia wynik HMAC. To najczęstsza przyczyna niezgodności podpisów podczas debugowania z terminala.Wydajna alternatywa — biblioteka cryptography
Dla większości zastosowań standardowy moduł hmac jest wystarczająco szybki. Jeśli już używasz biblioteki cryptography do TLS lub obsługi certyfikatów, ona również dostarcza HMAC oparty na OpenSSL. Główna praktyczna różnica od stdlib to oparte na wyjątkach API .verify() opisane poniżej — zgłasza wyjątek przy niezgodności zamiast zwracać wartość logiczną, którą można omyłkowo zignorować.
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() # surowe bajty
print(signature.hex())
# "9c4e2a..."
# Tryb weryfikacji — zgłasza InvalidSignature przy niezgodności
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature) # zgłasza cryptography.exceptions.InvalidSignature przy błędzieMetoda .verify() biblioteki cryptography jest szczególnie wygodna: zgłasza wyjątek przy niezgodności zamiast zwracać wartość logiczną. Utrudnia to przypadkowe zignorowanie błędu weryfikacji. Funkcja hmac.compare_digest() z biblioteki standardowej zwraca True/ False, co może być cicho zignorowane, jeśli programista zapomni sprawdzić wartość zwrotną.
Kolorowe wyjście w terminalu
Jeśli debugujesz podpisy HMAC w terminalu i chcesz kolorowego wyjścia, biblioteka rich sprawdza się tutaj dobrze.
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)Praca z dużymi plikami — przyrostowy HMAC
Dla plików powyżej 50 MB wczytywanie wszystkiego do pamięci tylko po to, aby obliczyć HMAC, jest marnotrawstwem. Metoda .update() obiektu HMAC pozwala przekazywać dane fragmentami. Zużycie pamięci pozostaje stałe niezależnie od rozmiaru pliku.
import hmac
import hashlib
def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
"""Oblicz HMAC-SHA256 pliku bez wczytywania go w całości do pamięci."""
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()
# Podpisz eksport bazy danych o rozmiarze 2 GB
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:
"""Weryfikuj podpis HMAC-SHA256 pliku."""
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)
# Zweryfikuj pobrany artefakt
is_valid = verify_file_hmac(
key=b"release_signing_key",
filepath="/tmp/release-v3.2.0.tar.gz",
expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"Integralność pliku: {'prawidłowa' if is_valid else 'USZKODZONA'}").update() zużywa stałą ilość pamięci chunk_size niezależnie od rozmiaru pliku. Domyślnie stosuję fragmenty 64 KB — wystarczająco duże, by zamortyzować narzut wywołań systemowych, wystarczająco małe, by mieścić się w pamięci podręcznej L2 większości sprzętu.Generowanie kryptograficznie bezpiecznych kluczy HMAC w Pythonie
Słaby lub przewidywalny klucz niweluje całą konstrukcję HMAC. Moduł secrets (Python 3.6+) dostarcza kryptograficznie silnych losowych bajtów. Dla HMAC-SHA256 używaj klucza 32-bajtowego. Dla HMAC-SHA512 używaj 64 bajtów. Odpowiadają one wewnętrznemu rozmiarowi bloku odpowiednich algorytmów skrótu, co stanowi optymalną długość klucza zgodnie z RFC 2104.
import secrets
# Generuj klucze odpowiadające rozmiarowi bloku algorytmu skrótu
sha256_key = secrets.token_bytes(32) # 256 bitów — dla HMAC-SHA256
sha512_key = secrets.token_bytes(64) # 512 bitów — dla HMAC-SHA512
# Reprezentacja hex — bezpieczna dla plików konfiguracyjnych i zmiennych środowiskowych
print(f"Klucz HMAC-SHA256: {sha256_key.hex()}")
# np. "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"
print(f"Klucz HMAC-SHA512: {sha512_key.hex()}")
# 128-znakowy ciąg hex
# URL-safe Base64 — zwarty, bezpieczny dla nagłówków HTTP
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Klucz Base64: {b64_key}")
# np. "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="random.random() ani random.randbytes() z modułu random do kluczy HMAC. Domyślny moduł random używa generatora PRNG Mersenne Twister, który jest przewidywalny po obserwacji 624 wyjść. Zawsze używaj secrets.token_bytes() dla losowości wrażliwej na bezpieczeństwo.Długość klucza i wymagania RFC 2104
RFC 2104 określa, że klucz HMAC może mieć dowolną długość, ale zaleca klucz o długości co najmniej L bajtów — gdzie L to długość wyjściowa bazowej funkcji skrótu. Dla HMAC-SHA256 wynosi to 32 bajty (256 bitów). Klucze krótsze niż L bitów proporcjonalnie zmniejszają margines bezpieczeństwa. Klucze dłuższe niż rozmiar bloku skrótu (64 bajty dla SHA-256, 128 bajtów dla SHA-512) są najpierw skracane przez haszowanie do rozmiaru bloku, więc stosowanie kluczy dłuższych niż rozmiar bloku nie przynosi korzyści. Trzymaj się 32 bajtów dla HMAC-SHA256 i 64 bajtów dla HMAC-SHA512.
Bezpieczne przechowywanie i rotacja kluczy
Nigdy nie umieszczaj kluczy HMAC na stałe w kodzie źródłowym. Standardowym podejściem dla wdrożeń produkcyjnych jest wczytywanie klucza ze zmiennej środowiskowej przy starcie: os.environ["HMAC_SECRET"].encode(). Dla środowisk wymagających wyższego poziomu bezpieczeństwa przechowuj klucze w systemie zarządzania sekretami, takim jak AWS Secrets Manager, HashiCorp Vault lub GCP Secret Manager, i pobieraj je w czasie wykonania. Systemy te zapewniają dzienniki audytu, kontrolę dostępu i automatyczną rotację bez konieczności wdrażania kodu.
Planuj rotację kluczy od początku. Gdy klucz jest rotowany, istnieje okno, w którym żądania w locie były podpisane starym kluczem i nie przejdą weryfikacji z nowym. Standardowym rozwiązaniem jest krótki okres nakładania się: akceptuj podpisy zarówno ze starego, jak i nowego klucza przez krótki czas (minuty do godzin), a następnie wycofaj stary klucz. Jeśli klucz został skompromitowany — ujawniony w logach, wycieknięty przez commit git lub ujawniony w incydencie — przeprowadź rotację natychmiast i traktuj wszystkie podpisy wygenerowane za pomocą skompromitowanego klucza jako niezaufane. Zweryfikuj ponownie wszystkie buforowane wyniki weryfikacji i powiadom odbiorców o zmianie klucza.
Używanie bytearray i memoryview z hmac.new()
Funkcja hmac.new() akceptuje dowolny obiekt bajtopodobny zarówno dla parametru key, jak i msg. Oznacza to, że możesz przekazać bytes, bytearray lub memoryview bezpośrednio, bez kopiowania ani konwersji. Ma to znaczenie przede wszystkim w dwóch scenariuszach: implementacjach protokołów sieciowych, gdzie socket.recv_into() zapisuje dane do wstępnie zaalokowanego bufora bytearray, oraz w systemach wysokiej przepustowości, gdzie unikanie pośrednich kopii zmniejsza presję na garbage collector. Wycinek memoryview jest zero-copy: udostępnia okno do oryginalnego bufora bez alokacji nowej pamięci. Przy dziesiątkach tysięcy wiadomości na sekundę eliminacja tych alokacji ma mierzalny wpływ na opóźnienie i przepustowość.
import hmac
import hashlib
# bytearray — zmienne bajty, przydatne dla buforów protokołów binarnych
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 — zero-copy wycinek większego bufora
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"
# HMAC tylko pierwszych 20 bajtów bez kopiowania
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")Częste błędy
Pierwsze dwa błędy widzę niemal w każdym przeglądzie kodu dotyczącym obsługi webhooków. Łatwo je popełnić pod presją czasu i trudno dostrzec bez wiedzy, na co zwracać uwagę.
Problem: Operator == przerywa działanie przy pierwszym niezgodnym bajcie, ujawniając informacje o czasie, które pozwalają atakującemu stopniowo odtworzyć oczekiwany podpis.
Rozwiązanie: Zawsze używaj hmac.compare_digest() do porównywania podpisów — działa w stałym czasie niezależnie od miejsca niezgodności.
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if received_sig == expected_sig: # PODATNE na atak czasowy
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): # stały czas
process_webhook(body)Problem: hmac.new() wymaga obiektów bajtopodobnych. Przekazanie ciągu str zgłasza TypeError: "key: expected bytes or bytearray, but got 'str'".
Rozwiązanie: Wywołaj .encode() na kluczu i wiadomości jako ciągach przed przekazaniem ich do hmac.new().
key = "my_api_secret" # str, nie bytes
msg = '{"event":"test"}' # str, nie 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)Problem: Wywołanie hmac.new(key, msg) bez trzeciego argumentu zgłasza TypeError. Przed Pythonem 3.4 domyślnie używał MD5, ale wartość domyślna została usunięta ze względów bezpieczeństwa.
Rozwiązanie: Zawsze podawaj algorytm jawnie: hashlib.sha256, hashlib.sha512 lub cokolwiek wymaga twój protokół.
# Brakujący digestmod — zgłasza TypeError w Pythonie 3.4+ sig = hmac.new(key, msg).hexdigest()
# Zawsze podawaj algorytm skrótu sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
Problem: Wielu dostawców webhooków (Stripe, Shopify) wysyła podpisy zakodowane w Base64, nie w hex. Porównanie ciągu hex z wartością Base64 zawsze się nie powiedzie, powodując odrzucenie wszystkich webhooków.
Rozwiązanie: Sprawdź dokumentację dostawcy pod kątem formatu podpisu. Jeśli używa Base64, zakoduj surowe bajty .digest(), nie ciąg .hexdigest().
# Dostawca wysyła Base64, ale obliczamy hex — nigdy się nie zgadza expected = hmac.new(key, body, hashlib.sha256).hexdigest() # "a3f1b9c0..." vs "o/G5wE59..." — zawsze niezgodność
import base64
# Dopasuj format dostawcy: surowe bajty → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..." — pasuje do nagłówkastdlib hmac vs cryptography — szybkie porównanie
Używaj modułu hmac z biblioteki standardowej do weryfikacji webhooków, podpisywania API i ogólnych operacji HMAC — nie wymaga żadnych zależności i obsługuje każdy standardowy algorytm. Używaj hmac.digest() do operacji wsadowych, gdzie liczy się szybkość jednorazowa. Sięgaj po bibliotekę cryptography tylko gdy już od niej zależysz dla innych operacji (TLS, X.509, szyfrowanie symetryczne) i chcesz API .verify() opartego na wyjątkach. Do szybkich sprawdzeń podpisów bez pisania kodu w Pythonie, użyj narzędzia Generator HMAC, aby wkleić klucz i wiadomość i natychmiast uzyskać wynik.
Często zadawane pytania
Jaka jest różnica między hmac.new() a hmac.digest() w Pythonie?
hmac.new() zwraca obiekt HMAC obsługujący przyrostowe wywołania .update() i udostępniający zarówno .digest() (surowe bajty), jak i .hexdigest() (ciąg hex). hmac.digest() to jednorazowa funkcja dodana w Pythonie 3.7, która zwraca surowe bajty bezpośrednio, bez tworzenia obiektu pośredniego. Użyj hmac.digest() gdy masz całą wiadomość i potrzebujesz tylko wyniku. Użyj hmac.new() gdy chcesz przekazywać dane fragmentami lub potrzebujesz wyjścia hex.
import hmac, hashlib
key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'
# Jednorazowe — zwraca surowe bajty
raw = hmac.digest(key, body, hashlib.sha256)
# Obiektowe — obsługuje przyrostowe aktualizacje i wyjście hex
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()Jak zweryfikować podpis HMAC w Pythonie?
Oblicz ponownie HMAC dla oryginalnej wiadomości z użyciem wspólnego sekretu, a następnie porównaj wynik za pomocą hmac.compare_digest(). Nigdy nie używaj == do porównywania podpisów. Operator == jest podatny na ataki czasowe, ponieważ przerywa działanie przy pierwszym niezgodnym bajcie, ujawniając informacje o oczekiwanej długości i treści podpisu.
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)Czy HMAC-SHA256 w Pythonie to to samo co haszowanie z hashlib.sha256?
Nie. hashlib.sha256 oblicza zwykły skrót SHA-256 danych wejściowych, który każdy może odtworzyć. HMAC-SHA256 wplata tajny klucz w obliczenia skrótu zgodnie z RFC 2104, więc tylko osoba znająca klucz może wygenerować lub zweryfikować prawidłowy wynik. Zwykły skrót dowodzi integralności danych. HMAC dowodzi zarówno integralności, jak i autentyczności.
import hmac, hashlib msg = b"transfer:9950:USD" key = b"api_secret_k8x2" plain_hash = hashlib.sha256(msg).hexdigest() # każdy może to obliczyć hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest() # wymaga klucza
Czy mogę używać HMAC-SHA1 w Pythonie 3?
Tak, przekaż hashlib.sha1 jako argument digestmod. HMAC-SHA1 nadal działa poprawnie w Pythonie 3, a moduł hmac nie wyświetla żadnych ostrzeżeń o przestarzałości. Niemniej jednak SHA-1 jest uważany za słaby dla nowych rozwiązań — jego odporność na kolizje wynosi poniżej 80 bitów, a NIST wycofał go dla większości zastosowań podpisów cyfrowych w 2015 roku. Głównym powodem stosowania HMAC-SHA1 jest dziś zgodność wsteczna z istniejącymi protokołami, które go wymagają, jak OAuth 1.0a lub pewne starsze systemy webhooków. Gdy kontrolujesz obie strony integracji, preferuj HMAC-SHA256 lub HMAC-SHA512 dla wszystkich nowych projektów.
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()
Jak wygenerować bezpieczny klucz HMAC w Pythonie?
Użyj secrets.token_bytes() z biblioteki standardowej. Dla HMAC-SHA256 standardowym zaleceniem jest klucz 32-bajtowy, ponieważ odpowiada rozmiarowi bloku skrótu. Dla HMAC-SHA512 użyj 64 bajtów. Nie używaj random.random() ani os.urandom() do generowania kluczy w kodzie aplikacji — secrets jest właściwym modułem dla losowości wrażliwej na bezpieczeństwo od Pythona 3.6.
import secrets hmac_sha256_key = secrets.token_bytes(32) # 256 bitów hmac_sha512_key = secrets.token_bytes(64) # 512 bitów # Zapisz jako hex do plików konfiguracyjnych print(hmac_sha256_key.hex()) # np. "a3f1b9c04e..."
Dlaczego hmac.new() wymaga digestmod w Pythonie 3?
Przed Pythonem 3.4 digestmod domyślnie używał MD5, który jest kryptograficznie złamany — MD5 ma znane ataki kolizyjne i nigdy nie powinien być stosowany w nowym kodzie wrażliwym na bezpieczeństwo. Twórcy Pythona usunęli domyślną wartość, aby wymusić jawny wybór algorytmu, zapobiegając cichemu wdrażaniu MAC opartych na MD5. Jeśli wywołasz hmac.new(key, msg) bez digestmod, otrzymasz TypeError. Zawsze podawaj algorytm jawnie: hashlib.sha256, hashlib.sha512 lub inny konstruktor hashlib. W razie wątpliwości hashlib.sha256 to bezpieczny wybór domyślny — bez znanych słabości i wystarczająco szybki dla każdego praktycznego zastosowania.
import hmac, hashlib key = b"secret" msg = b"data" # To zgłasza TypeError w Pythonie 3.4+ # hmac.new(key, msg) # TypeError: missing required argument: 'digestmod' # Zawsze podawaj algorytm h = hmac.new(key, msg, hashlib.sha256)
Do szybkiego sprawdzenia HMAC bez uruchamiania skryptu Python, wklej swój klucz i wiadomość do internetowego Generatora HMAC — obsługuje SHA-256, SHA-384 i SHA-512 z natychmiastowymi wynikami.
Powiązane narzędzia
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.