HMAC w Pythonie — hmac.new() SHA-256 przewodnik + przykłady

·DevOps Engineer & Python Automation Specialist·Sprawdzono przezMaria Santos·Opublikowano

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.

Before · Python
After · Python
# 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".

Python 3.7+ — minimalny przykład HMAC-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.

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

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

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

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

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

# 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.

Python 3.7+ — kodowanie ciągów znaków do bajtów
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 hex
Uwaga:Parametr digestmod 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().

Python 3.7+ — kodowanie HMAC-SHA256 do Base64
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.

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 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 Authorization

HMAC-SHA512 — dłuższe wyjście

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 bajty (512 bitów)
print(len(h.hexdigest()))  # 128 znaków hex
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."

HMAC-MD5 — tylko dla starszych systemów

Python 3.7+ — HMAC-MD5
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"
Ostrzeżenie:HMAC-MD5 jest akceptowalny jedynie dla zgodności wstecznej z systemami, których nie można migrować. Dla każdego nowego projektu używaj co najmniej HMAC-SHA256. MD5 ma znane podatności na kolizje, które choć mniej bezpośrednio exploitowalne w trybie HMAC, czynią go słabym wyborem domyślnym.

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()

Parametr
Typ
Domyślnie
Opis
key
bytes | bytearray
(wymagane)
Tajny klucz — musi być typu bytes, nie str
msg
bytes | None
None
Początkowa wiadomość do zahaszowania; więcej danych można dodać przez .update()
digestmod
str | callable
(wymagane)
Algorytm skrótu — np. hashlib.sha256 lub ciąg "sha256"

hmac.digest() jednorazowe (Python 3.7+)

Parametr
Typ
Opis
key
bytes
Tajny klucz
msg
bytes
Wiadomość do uwierzytelnienia
digest
str | callable
Algorytm skrótu — tak samo jak digestmod w hmac.new()

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.

Python 3.7+ — hmac.digest() jednorazowe
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.

Uwaga: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

Python 3.7+ — weryfikacja HMAC webhooka (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():
    # 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 "", 200

Funkcja 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ę.

Python 3.7+ — weryfikacja X-Hub-Signature-256 GitHub
import hmac
import hashlib
from flask import Flask, request, abort

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

@app.route("/webhooks/github", methods=["POST"])
def handle_github_webhook():
    # GitHub 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 "", 200

Ten 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.

Python 3.7+ — podpisywanie żądań API z HMAC-SHA256
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

Python 3.7+ — podpisane żądania HMAC 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.

bash — HMAC-SHA256 przez jednolinijkowiec Python
python3 -c "
import hmac, hashlib
print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest())
"
# wypisuje: 64-znakowy ciąg hex
bash — HMAC-SHA256 przez openssl (do porównania)
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
bash — HMAC ze zmiennej środowiskowej
# 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())
"
Uwaga:Flaga 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ć.

bash — instalacja cryptography
pip install cryptography
Python 3.7+ — HMAC przez bibliotekę 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łędzie

Metoda .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.

bash — instalacja rich
pip install rich
Python 3.7+ — kolorowe wyjście HMAC z 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)
Uwaga:Rich służy wyłącznie do wyświetlania w terminalu. Nie używaj go przy zapisywaniu podpisów HMAC do plików, nagłówków HTTP ani systemów agregacji logów — kody ANSI zepsują dane wyjściowe.

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.

Python 3.7+ — HMAC dużego pliku fragmentami
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}")
Python 3.7+ — weryfikacja podpisu HMAC pobranego pliku
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'}")
Uwaga:Przejdź na fragmentowany HMAC, gdy plik przekracza 50–100 MB lub gdy przetwarzasz przesłane pliki na serwerze internetowym, gdzie liczy się pamięć na żądanie. Podejście z .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.

Python 3.7+ — generowanie kluczy HMAC z secrets
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="
Ostrzeżenie:Nigdy nie używaj 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ść.

Python 3.7+ — bytearray i memoryview z HMAC
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ę.

Porównywanie podpisów HMAC z == zamiast hmac.compare_digest()

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.

Before · Python
After · Python
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)
Przekazywanie ciągu znaków zamiast bajtów do hmac.new()

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().

Before · Python
After · Python
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)
Pominięcie digestmod (Python 3.4+)

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ół.

Before · Python
After · Python
# 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()
Używanie .hexdigest() gdy dostawca oczekuje Base64

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().

Before · Python
After · Python
# 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łówka

stdlib hmac vs cryptography — szybkie porównanie

Metoda
Algorytm
Wynik
Strumieniowanie
Typy niestandardowe
Wymaga instalacji
hmac.new() + hexdigest()
Dowolny hashlib
Ciąg hex
✓ przez .update()
N/D
Nie (stdlib)
hmac.new() + digest()
Dowolny hashlib
Surowe bajty
✓ przez .update()
N/D
Nie (stdlib)
hmac.digest()
Dowolny hashlib
Surowe bajty
✗ (jednorazowe)
N/D
Nie (stdlib, 3.7+)
hashlib.sha256 (zwykły skrót)
Tylko SHA-256
Hex lub bajty
✓ przez .update()
N/D
Nie (stdlib)
cryptography (HMAC)
Dowolny
Surowe bajty
✓ przez .update()
N/D
pip install
pyca/cryptography + CMAC
AES-CMAC
Surowe bajty
✓ przez .update()
N/D
pip install

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.

Python
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.

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)

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.

Python
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.

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()

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.

Python
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.

Python
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

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 SantosRecenzent techniczny

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.