HMAC trong Python — Hướng dẫn hmac.new() SHA-256 + ví dụ

·DevOps Engineer & Python Automation Specialist·Đánh giá bởiMaria Santos·Đã xuất bản

Sử dụng Trình tạo HMAC miễn phí trực tiếp trên trình duyệt — không cần cài đặt.

Dùng thử Trình tạo HMAC trực tuyến →

Mỗi callback webhook, mỗi yêu cầu API được ký, mỗi thông báo sự kiện Stripe hay GitHub đều sử dụng chữ ký HMAC để chứng minh payload không bị giả mạo. Module hmac của Python xử lý HMAC-SHA256 trong Python chỉ với một lần gọi hàm: hmac.new(key, msg, hashlib.sha256). Không cần pip install, không cần C extension, không phụ thuộc bên thứ ba. Để kiểm tra chữ ký nhanh mà không cần viết mã, công cụ HMAC Generator trực tuyến cho kết quả ngay lập tức. Hướng dẫn này đề cập đến hmac.new(), hmac.digest(), hmac.compare_digest(), mã hóa Base64, xác minh webhook, ký yêu cầu API, và mọi thuật toán băm từ SHA-1 đến SHA-512. Tất cả ví dụ dành cho Python 3.7+.

  • hmac.new(key, msg, hashlib.sha256) là điểm khởi đầu tiêu chuẩn — key và msg phải là bytes, digestmod là bắt buộc từ Python 3.4.
  • hmac.digest(key, msg, "sha256") là lựa chọn thay thế một lần nhanh hơn được thêm vào Python 3.7 — trả về bytes thô, không tạo đối tượng trung gian.
  • Luôn xác minh chữ ký bằng hmac.compare_digest() để ngăn chặn tấn công timing — không bao giờ dùng == để so sánh HMAC.
  • Mã hóa Base64 bytes .digest() thô cho HTTP headers và chữ ký webhook: base64.b64encode(h.digest()).
  • Module hmac chấp nhận mọi thuật toán hashlib: sha1, sha256, sha384, sha512, md5, blake2b.

HMAC là gì?

HMAC (Hash-based Message Authentication Code) là một cấu trúc được định nghĩa trong RFC 2104 kết hợp khóa bí mật với một hàm băm để tạo ra thẻ xác thực có kích thước cố định. Không giống giá trị băm thuần túy (bất kỳ ai cũng có thể tính), HMAC yêu cầu phải biết khóa bí mật. Điều này cho phép bạn xác minh cả tính toàn vẹn lẫn tính xác thực của một thông điệp. Nếu dù chỉ một byte của thông điệp hoặc khóa thay đổi, kết quả đầu ra sẽ hoàn toàn khác. Cấu trúc này hoạt động bằng cách băm khóa XOR với hai hằng số đệm khác nhau (ipad và opad), bọc thông điệp giữa hai lần thực hiện băm. Module hmac của Python triển khai RFC này trực tiếp.

Before · Python
After · Python
# Băm SHA-256 thuần túy — không có khóa bí mật, ai cũng tính được
hashlib.sha256(b"payment:9950:USD").hexdigest()
# "7a3b1c..."  (tất định, công khai)
# HMAC-SHA256 — yêu cầu khóa bí mật để tạo ra
hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest()
# "e4f2a8..."  (chỉ người giữ khóa mới tính được)

hmac.new() — Điểm Vào Của Thư Viện Chuẩn

Module hmac là một phần của thư viện chuẩn Python. Chỉ cần hai lệnh import là bạn sẵn sàng: import hmac, hashlib. Ba hàm chính là hmac.new() (tạo đối tượng HMAC), hmac.digest() (một lần, Python 3.7+), và hmac.compare_digest() (so sánh trong thời gian cố định). Không cần pip install.

hmac.new(key, msg, digestmod) nhận ba đối số. Cả key msg phải là đối tượng kiểu bytes ( bytes, bytearray, hoặc memoryview). Đối số digestmod là bắt buộc từ Python 3.4 và chấp nhận bất kỳ hàm khởi tạo hashlib nào (như hashlib.sha256) hoặc tên chuỗi như "sha256".

Python 3.7+ — ví dụ HMAC-SHA256 tối giản
import hmac
import hashlib

key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'

# Tạo đối tượng HMAC và lấy chữ ký hex
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"

