HMAC trong Python — Hướng dẫn hmac.new() SHA-256 + ví dụ
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.
# 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 và 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".
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ã.
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.
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ándigestmod 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().
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.
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 headerHMAC-SHA512 — Đầu Ra Dài Hơn
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ũ
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"
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()
hmac.digest() một lần (Python 3.7+)
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.
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.
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
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 "", 200Hà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.
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 "", 200Mẫ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ộ.
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
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.
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ự
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
# 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())
"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.
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() # 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 saiPhươ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.
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)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.
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}")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'}").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.
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="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.
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ì.
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.
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)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().
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)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.
# 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()
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().
# 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 headerstdlib hmac so với cryptography — So Sánh Nhanh
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.
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.
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.
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.
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.
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ế.
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
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.