Đối tượng HMAC cung cấp hai phương thức đầu ra. .digest() trả về bytes thô (32 byte cho SHA-256, 64 cho SHA-512). .hexdigest() trả về chuỗi hex viết thường. Chuỗi hex là kiểu str Python thông thường — không cần bước giải mã.

Python 3.7+ — .digest() so với .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

# Chúng biểu diễn cùng dữ liệu — hex chỉ là chuỗi mã hóa của bytes
assert raw_bytes.hex() == hex_string

Nếu khóa hoặc thông điệp của bạn là chuỗi Python, hãy gọi .encode() để chuyển đổi sang bytes trước khi truyền vào hmac.new(). Đây là lỗi mà hầu như ai cũng gặp lần đầu — chuỗi Python 3 là Unicode, không phải bytes, và module hmac từ chối chúng.

Python 3.7+ — chuyển đổi chuỗi sang bytes
import hmac
import hashlib

# Khóa và thông điệp dạng chuỗi — .encode() chuyển sang bytes 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..."  — đầu ra chuỗi hex nhất quán
Lưu ý:Tham số digestmod không có giá trị mặc định từ Python 3.4. Gọi hmac.new(key, msg) mà không có nó sẽ gây ra TypeError. Trước 3.4, nó mặc định là MD5, đó là lý do các nhà phát triển Python đã xóa giá trị mặc định — buộc bạn phải lựa chọn rõ ràng và an toàn.

HMAC-SHA256 Base64, SHA-1, SHA-512 và MD5

Hàm hmac.new() hoạt động với bất kỳ thuật toán băm nào có sẵn trong hashlib. Hầu hết nhà cung cấp webhook và cổng API sử dụng HMAC-SHA256, nhưng bạn sẽ gặp SHA-1 trong OAuth 1.0a, SHA-512 trong các giao thức yêu cầu nó, và MD5 trong các hệ thống cũ chưa được cập nhật.

HMAC-SHA256 với Đầu Ra Base64

Nhiều nhà cung cấp webhook gửi chữ ký dưới dạng chuỗi mã hóa Base64 trong HTTP header. Để tạo ra cùng định dạng, truyền bytes .digest() thô vào base64.b64encode().

Python 3.7+ — mã hóa Base64 HMAC-SHA256
import hmac
import hashlib
import base64

key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'

# Digest thô → Base64 (thường dùng cho Authorization headers và chữ ký webhook)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")

print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

# Đây là giá trị bạn sẽ so sánh với header X-Signature
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="

HMAC-SHA1 — Tương Thích Giao Thức Cũ

SHA-1 được coi là yếu cho các thiết kế mới, nhưng HMAC-SHA1 vẫn được yêu cầu bởi OAuth 1.0a và một số triển khai webhook cũ hơn. Mã nguồn hoàn toàn giống nhau — chỉ cần thay thuật toán.

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 dùng consumer_secret&token_secret làm khóa ký
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..."  — mã hóa URL cái này cho Authorization header

HMAC-SHA512 — Đầu Ra Dài Hơn

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 byte (512 bit)
print(len(h.hexdigest()))  # 128 ký tự hex
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."

HMAC-MD5 — Chỉ Dùng Cho Hệ Thống Cũ

Python 3.7+ — HMAC-MD5
import hmac
import hashlib

# MD5 đã bị phá vỡ về mặt mật mã học — chỉ dùng để tương thích giao thức cũ
key = b"legacy_api_key"
msg = b"action=charge&amount=1500&merchant=store_42"

sig = hmac.new(key, msg, hashlib.md5).hexdigest()
print(sig)  # chuỗi hex 32 ký tự
# "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
Cảnh báo:HMAC-MD5 chỉ được chấp nhận để tương thích ngược với các hệ thống bạn không thể di chuyển. Với mọi dự án mới, hãy dùng HMAC-SHA256 tối thiểu. MD5 có các lỗ hổng va chạm đã biết, dù ít bị khai thác trực tiếp hơn trong chế độ HMAC, vẫn là lựa chọn mặc định kém.

Tham Khảo Tham Số hmac.new()

Chữ ký hàm khởi tạo là hmac.new(key, msg=None, digestmod). Tất cả ba hàm trong module đều dùng chung cùng mẫu cho đối số khóa và thuật toán.

Hàm khởi tạo hmac.new()

Tham số
Kiểu
Mặc định
Mô tả
key
bytes | bytearray
(bắt buộc)
Khóa bí mật — phải là bytes, không phải chuỗi
msg
bytes | None
None
Dữ liệu ban đầu cần băm; có thể thêm dữ liệu qua .update()
digestmod
str | callable
(bắt buộc)
Thuật toán băm — ví dụ: hashlib.sha256 hoặc chuỗi "sha256"

hmac.digest() một lần (Python 3.7+)

Tham số
Kiểu
Mô tả
key
bytes
Khóa bí mật
msg
bytes
Dữ liệu cần xác thực
digest
str | callable
Thuật toán băm — giống digestmod trong hmac.new()

Tham số digestmod chấp nhận callable (như hashlib.sha256) hoặc tên chuỗi (như "sha256"). Dạng callable được ưu tiên hơn vì nó được kiểm tra tại thời điểm import — lỗi đánh máy ở dạng chuỗi chỉ gây lỗi khi chạy.

hmac.digest() — HMAC Một Lần Nhanh (Python 3.7+)

Python 3.7 đã thêm hmac.digest(key, msg, digest) như một hàm cấp module. Nó tính HMAC trong một lần gọi mà không tạo đối tượng HMAC trung gian. Giá trị trả về là bytes thô (tương đương với gọi .digest() trên đối tượng). Hàm này sử dụng triển khai C được tối ưu hóa trên CPython và tránh chi phí cấp phát đối tượng, giúp nó nhanh hơn đáng kể trong các vòng lặp sát.

Python 3.7+ — hmac.digest() một lần
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}',
]

# Digest một lần — không tạo đối tượng HMAC trung gian
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]

# Chuyển sang hex để hiển thị
for msg, sig in zip(messages, signatures):
    print(f"{msg[:30]}... -> {sig.hex()[:24]}...")

Hạn chế: hmac.digest() chỉ trả về bytes thô. Nếu bạn cần chuỗi hex trực tiếp, bạn vẫn cần hmac.new() với phương thức .hexdigest() của nó, hoặc nối thêm .hex() vào kết quả bytes.

Lưu ý:hmac.digest() không hỗ trợ các lần gọi .update() liên tiếp. Nếu bạn đang đọc file lớn theo từng phần và cần HMAC nội dung, hãy dùng hmac.new() và gọi .update(chunk) trong vòng lặp.

Xác Minh Chữ Ký HMAC từ Webhook và Phản Hồi API

Cách dùng HMAC phổ biến nhất trong Python là xác minh chữ ký webhook. Mọi nhà cung cấp lớn (Stripe, GitHub, Shopify, Twilio) đều ký payload bằng HMAC-SHA256 và gửi chữ ký trong header. Mẫu luôn giống nhau: tính lại HMAC trên phần thân yêu cầu thô, sau đó so sánh bằng hmac.compare_digest().

Xác Minh Chữ Ký Webhook

Python 3.7+ — xác minh HMAC webhook (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():
    # Lấy phần thân thô — phải khớp chính xác với những gì đã ký
    raw_body = request.get_data()

    # Lấy chữ ký từ header
    received_sig = request.headers.get("X-Signature-256", "")

    # Tính lại HMAC trên phần thân thô
    expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()

    # So sánh trong thời gian cố định — ngăn chặn tấn công timing
    if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
        abort(403, "Invalid signature")

    # Chữ ký đã xác minh — xử lý sự kiện
    event = request.get_json()
    print(f"Verified event: {event['type']} for {event['data']['amount']}")
    return "", 200

Hàm hmac.compare_digest() so sánh hai chuỗi hoặc chuỗi byte trong thời gian cố định. Phép so sánh == thông thường dừng lại ngay tại byte đầu tiên không khớp. Kẻ tấn công có thể đo thời gian phản hồi qua nhiều yêu cầu và dần dần tái tạo chữ ký dự kiến từng byte một. So sánh trong thời gian cố định loại bỏ kênh tấn công phụ này.

Xác Minh Webhook GitHub

Định dạng webhook của GitHub minh họa mẫu đầy đủ. Nó gửi header X-Hub-Signature-256 chứa sha256= theo sau là HMAC-SHA256 mã hóa hex của phần thân yêu cầu thô, được ký bằng bí mật webhook bạn cấu hình trong cài đặt kho lưu trữ. Điểm khác biệt chính so với xác minh webhook thông thường là bạn phải bỏ tiền tố sha256= trước khi so sánh, và phải đọc bytes thô của phần thân yêu cầu — phân tích JSON trước sẽ thay đổi biểu diễn byte và làm hỏng quá trình xác minh.

Python 3.7+ — xác minh X-Hub-Signature-256 của 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 gửi: 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()  # bytes thô — không phân tích JSON trước

    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

Mẫu tương tự áp dụng cho Shopify (X-Shopify-Hmac-SHA256) và Twilio (X-Twilio-Signature), với sự khác biệt duy nhất là tên header và liệu chữ ký có ở dạng hex hay Base64. Luôn kiểm tra tài liệu nhà cung cấp để xác nhận định dạng mã hóa — nhầm lẫn giữa hex và Base64 là nguyên nhân phổ biến nhất gây ra lỗi không khớp chữ ký.

Xác Thực Yêu Cầu API với HMAC

Một số API yêu cầu client phải ký mỗi yêu cầu bằng HMAC. Chuỗi được ký thường bao gồm phương thức HTTP, đường dẫn, dấu thời gian và phần thân yêu cầu. Đây là mẫu tôi dùng cho xác thực giữa các dịch vụ nội bộ.

Python 3.7+ — ký yêu cầu API với HMAC-SHA256
import hmac
import hashlib
import time
import json

def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
    """Tạo chữ ký HMAC-SHA256 cho yêu cầu API."""
    timestamp = str(int(time.time()))

    # Xây dựng chuỗi ký — method + path + timestamp + body
    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,
    }

# Sử dụng
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..."}

Ký Yêu Cầu HTTP với Thư Viện requests

Python 3.7+ — yêu cầu có chữ ký HMAC với thư viện 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 gọn, tất định
    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

# Gửi yêu cầu POST có chữ ký
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())

Lưu ý nhanh: hãy dùng separators=(",", ":") khi tuần tự hóa phần thân để ký. Mặc định của json.dumps() thêm khoảng cách sau dấu phân cách, điều này thay đổi biểu diễn byte và làm hỏng xác minh chữ ký nếu server tuần tự hóa khác đi. JSON gọn mang lại dạng chuẩn tắc.

Tạo HMAC từ Dòng Lệnh

Đôi khi bạn cần tính HMAC mà không cần viết script. Cờ -c của Python và openssl đều xử lý được từ terminal.

bash — HMAC-SHA256 qua Python một dòng
python3 -c "
import hmac, hashlib
print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest())
"
# xuất ra: chuỗi hex 64 ký tự
bash — HMAC-SHA256 qua openssl (để so sánh)
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret"
# SHA2-256(stdin)= 7d11...

# Đưa file qua HMAC openssl
openssl dgst -sha256 -hmac "my_secret" < payload.json
bash — HMAC từ biến môi trường
# Lưu khóa trong biến môi trường để tránh lộ lịch sử shell
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())
"
Lưu ý:Cờ echo -n rất quan trọng — nếu không có nó, echo sẽ thêm ký tự xuống dòng vào thông điệp, làm thay đổi kết quả HMAC. Đây là nguyên nhân phổ biến nhất gây ra lỗi không khớp chữ ký khi gỡ lỗi từ terminal.

Lựa Chọn Thay Thế Hiệu Năng Cao — Thư Viện cryptography

Với hầu hết ứng dụng, module hmac chuẩn đã đủ nhanh. Nếu bạn đã dùng thư viện cryptography cho TLS hoặc xử lý chứng chỉ, nó cũng cung cấp HMAC được hỗ trợ bởi OpenSSL. Sự khác biệt thực tế chính so với stdlib là API .verify() dựa trên ngoại lệ được mô tả bên dưới — nó gây ra ngoại lệ khi không khớp thay vì trả về boolean mà bạn có thể quên kiểm tra.

bash — cài đặt cryptography
pip install cryptography
Python 3.7+ — HMAC qua thư viện 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()  # bytes thô

print(signature.hex())
# "9c4e2a..."

# Chế độ xác minh — gây InvalidSignature nếu không khớp
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature)  # gây cryptography.exceptions.InvalidSignature nếu sai

Phương thức .verify() của thư viện cryptography rất hữu ích: nó gây ra ngoại lệ khi không khớp thay vì trả về boolean. Điều này giúp khó bỏ qua lỗi xác minh hơn. Hàm hmac.compare_digest() của thư viện chuẩn trả về True/ False, có thể bị bỏ qua âm thầm nếu lập trình viên quên kiểm tra giá trị trả về.

Đầu Ra Terminal với Tô Màu Cú Pháp

Nếu bạn đang gỡ lỗi chữ ký HMAC trong terminal và muốn đầu ra có màu, rich xử lý điều này rất tốt.

bash — cài đặt rich
pip install rich
Python 3.7+ — đầu ra HMAC có màu với 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)
Lưu ý:Rich chỉ dùng để hiển thị trên terminal. Không dùng nó khi ghi chữ ký HMAC vào file, HTTP headers, hoặc hệ thống tổng hợp log — các mã thoát ANSI sẽ làm hỏng đầu ra.

Làm Việc với File Lớn — HMAC Liên Tiếp

Với các file trên 50 MB, việc nạp toàn bộ vào bộ nhớ chỉ để tính HMAC là lãng phí. Phương thức .update() trên đối tượng HMAC cho phép bạn đưa dữ liệu vào từng phần. Điều này giữ mức sử dụng bộ nhớ không đổi bất kể kích thước file.

Python 3.7+ — HMAC file lớn theo từng phần
import hmac
import hashlib

def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
    """Tính HMAC-SHA256 của file mà không nạp toàn bộ vào bộ nhớ."""
    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()

# Ký bản xuất cơ sở dữ liệu 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+ — xác minh chữ ký HMAC của file đã tải về
import hmac
import hashlib

def verify_file_hmac(key: bytes, filepath: str, expected_hex: str) -> bool:
    """Xác minh chữ ký HMAC-SHA256 của một file."""
    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)

# Xác minh artifact đã tải về
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'}")
Lưu ý:Chuyển sang HMAC theo phần khi file vượt quá 50-100 MB hoặc khi xử lý tải lên trong web server nơi bộ nhớ mỗi yêu cầu rất quan trọng. Phương pháp .update() sử dụng bộ nhớ cố định chunk_size bất kể kích thước file. Tôi mặc định dùng khối 64 KB — đủ lớn để phân bổ chi phí gọi syscall, đủ nhỏ để nằm trong cache L2 trên hầu hết phần cứng.

Tạo Khóa HMAC An Toàn Mật Mã Học trong Python

Khóa yếu hoặc có thể đoán được sẽ làm suy yếu toàn bộ cấu trúc HMAC. Module secrets (Python 3.6+) cung cấp các byte ngẫu nhiên mạnh về mặt mật mã học. Với HMAC-SHA256, dùng khóa 32 byte. Với HMAC-SHA512, dùng 64 byte. Chúng khớp với kích thước khối bên trong của các thuật toán băm tương ứng, đây là độ dài khóa tối ưu theo RFC 2104.

Python 3.7+ — tạo khóa HMAC với secrets
import secrets

# Tạo khóa khớp với kích thước khối của thuật toán băm
sha256_key = secrets.token_bytes(32)   # 256 bit — cho HMAC-SHA256
sha512_key = secrets.token_bytes(64)   # 512 bit — cho HMAC-SHA512

# Biểu diễn hex — an toàn cho file cấu hình và biến môi trường
print(f"HMAC-SHA256 key: {sha256_key.hex()}")
# vd. "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"

print(f"HMAC-SHA512 key: {sha512_key.hex()}")
# chuỗi hex 128 ký tự

# Base64 an toàn cho URL — gọn, an toàn cho HTTP headers
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Base64 key: {b64_key}")
# vd. "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="
Cảnh báo:Không bao giờ dùng random.random() hay random.randbytes() từ module random cho khóa HMAC. Module random mặc định sử dụng Mersenne Twister PRNG, có thể dự đoán được sau khi quan sát 624 đầu ra. Luôn dùng secrets.token_bytes() cho các yêu cầu bảo mật.

Độ Dài Khóa và Yêu Cầu RFC 2104

RFC 2104 quy định rằng khóa HMAC có thể có độ dài bất kỳ, nhưng khuyến nghị khóa ít nhất L byte — trong đó L là độ dài đầu ra của hàm băm cơ sở. Với HMAC-SHA256, đó là 32 byte (256 bit). Khóa ngắn hơn L bit sẽ giảm biên độ bảo mật theo tỷ lệ. Khóa dài hơn kích thước khối của hàm băm (64 byte cho SHA-256, 128 byte cho SHA-512) sẽ được băm xuống kích thước khối trước, do đó không có lợi ích khi dùng khóa dài hơn kích thước khối. Hãy dùng 32 byte cho HMAC-SHA256 và 64 byte cho HMAC-SHA512.

Lưu Trữ Khóa An Toàn và Xoay Vòng

Không bao giờ hardcode khóa HMAC trong mã nguồn. Cách tiếp cận tiêu chuẩn cho các triển khai sản xuất là nạp khóa từ biến môi trường khi khởi động: os.environ["HMAC_SECRET"].encode(). Với các môi trường cần bảo mật cao hơn, hãy lưu khóa trong hệ thống quản lý bí mật như AWS Secrets Manager, HashiCorp Vault, hoặc GCP Secret Manager và lấy chúng khi chạy. Các hệ thống này cung cấp nhật ký kiểm tra, kiểm soát truy cập và xoay vòng tự động mà không cần triển khai mã.

Hãy lên kế hoạch xoay vòng khóa ngay từ đầu. Khi khóa được xoay, có một khoảng thời gian mà các yêu cầu đang xử lý đã được ký bằng khóa cũ sẽ thất bại xác minh với khóa mới. Biện pháp giảm thiểu tiêu chuẩn là một khoảng thời gian chồng chéo ngắn: chấp nhận chữ ký từ cả khóa cũ và mới trong một thời gian ngắn (vài phút đến vài giờ), sau đó ngừng dùng khóa cũ. Nếu khóa bị xâm phạm — lộ trong log, rò rỉ qua commit git, hoặc được tiết lộ trong một sự cố — hãy xoay ngay lập tức và coi tất cả chữ ký được tạo bằng khóa bị xâm phạm là không đáng tin. Xác minh lại mọi kết quả xác minh đã được lưu vào bộ nhớ đệm và thông báo cho những người dùng hạ nguồn về sự thay đổi khóa.

Sử Dụng bytearray và memoryview với hmac.new()

Hàm hmac.new() chấp nhận bất kỳ đối tượng kiểu bytes nào cho cả tham số key và msg. Điều này có nghĩa là bạn có thể truyền bytes, bytearray, hoặc memoryview trực tiếp, không cần sao chép hay chuyển đổi. Điều này quan trọng nhất trong hai trường hợp: triển khai giao thức mạng nơi socket.recv_into() ghi dữ liệu vào bộ đệm bytearray được cấp phát sẵn, và các hệ thống thông lượng cao nơi tránh sao chép trung gian giảm áp lực GC. Một lát memoryview là zero-copy: nó hiển thị một cửa sổ vào bộ đệm gốc mà không cấp phát bộ nhớ mới. Ở hàng chục nghìn thông điệp mỗi giây, việc loại bỏ các cấp phát đó tạo ra sự khác biệt đáng kể về độ trễ và thông lượng.

Python 3.7+ — bytearray và memoryview với HMAC
import hmac
import hashlib

# bytearray — bytes có thể thay đổi, hữu ích cho bộ đệm giao thức nhị phân
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 — lát zero-copy của bộ đệm lớn hơn
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"

# HMAC chỉ 20 byte đầu tiên mà không cần sao chép
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")

Các Lỗi Thường Gặp

Tôi thấy hai lỗi đầu tiên trong hầu hết mọi code review liên quan đến webhook handler. Chúng dễ xuất hiện dưới áp lực thời gian và khó phát hiện nếu không biết phải tìm gì.

So sánh chữ ký HMAC bằng == thay vì hmac.compare_digest()

Vấn đề: Toán tử == dừng lại ngay tại byte đầu tiên không khớp, làm lộ thông tin về thời gian cho phép kẻ tấn công tái tạo dần chữ ký dự kiến.

Cách sửa: Luôn dùng hmac.compare_digest() để so sánh chữ ký — nó chạy trong thời gian cố định bất kể vị trí không khớp.

Before · Python
After · Python
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()

if received_sig == expected_sig:  # DỄ BỊ tấn công timing
    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):  # thời gian cố định
    process_webhook(body)
Truyền chuỗi thay vì bytes vào hmac.new()

Vấn đề: hmac.new() yêu cầu đối tượng kiểu bytes. Truyền Python str sẽ gây TypeError: "key: expected bytes or bytearray, but got 'str'".

Cách sửa: Gọi .encode() trên khóa và thông điệp dạng chuỗi trước khi truyền vào hmac.new().

Before · Python
After · Python
key = "my_api_secret"  # str, không phải bytes
msg = '{"event":"test"}'  # str, không phải 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)
Quên digestmod (Python 3.4+)

Vấn đề: Gọi hmac.new(key, msg) không có đối số thứ ba sẽ gây TypeError. Trước Python 3.4, nó mặc định là MD5, nhưng giá trị mặc định đã bị xóa vì lý do bảo mật.

Cách sửa: Luôn truyền thuật toán một cách rõ ràng: hashlib.sha256, hashlib.sha512, hoặc bất kỳ thuật toán nào giao thức của bạn yêu cầu.

Before · Python
After · Python
# Thiếu digestmod — gây TypeError trong Python 3.4+
sig = hmac.new(key, msg).hexdigest()
# Luôn chỉ định thuật toán băm
sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
Dùng .hexdigest() khi nhà cung cấp mong đợi Base64

Vấn đề: Nhiều nhà cung cấp webhook (Stripe, Shopify) gửi chữ ký mã hóa Base64, không phải hex. So sánh chuỗi hex với giá trị Base64 luôn thất bại, khiến tất cả webhook bị từ chối.

Cách sửa: Kiểm tra tài liệu nhà cung cấp về định dạng chữ ký. Nếu họ dùng Base64, hãy mã hóa bytes .digest() thô, không phải chuỗi .hexdigest().

Before · Python
After · Python
# Nhà cung cấp gửi Base64, nhưng ta tính hex — không bao giờ khớp
expected = hmac.new(key, body, hashlib.sha256).hexdigest()
# "a3f1b9c0..."  vs  "o/G5wE59..."  — luôn không khớp
import base64
# Khớp định dạng nhà cung cấp: bytes thô → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..."  — khớp với header

stdlib hmac so với cryptography — So Sánh Nhanh

Phương thức
Thuật toán
Đầu ra
Streaming
Kiểu tùy chỉnh
Cần cài đặt
hmac.new() + hexdigest()
Any hashlib
Chuỗi hex
✓ qua .update()
N/A
Không (stdlib)
hmac.new() + digest()
Any hashlib
Bytes thô
✓ qua .update()
N/A
Không (stdlib)
hmac.digest()
Any hashlib
Bytes thô
✗ (một lần)
N/A
Không (stdlib, 3.7+)
hashlib.sha256 (băm thông thường)
Chỉ SHA-256
Hex hoặc bytes
✓ qua .update()
N/A
Không (stdlib)
cryptography (HMAC)
Bất kỳ
Bytes thô
✓ qua .update()
N/A
pip install
pyca/cryptography + CMAC
AES-CMAC
Bytes thô
✓ qua .update()
N/A
pip install

Dùng module hmac stdlib cho xác minh webhook, ký API và các thao tác HMAC chung — không cần phụ thuộc và bao phủ mọi thuật toán tiêu chuẩn. Dùng hmac.digest() cho các thao tác batch nơi tốc độ một lần quan trọng. Chỉ dùng thư viện cryptography khi bạn đã phụ thuộc vào nó cho các thao tác khác (TLS, X.509, mã hóa đối xứng) và muốn API .verify() dựa trên ngoại lệ. Để kiểm tra chữ ký nhanh mà không cần viết bất kỳ mã Python nào, hãy dùng công cụ HMAC Generator để dán khóa và thông điệp của bạn và nhận kết quả ngay lập tức.

Câu Hỏi Thường Gặp

Sự khác biệt giữa hmac.new() và hmac.digest() trong Python là gì?

hmac.new() trả về một đối tượng HMAC hỗ trợ các lần gọi .update() liên tiếp và cung cấp cả .digest() (bytes thô) lẫn .hexdigest() (chuỗi hex). hmac.digest() là hàm một lần được thêm vào Python 3.7, trả về bytes thô trực tiếp mà không tạo đối tượng trung gian. Dùng hmac.digest() khi bạn đã có toàn bộ dữ liệu và chỉ cần kết quả. Dùng hmac.new() khi cần đưa dữ liệu vào từng phần hoặc cần đầu ra dạng hex.

Python
import hmac, hashlib

key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'

# Một lần — trả về bytes thô
raw = hmac.digest(key, body, hashlib.sha256)

# Dạng đối tượng — hỗ trợ cập nhật liên tiếp và đầu ra hex
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()

Làm thế nào để xác minh chữ ký HMAC trong Python?

Tính lại HMAC trên dữ liệu gốc bằng khóa bí mật chung, sau đó so sánh bằng hmac.compare_digest(). Không bao giờ dùng == để so sánh chữ ký. Toán tử == dễ bị tấn công timing vì nó dừng lại ngay khi gặp byte đầu tiên không khớp, làm lộ thông tin về độ dài và nội dung chữ ký dự kiến.

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)

HMAC SHA-256 trong Python có giống với băm bằng hashlib.sha256 không?

Không. hashlib.sha256 tính toán giá trị băm SHA-256 thuần túy từ dữ liệu đầu vào, bất kỳ ai cũng có thể tái tạo. HMAC-SHA256 kết hợp khóa bí mật vào quá trình tính băm theo RFC 2104, nên chỉ người có khóa mới tạo ra hoặc xác minh đúng kết quả. Giá trị băm thuần túy chứng minh tính toàn vẹn của dữ liệu. HMAC chứng minh cả tính toàn vẹn lẫn tính xác thực.

Python
import hmac, hashlib

msg = b"transfer:9950:USD"
key = b"api_secret_k8x2"

plain_hash = hashlib.sha256(msg).hexdigest()  # ai cũng có thể tính được
hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest()  # yêu cầu có khóa

Tôi có thể dùng HMAC-SHA1 trong Python 3 không?

Có, truyền hashlib.sha1 làm đối số digestmod. HMAC-SHA1 vẫn hoạt động tốt trong Python 3 và module hmac không có cảnh báo về việc không dùng nó nữa. Tuy nhiên, SHA-1 được coi là yếu đối với các thiết kế mới — khả năng chống va chạm của nó thấp hơn 80 bit và NIST đã ngừng khuyến nghị sử dụng cho hầu hết các mục đích ký số từ năm 2015. Lý do chính để dùng HMAC-SHA1 hiện nay là tương thích ngược với các giao thức hiện có yêu cầu nó, như OAuth 1.0a hoặc một số hệ thống webhook cũ. Khi bạn kiểm soát cả hai phía của tích hợp, hãy ưu tiên HMAC-SHA256 hoặc HMAC-SHA512 cho mọi công việc mới.

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

Làm thế nào để tạo khóa HMAC bảo mật trong Python?

Dùng secrets.token_bytes() từ thư viện chuẩn. Với HMAC-SHA256, khóa 32 byte là khuyến nghị tiêu chuẩn vì khớp với kích thước khối băm. Với HMAC-SHA512, dùng 64 byte. Không dùng random.random() hay os.urandom() để tạo khóa trong mã ứng dụng — secrets là module đúng đắn cho các yêu cầu bảo mật từ Python 3.6.

Python
import secrets

hmac_sha256_key = secrets.token_bytes(32)  # 256 bits
hmac_sha512_key = secrets.token_bytes(64)  # 512 bits

# Lưu dạng hex cho file cấu hình
print(hmac_sha256_key.hex())
# vd. "a3f1b9c04e..."

Tại sao hmac.new() yêu cầu digestmod trong Python 3?

Trước Python 3.4, digestmod mặc định là MD5 — thuật toán đã bị phá vỡ về mặt mật mã học với các cuộc tấn công va chạm đã biết, không nên dùng trong mã bảo mật mới. Các nhà phát triển Python đã xóa giá trị mặc định để buộc lựa chọn thuật toán rõ ràng, tránh việc vô tình triển khai MAC dựa trên MD5. Nếu gọi hmac.new(key, msg) không có digestmod, bạn sẽ nhận TypeError. Luôn truyền thuật toán một cách rõ ràng: hashlib.sha256, hashlib.sha512, hoặc bất kỳ hàm khởi tạo hashlib nào khác. Khi không chắc, hashlib.sha256 là lựa chọn mặc định an toàn — không có điểm yếu đã biết và đủ nhanh cho mọi khối lượng công việc thực tế.

Python
import hmac, hashlib

key = b"secret"
msg = b"data"

# Dòng này gây TypeError trong Python 3.4+
# hmac.new(key, msg)  # TypeError: missing required argument: 'digestmod'

# Luôn chỉ định thuật toán
h = hmac.new(key, msg, hashlib.sha256)

Để kiểm tra HMAC nhanh mà không cần chạy script Python, hãy dán khóa và thông điệp của bạn vào HMAC Generator trực tuyến — hỗ trợ SHA-256, SHA-384 và SHA-512 với kết quả tức thì.

Công Cụ Liên Quan

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 SantosNgười đánh giá kỹ thuật

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.