HMAC در Python — راهنمای hmac.new() SHA-256 + مثالهای کد
از تولیدکننده HMAC آنلاین رایگان مستقیم در مرورگرتان استفاده کنید — نیازی به نصب نیست.
امتحان کردن تولیدکننده HMAC آنلاین ←هر callback وبهوک، هر درخواست API امضاشده، هر رویداد Stripe یا GitHub از یک امضای HMAC برای تضمین یکپارچگی محتوا استفاده میکند. ماژول hmac در Python با یک فراخوانی تابع، HMAC-SHA256 در Python را پیادهسازی میکند: hmac.new(key, msg, hashlib.sha256). بدون pip install، بدون افزونه C، بدون وابستگی خارجی. برای بررسی سریع امضا بدون نوشتن کد، مولد آنلاین HMAC نتیجه را فوری ارائه میدهد. این راهنما شامل hmac.new()، hmac.digest()، hmac.compare_digest()، رمزگذاری Base64، تأیید وبهوک، امضای درخواست API، و تمام الگوریتمهای هش از SHA-1 تا SHA-512 است. تمام مثالها برای Python 3.7+ طراحی شدهاند.
- ✓hmac.new(key, msg, hashlib.sha256) نقطه ورود استاندارد است — key و msg باید bytes باشند، digestmod از Python 3.4 الزامی است.
- ✓hmac.digest(key, msg, "sha256") یک جایگزین سریع یکمرحلهای است که در Python 3.7 اضافه شد — bytes خام برمیگرداند، بدون شیء میانی.
- ✓همیشه امضاها را با hmac.compare_digest() تأیید کنید تا از حملات زمانبندی جلوگیری شود — هرگز از == برای مقایسه HMAC استفاده نکنید.
- ✓خروجی خام .digest() را برای هدرهای HTTP و امضاهای وبهوک به Base64 تبدیل کنید: base64.b64encode(h.digest()).
- ✓ماژول hmac هر الگوریتم hashlib را میپذیرد: sha1، sha256، sha384، sha512، md5، blake2b.
HMAC چیست؟
HMAC (کد احراز هویت پیام مبتنی بر هش) یک ساختار تعریفشده در RFC 2104 است که یک کلید محرمانه را با یک تابع هش ترکیب میکند تا یک برچسب احراز هویت با اندازه ثابت تولید کند. بر خلاف هش ساده (که هر کسی میتواند آن را محاسبه کند)، یک HMAC نیاز به دانستن کلید محرمانه دارد. این یعنی میتوانید از آن برای تأیید هم صحت و هم اصالت یک پیام استفاده کنید. اگر حتی یک بایت از پیام یا کلید تغییر کند، خروجی کاملاً متفاوت خواهد بود. این ساختار با هش کردن کلید XOR شده با دو ثابت پدینگ مختلف (ipad و opad) کار میکند و پیام را بین دو عملیات هش قرار میدهد. ماژول hmac Python این RFC را مستقیماً پیادهسازی میکند.
# HMAC-SHA256 — برای تولید به کلید محرمانه نیاز دارد hmac.new(b"api_secret", b"payment:9950:USD", hashlib.sha256).hexdigest() # "e4f2a8..." (فقط دارنده کلید میتواند محاسبه کند)
# هش SHA-256 ساده — بدون کلید محرمانه، هر کسی میتواند محاسبه کند hashlib.sha256(b"payment:9950:USD").hexdigest() # "7a3b1c..." (قطعی، عمومی)
hmac.new() — نقطه ورود کتابخانه استاندارد
ماژول hmac بخشی از کتابخانه استاندارد Python است. دو import و آمادهاید: import hmac, hashlib. سه تابع اصلی عبارتند از hmac.new() (یک شیء HMAC میسازد)، hmac.digest() (یکمرحلهای، Python 3.7+)، و hmac.compare_digest() (مقایسه با زمان ثابت). نیازی به pip install نیست.
hmac.new(key, msg, digestmod) سه آرگومان میگیرد. هر دوی key و msg باید از نوع bytes-like باشند ( bytes، bytearray، یا memoryview). آرگومان digestmod از Python 3.4 الزامی است و هر سازنده hashlib (مانند hashlib.sha256) یا نام رشتهای مانند "sha256" را میپذیرد.
import hmac
import hashlib
key = b"webhook_signing_key_2026"
message = b'{"event":"invoice.paid","invoice_id":"inv_8f3a","amount":19900}'
# شیء HMAC را بسازید و امضای هگز را دریافت کنید
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
print(signature)
# "b4e74f6c9a1d3e5f8b2a7c0d4e6f1a3b5c7d9e0f2a4b6c8d0e1f3a5b7c9d0e2f"شیء HMAC دو متد خروجی دارد. .digest() bytes خام برمیگرداند (۳۲ بایت برای SHA-256، ۶۴ بایت برای SHA-512). .hexdigest() یک رشته هگز با حروف کوچک برمیگرداند. رشته هگز یک str ساده Python است — نیازی به مرحله رمزگشایی نیست.
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 # هر دو نمایانگر یک داده هستند — هگز فقط رمزگذاری رشتهای bytes است assert raw_bytes.hex() == hex_string
اگر کلید یا پیام شما یک رشته Python است، قبل از ارسال به hmac.new()، .encode() را فراخوانی کنید تا به bytes تبدیل شود. این تقریباً همه را بار اول گیج میکند — رشتههای Python 3 از نوع Unicode هستند، نه bytes، و ماژول hmac آنها را نمیپذیرد.
import hmac
import hashlib
# کلید و پیام رشتهای — .encode() به 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..." — خروجی رشته هگز یکسانdigestmod از Python 3.4 هیچ مقدار پیشفرضی ندارد. فراخوانی hmac.new(key, msg) بدون آن TypeError ایجاد میکند. قبل از نسخه ۳.۴ به طور پیشفرض MD5 بود، به همین دلیل نگهدارندگان Python پیشفرض را حذف کردند — تا شما را مجبور کنند انتخاب صریح و امنی داشته باشید.HMAC-SHA256 با Base64، SHA-1، SHA-512، و MD5
تابع hmac.new() با هر الگوریتم هش موجود در hashlib کار میکند. اکثر ارائهدهندگان وبهوک و دروازههای API از HMAC-SHA256 استفاده میکنند، اما SHA-1 را در OAuth 1.0a، SHA-512 را در پروتکلهایی که آن را الزامی میکنند، و MD5 را در سیستمهای قدیمی که بهروزرسانی نشدهاند خواهید دید.
HMAC-SHA256 با خروجی Base64
بسیاری از ارائهدهندگان وبهوک امضا را به صورت رشته Base64 در یک هدر HTTP ارسال میکنند. برای تولید همین فرمت، bytes خام .digest() را به base64.b64encode() ارسال کنید.
import hmac
import hashlib
import base64
key = b"whsec_MbkP7x9yFqHGn3tRdWz5"
payload = b'{"id":"evt_1Nq","type":"charge.succeeded","data":{"amount":4200}}'
# خلاصه خام → Base64 (رایج برای هدرهای Authorization و امضاهای وبهوک)
raw_digest = hmac.new(key, payload, hashlib.sha256).digest()
b64_signature = base64.b64encode(raw_digest).decode("ascii")
print(b64_signature)
# "dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="
# این مقداری است که با هدر X-Signature مقایسه میکنید
header_value = f"sha256={b64_signature}"
print(header_value)
# "sha256=dGhpcyBpcyBhIHNhbXBsZSBzaWduYXR1cmU="HMAC-SHA1 — سازگاری با پروتکلهای قدیمی
SHA-1 برای پروژههای جدید ضعیف در نظر گرفته میشود، اما HMAC-SHA1 همچنان توسط OAuth 1.0a و برخی پیادهسازیهای قدیمیتر وبهوک الزامی است. کد یکسان است — فقط الگوریتم را عوض کنید.
import hmac
import hashlib
consumer_secret = b"oauth_consumer_secret_2026"
token_secret = b"oauth_token_secret_2026"
# OAuth 1.0a از consumer_secret&token_secret به عنوان کلید امضا استفاده میکند
signing_key = consumer_secret + b"&" + token_secret
base_string = b"GET&https%3A%2F%2Fapi.service.com%2Fv1%2Forders&oauth_nonce%3D7f3a91bc"
sig = hmac.new(signing_key, base_string, hashlib.sha1).digest()
import base64
oauth_signature = base64.b64encode(sig).decode("ascii")
print(oauth_signature)
# "Tza3R9sE..." — برای هدر Authorization این را URL-encode کنیدHMAC-SHA512 — خروجی طولانیتر
import hmac
import hashlib
key = b"high_security_signing_key_64_bytes_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
msg = b'{"transfer_id":"xfr_9c2e","amount":500000,"currency":"EUR"}'
h = hmac.new(key, msg, hashlib.sha512)
print(len(h.digest())) # 64 بایت (512 بیت)
print(len(h.hexdigest())) # 128 کاراکتر هگز
print(h.hexdigest()[:40] + "...")
# "8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f..."HMAC-MD5 — فقط برای سیستمهای قدیمی
import hmac import hashlib # MD5 از نظر رمزنگاری شکسته شده — فقط برای سازگاری با پروتکلهای قدیمی استفاده کنید key = b"legacy_api_key" msg = b"action=charge&amount=1500&merchant=store_42" sig = hmac.new(key, msg, hashlib.md5).hexdigest() print(sig) # رشته هگز ۳۲ کاراکتری # "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
مرجع پارامترهای hmac.new()
امضای سازنده hmac.new(key, msg=None, digestmod) است. هر سه تابع ماژول الگوی یکسانی برای آرگومانهای کلید و الگوریتم دارند.
سازنده hmac.new()
hmac.digest() یکمرحلهای (Python 3.7+)
پارامتر digestmod هم callable (مانند hashlib.sha256) و هم نام رشتهای (مانند "sha256") میپذیرد. فرم callable ترجیح داده میشود زیرا در زمان import اعتبارسنجی میشود — یک اشتباه تایپی در فرم رشتهای فقط در زمان اجرا خطا میدهد.
hmac.digest() — HMAC یکمرحلهای سریع (Python 3.7+)
Python 3.7 تابع hmac.digest(key, msg, digest) را به عنوان یک تابع سطح ماژول اضافه کرد. این تابع HMAC را در یک فراخوانی بدون ساختن شیء HMAC میانی محاسبه میکند. مقدار بازگشتی bytes خام است (معادل فراخوانی .digest() روی شیء). این تابع از یک پیادهسازی بهینهشده C در CPython استفاده میکند و از سربار تخصیص شیء اجتناب میکند، که آن را در حلقههای پرتکرار به طور قابل اندازهگیری سریعتر میکند.
import hmac
import hashlib
key = b"batch_signing_key_2026"
messages = [
b'{"order_id":"ord_001","total":4500}',
b'{"order_id":"ord_002","total":8900}',
b'{"order_id":"ord_003","total":2200}',
]
# خلاصه یکمرحلهای — بدون شیء HMAC میانی
signatures = [hmac.digest(key, msg, hashlib.sha256) for msg in messages]
# برای نمایش به هگز تبدیل کنید
for msg, sig in zip(messages, signatures):
print(f"{msg[:30]}... -> {sig.hex()[:24]}...")محدودیت: hmac.digest() فقط bytes خام برمیگرداند. اگر مستقیماً به رشته هگز نیاز دارید، همچنان به hmac.new() برای متد .hexdigest() آن نیاز دارید، یا .hex() را روی نتیجه bytes زنجیر کنید.
hmac.digest() از فراخوانیهای تدریجی .update() پشتیبانی نمیکند. اگر یک فایل بزرگ را به صورت تکهتکه میخوانید و باید محتوا را HMAC کنید، از hmac.new() استفاده کنید و .update(chunk) را در یک حلقه صدا بزنید.تأیید امضای HMAC از وبهوک و پاسخ API
رایجترین کاربرد HMAC در Python تأیید امضاهای وبهوک است. هر ارائهدهنده اصلی (Stripe، GitHub، Shopify، Twilio) محتوا را با HMAC-SHA256 امضا میکند و امضا را در یک هدر ارسال میکند. الگو همیشه یکسان است: HMAC را روی بدنه خام درخواست محاسبه مجدد کنید، سپس با hmac.compare_digest() مقایسه کنید.
تأیید امضای وبهوک
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = b"whsec_MbkP7x9yFqHGn3tRdWz5"
@app.route("/webhooks/payments", methods=["POST"])
def handle_payment_webhook():
# بدنه خام را بگیرید — باید دقیقاً با آنچه امضا شده مطابقت داشته باشد
raw_body = request.get_data()
# امضا را از هدر بگیرید
received_sig = request.headers.get("X-Signature-256", "")
# HMAC را روی بدنه خام محاسبه مجدد کنید
expected_sig = hmac.new(WEBHOOK_SECRET, raw_body, hashlib.sha256).hexdigest()
# مقایسه با زمان ثابت — از حملات زمانبندی جلوگیری میکند
if not hmac.compare_digest(f"sha256={expected_sig}", received_sig):
abort(403, "Invalid signature")
# امضا تأیید شد — رویداد را پردازش کنید
event = request.get_json()
print(f"Verified event: {event['type']} for {event['data']['amount']}")
return "", 200تابع hmac.compare_digest() دو رشته یا دنباله bytes را با زمان ثابت مقایسه میکند. مقایسه معمولی با == در اولین بایت نامطابق اتصال کوتاه میکند. یک مهاجم میتواند زمان پاسخ را در تعداد زیادی درخواست اندازهگیری کند و به تدریج امضای مورد انتظار را بایت به بایت بازسازی کند. مقایسه با زمان ثابت این کانال جانبی را از بین میبرد.
تأیید وبهوک GitHub
فرمت وبهوک GitHub الگوی کامل را نشان میدهد. یک هدر X-Hub-Signature-256 ارسال میکند که حاوی sha256= به دنبال HMAC-SHA256 هگز-رمزگذاریشده بدنه خام درخواست است که با رمز وبهوکی که در تنظیمات مخزن پیکربندی کردهاید امضا شده. تفاوت کلیدی از تأیید وبهوک عمومی این است که باید پیشوند sha256= را قبل از مقایسه حذف کنید، و باید bytes خام بدنه درخواست را بخوانید — ابتدا JSON را پارس کردن نمایش bytes را تغییر داده و تأیید را خراب میکند.
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
GITHUB_WEBHOOK_SECRET = b"your_github_webhook_secret"
@app.route("/webhooks/github", methods=["POST"])
def handle_github_webhook():
# GitHub ارسال میکند: X-Hub-Signature-256: sha256=<hex_digest>
sig_header = request.headers.get("X-Hub-Signature-256", "")
if not sig_header.startswith("sha256="):
abort(403, "Missing or malformed signature header")
received_hex = sig_header[len("sha256="):]
raw_body = request.get_data() # bytes خام — قبل از این JSON را پارس نکنید
expected_hex = hmac.new(
GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_hex, received_hex):
abort(403, "Signature mismatch — payload may have been tampered with")
event_type = request.headers.get("X-GitHub-Event", "unknown")
payload = request.get_json()
print(f"Verified GitHub {event_type} event: {payload.get('action', '')}")
return "", 200همین الگو برای Shopify (X-Shopify-Hmac-SHA256) و Twilio (X-Twilio-Signature) صدق میکند، با تنها تفاوت در نام هدر و اینکه آیا امضا هگز است یا Base64. همیشه مستندات ارائهدهنده را برای تأیید فرمت رمزگذاری بررسی کنید — اشتباه گرفتن هگز و Base64 رایجترین دلیل خطاهای عدم تطابق امضا است.
احراز هویت درخواست API با HMAC
برخی APIها از کلاینت میخواهند که هر درخواست را با HMAC امضا کند. رشته امضاشده معمولاً شامل متد HTTP، مسیر، مُهر زمانی، و بدنه درخواست است. این الگویی است که من برای احراز هویت سرویسبهسرویس داخلی استفاده میکنم.
import hmac
import hashlib
import time
import json
def sign_request(secret: bytes, method: str, path: str, body: str) -> dict:
"""تولید امضای HMAC-SHA256 برای یک درخواست API."""
timestamp = str(int(time.time()))
# رشته امضا را بسازید — متد + مسیر + مُهر زمانی + بدنه
signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
signature = hmac.new(
secret,
signing_string.encode(),
hashlib.sha256
).hexdigest()
return {
"X-Timestamp": timestamp,
"X-Signature": signature,
}
# استفاده
api_secret = b"sk_hmac_9f3a2b7c4d8e1a6f"
body = json.dumps({"customer_id": "cust_4421", "action": "suspend_account"})
headers = sign_request(api_secret, "POST", "/api/v2/customers/actions", body)
print(headers)
# {"X-Timestamp": "1711612200", "X-Signature": "a3f1b9c0..."}امضای درخواستهای HTTP با کتابخانه requests
import hmac
import hashlib
import time
import json
import requests
API_SECRET = b"sk_hmac_9f3a2b7c4d8e1a6f"
BASE_URL = "https://api.billing-service.internal"
def make_signed_request(method: str, path: str, payload: dict) -> requests.Response:
body = json.dumps(payload, separators=(",", ":")) # JSON فشرده، قطعی
timestamp = str(int(time.time()))
signing_string = f"{method}\n{path}\n{timestamp}\n{body}"
signature = hmac.new(API_SECRET, signing_string.encode(), hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json",
"X-Timestamp": timestamp,
"X-Signature": f"hmac-sha256={signature}",
}
try:
return requests.request(method, f"{BASE_URL}{path}", data=body, headers=headers)
except requests.RequestException as e:
raise RuntimeError(f"Signed request failed: {e}") from e
# ارسال یک درخواست POST امضاشده
resp = make_signed_request("POST", "/api/v2/invoices", {
"customer_id": "cust_4421",
"line_items": [
{"description": "Pro plan - March 2026", "amount": 4900},
{"description": "Extra seats (3)", "amount": 2100},
],
})
print(resp.status_code, resp.json())نکته مهم: از separators=(",", ":") هنگام سریالسازی بدنه برای امضا استفاده کنید. تابع پیشفرض json.dumps() بعد از جداکنندهها فاصله اضافه میکند که نمایش bytes را تغییر داده و اگر سرور به شکل متفاوتی سریالسازی کند، تأیید امضا را خراب میکند. JSON فشرده به شما یک فرم استاندارد میدهد.
تولید HMAC از خط فرمان
گاهی نیاز دارید بدون نوشتن اسکریپت یک HMAC محاسبه کنید. پرچم -c در Python و openssl هر دو این کار را از ترمینال انجام میدهند.
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # خروجی: رشته هگز ۶۴ کاراکتری
echo -n "message_to_sign" | openssl dgst -sha256 -hmac "my_secret" # SHA2-256(stdin)= 7d11... # یک فایل را از طریق HMAC openssl ارسال کنید openssl dgst -sha256 -hmac "my_secret" < payload.json
# کلید را در متغیر محیطی ذخیره کنید تا در تاریخچه پوسته نمایش داده نشود
export HMAC_KEY="sk_live_9f3a2b"
echo -n '{"event":"test"}' | python3 -c "
import hmac, hashlib, sys, os
key = os.environ['HMAC_KEY'].encode()
msg = sys.stdin.buffer.read()
print(hmac.new(key, msg, hashlib.sha256).hexdigest())
"echo -n حیاتی است — بدون آن، echo یک کاراکتر خط جدید به پیام اضافه میکند که خروجی HMAC را تغییر میدهد. این رایجترین دلیل عدم تطابق امضا هنگام اشکالزدایی از ترمینال است.جایگزین پرکارایی — کتابخانه cryptography
برای اکثر برنامهها، ماژول استاندارد hmac به اندازه کافی سریع است. اگر از کتابخانه cryptography برای TLS یا مدیریت گواهینامهها استفاده میکنید، این کتابخانه نیز HMAC پشتیبانیشده توسط OpenSSL ارائه میدهد. تفاوت عملی اصلی با stdlib رابط .verify() مبتنی بر استثنا است که در زیر توضیح داده شده — در صورت عدم تطابق استثنا ایجاد میکند به جای اینکه booleanای که ممکن است فراموش کنید بررسی کنید را برگرداند.
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 خام
print(signature.hex())
# "9c4e2a..."
# حالت تأیید — در صورت عدم تطابق InvalidSignature ایجاد میکند
h_verify = crypto_hmac.HMAC(key, hashes.SHA256())
h_verify.update(message)
h_verify.verify(signature) # در صورت اشتباه cryptography.exceptions.InvalidSignature ایجاد میکندمتد .verify() کتابخانه cryptography بسیار مفید است: در صورت عدم تطابق استثنا ایجاد میکند به جای اینکه boolean برگرداند. این کار بیتوجهی به خطای تأیید را سختتر میکند. تابع hmac.compare_digest() در کتابخانه استاندارد True/ False برمیگرداند که اگر توسعهدهنده فراموش کند مقدار بازگشتی را بررسی کند میتواند بیصدا نادیده گرفته شود.
خروجی ترمینال با رنگبندی نحوی
اگر در حال اشکالزدایی امضاهای HMAC در ترمینال هستید و خروجی رنگی میخواهید، rich این کار را به خوبی انجام میدهد.
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)کار با فایلهای بزرگ — HMAC تدریجی
برای فایلهای بیش از ۵۰ مگابایت، بارگذاری همه چیز در حافظه فقط برای محاسبه HMAC اتلاف منابع است. متد .update() روی شیء HMAC به شما اجازه میدهد داده را به صورت تکهتکه بدهید. این میزان مصرف حافظه را بدون توجه به اندازه فایل ثابت نگه میدارد.
import hmac
import hashlib
def hmac_file(key: bytes, filepath: str, chunk_size: int = 8192) -> str:
"""محاسبه HMAC-SHA256 یک فایل بدون بارگذاری کامل آن در حافظه."""
h = hmac.new(key, digestmod=hashlib.sha256)
try:
with open(filepath, "rb") as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
h.update(chunk)
except OSError as e:
raise OSError(f"Cannot read file '{filepath}': {e}") from e
return h.hexdigest()
# امضای یک خروجی پایگاه داده ۲ گیگابایتی
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:
"""تأیید امضای HMAC-SHA256 یک فایل."""
h = hmac.new(key, digestmod=hashlib.sha256)
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return hmac.compare_digest(h.hexdigest(), expected_hex)
# تأیید یک artifact دانلودشده
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() صرفنظر از اندازه فایل از حافظه ثابتی به اندازه chunk_size استفاده میکند. من به طور پیشفرض از تکههای ۶۴ کیلوبایتی استفاده میکنم — به اندازه کافی بزرگ برای جبران سربار syscall، به اندازه کافی کوچک برای ماندن در کش L2 اکثر سختافزارها.تولید کلید HMAC امن از نظر رمزنگاری در Python
یک کلید ضعیف یا قابل پیشبینی کل ساختار HMAC را تضعیف میکند. ماژول secrets (Python 3.6+) bytes تصادفی قوی از نظر رمزنگاری فراهم میکند. برای HMAC-SHA256، از کلید ۳۲ بایتی استفاده کنید. برای HMAC-SHA512، از ۶۴ بایت استفاده کنید. این اندازهها با اندازه بلوک داخلی الگوریتمهای هش مربوطه مطابقت دارند که طول کلید بهینه طبق RFC 2104 است.
import secrets
# تولید کلیدهایی که با اندازه بلوک الگوریتم هش مطابقت دارند
sha256_key = secrets.token_bytes(32) # ۲۵۶ بیت — برای HMAC-SHA256
sha512_key = secrets.token_bytes(64) # ۵۱۲ بیت — برای HMAC-SHA512
# نمایش هگز — مناسب برای فایلهای پیکربندی و متغیرهای محیطی
print(f"HMAC-SHA256 key: {sha256_key.hex()}")
# مثلاً "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"
print(f"HMAC-SHA512 key: {sha512_key.hex()}")
# رشته هگز ۱۲۸ کاراکتری
# Base64 ایمن برای URL — فشرده، مناسب برای هدرهای HTTP
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"Base64 key: {b64_key}")
# مثلاً "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="random.random() یا random.randbytes() از ماژول random برای کلیدهای HMAC استفاده نکنید. ماژول پیشفرض random از PRNG Mersenne Twister استفاده میکند که پس از مشاهده ۶۲۴ خروجی قابل پیشبینی است. همیشه از secrets.token_bytes() برای تصادفیسازی حساس به امنیت استفاده کنید.طول کلید و الزامات RFC 2104
RFC 2104 مشخص میکند که کلید HMAC میتواند هر طولی داشته باشد، اما کلیدی با حداقل L بایت را توصیه میکند — که L طول خروجی تابع هش زیرین است. برای HMAC-SHA256، این ۳۲ بایت (۲۵۶ بیت) است. کلیدهای کوتاهتر از L بیت حاشیه امنیتی را به تناسب کاهش میدهند. کلیدهای بلندتر از اندازه بلوک هش (۶۴ بایت برای SHA-256، ۱۲۸ بایت برای SHA-512) ابتدا به اندازه بلوک هش میشوند، بنابراین استفاده از کلیدهای بلندتر از اندازه بلوک هیچ مزیتی ندارد. برای HMAC-SHA256 از ۳۲ بایت و برای HMAC-SHA512 از ۶۴ بایت استفاده کنید.
ذخیرهسازی امن کلید و چرخش آن
هرگز کلیدهای HMAC را در کد منبع هاردکد نکنید. رویکرد استاندارد برای استقرارهای تولیدی این است که کلید را از یک متغیر محیطی در زمان راهاندازی بارگذاری کنید: os.environ["HMAC_SECRET"].encode(). برای محیطهای با اطمینان بالاتر، کلیدها را در یک سیستم مدیریت اسرار مانند AWS Secrets Manager، HashiCorp Vault، یا GCP Secret Manager ذخیره کنید و در زمان اجرا بازیابی کنید. این سیستمها گزارشهای حسابرسی، کنترلهای دسترسی، و چرخش خودکار بدون نیاز به استقرار کد را فراهم میکنند.
از ابتدا برای چرخش کلید برنامهریزی کنید. وقتی یک کلید چرخانده میشود، یک پنجره زمانی وجود دارد که درخواستهای در حال انجام با کلید قدیمی امضا شدهاند و در مقابل کلید جدید تأیید نخواهند شد. راهحل استاندارد یک دوره همپوشانی کوتاه است: برای مدت کوتاهی (دقیقه تا ساعت) امضاهای هر دو کلید قدیمی و جدید را بپذیرید، سپس کلید قدیمی را بازنشسته کنید. اگر یک کلید به خطر افتاد — در لاگها نمایش داده شد، در یک commit git فاش شد، یا در یک حادثه افشا شد — فوراً آن را بچرخانید و تمام امضاهای تولیدشده با کلید در معرض خطر را غیرقابل اعتماد تلقی کنید. هر نتیجه تأیید کششده را دوباره تأیید کنید و مصرفکنندگان پاییندستی را از تغییر کلید خبر کنید.
استفاده از bytearray و memoryview با hmac.new()
تابع hmac.new() هر شیء bytes-like را برای پارامترهای key و msg میپذیرد. این یعنی میتوانید bytes، bytearray، یا memoryview را مستقیماً بدون کپی یا تبدیل ارسال کنید. این در دو سناریو بیشترین اهمیت را دارد: پیادهسازیهای پروتکل شبکه که socket.recv_into() داده را در یک بافر bytearray از پیش تخصیصیافته مینویسد، و سیستمهای پرتوان که اجتناب از کپیهای میانی فشار GC را کاهش میدهد. یک برش memoryview بدون کپی است: یک پنجره به بافر اصلی را بدون تخصیص حافظه جدید نشان میدهد. در دهها هزار پیام در ثانیه، حذف این تخصیصها تفاوت قابل اندازهگیری در تأخیر و توان خروجی ایجاد میکند.
import hmac
import hashlib
# bytearray — bytes قابل تغییر، مفید برای بافرهای پروتکل باینری
key = bytearray(b"protocol_signing_key")
frame = bytearray(b'\x01\x02\x03\x04payload_data_here')
sig = hmac.new(key, frame, hashlib.sha256).hexdigest()
print(f"Frame signature: {sig[:32]}...")
# memoryview — برش بدون کپی از یک بافر بزرگتر
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"
# HMAC فقط ۲۰ بایت اول بدون کپی
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"Sensor signature: {sig[:32]}...")اشتباهات رایج
دو اشتباه اول را در تقریباً هر بررسی کد مربوط به هندلرهای وبهوک میبینم. تحت فشار زمانی راحت وارد میشوند و بدون دانستن چه چیزی را باید دنبال کرد سخت تشخیص داده میشوند.
مشکل: عملگر == در اولین بایت نامطابق اتصال کوتاه میکند و اطلاعات زمانبندی را فاش میکند که به مهاجم اجازه میدهد امضای مورد انتظار را به تدریج بازسازی کند.
راهحل: همیشه از hmac.compare_digest() برای مقایسه امضا استفاده کنید — بدون توجه به محل عدم تطابق با زمان ثابت اجرا میشود.
received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if hmac.compare_digest(received_sig, expected_sig): # زمان ثابت
process_webhook(body)received_sig = request.headers["X-Signature"]
expected_sig = hmac.new(key, body, hashlib.sha256).hexdigest()
if received_sig == expected_sig: # آسیبپذیر به حمله زمانبندی
process_webhook(body)مشکل: hmac.new() به اشیاء bytes-like نیاز دارد. ارسال str در Python TypeError ایجاد میکند: "key: expected bytes or bytearray, but got 'str'".
راهحل: قبل از ارسال کلیدها و پیامهای رشتهای به hmac.new()، .encode() را روی آنها فراخوانی کنید.
key = "my_api_secret"
msg = '{"event":"test"}'
sig = hmac.new(key.encode(), msg.encode(), hashlib.sha256)key = "my_api_secret" # str، نه bytes
msg = '{"event":"test"}' # str، نه bytes
sig = hmac.new(key, msg, hashlib.sha256) # TypeError!مشکل: فراخوانی hmac.new(key, msg) بدون آرگومان سوم TypeError ایجاد میکند. قبل از Python 3.4 به طور پیشفرض MD5 بود، اما پیشفرض به دلایل امنیتی حذف شد.
راهحل: همیشه الگوریتم را به صراحت مشخص کنید: hashlib.sha256، hashlib.sha512، یا هر چیزی که پروتکل شما نیاز دارد.
# همیشه الگوریتم هش را مشخص کنید sig = hmac.new(key, msg, hashlib.sha256).hexdigest()
# digestmod مفقود است — در Python 3.4+ TypeError ایجاد میکند sig = hmac.new(key, msg).hexdigest()
مشکل: بسیاری از ارائهدهندگان وبهوک (Stripe، Shopify) امضاهای Base64 ارسال میکنند، نه هگز. مقایسه یک رشته هگز با یک مقدار Base64 همیشه شکست میخورد و باعث رد شدن تمام وبهوکها میشود.
راهحل: مستندات ارائهدهنده را برای فرمت امضا بررسی کنید. اگر از Base64 استفاده میکنند، bytes خام .digest() را رمزگذاری کنید، نه رشته .hexdigest() را.
import base64
# فرمت ارائهدهنده را مطابقت دهید: bytes خام → Base64
raw = hmac.new(key, body, hashlib.sha256).digest()
expected = base64.b64encode(raw).decode("ascii")
# "o/G5wE59..." — با هدر مطابقت دارد# ارائهدهنده Base64 ارسال میکند، اما ما هگز محاسبه میکنیم — هرگز مطابقت ندارد expected = hmac.new(key, body, hashlib.sha256).hexdigest() # "a3f1b9c0..." vs "o/G5wE59..." — همیشه عدم تطابق
stdlib hmac در مقابل cryptography — مقایسه سریع
از ماژول stdlib hmac برای تأیید وبهوک، امضای API، و عملیات HMAC عمومی استفاده کنید — هیچ وابستگیای نیاز ندارد و هر الگوریتم استاندارد را پوشش میدهد. از hmac.digest() برای عملیات دستهای که سرعت یکمرحلهای اهمیت دارد استفاده کنید. تنها زمانی به کتابخانه cryptography روی بیاورید که از قبل برای عملیات دیگر (TLS، X.509، رمزگذاری متقارن) به آن وابسته هستید و رابط .verify() مبتنی بر استثنا را میخواهید. برای بررسی سریع امضا بدون نوشتن Python، از ابزار مولد HMAC استفاده کنید تا کلید و پیام را جایگذاری کنید و نتیجه را فوری دریافت کنید.
سوالات متداول
تفاوت hmac.new() و hmac.digest() در Python چیست؟
hmac.new() یک شیء HMAC برمیگرداند که از فراخوانیهای تدریجی .update() پشتیبانی میکند و هم .digest() (bytes خام) و هم .hexdigest() (رشته هگز) را در اختیار میگذارد. hmac.digest() یک تابع یکمرحلهای است که در Python 3.7 اضافه شد و بدون ساختن شیء میانی، مستقیماً bytes خام را برمیگرداند. از hmac.digest() زمانی استفاده کنید که پیام کامل در دسترس است و فقط به نتیجه نیاز دارید. از hmac.new() زمانی استفاده کنید که باید داده را به صورت تکهتکه بدهید یا به خروجی هگز نیاز دارید.
import hmac, hashlib
key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'
# یکمرحلهای — bytes خام برمیگرداند
raw = hmac.digest(key, body, hashlib.sha256)
# مبتنی بر شیء — از بهروزرسانی تدریجی و خروجی هگز پشتیبانی میکند
h = hmac.new(key, body, hashlib.sha256)
hex_sig = h.hexdigest()چطور میتوان یک امضای HMAC را در Python تأیید کرد؟
HMAC را روی پیام اصلی با استفاده از کلید مشترک محاسبه مجدد کنید، سپس با hmac.compare_digest() مقایسه نمایید. هرگز از == برای مقایسه امضا استفاده نکنید. عملگر == به دلیل اتصال کوتاه در اولین بایت نامطابق، اطلاعاتی درباره طول و محتوای امضای مورد انتظار فاش میکند و در برابر حملات زمانبندی آسیبپذیر است.
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-SHA256 در Python همان هش hashlib.sha256 است؟
خیر. hashlib.sha256 یک هش SHA-256 ساده از ورودی محاسبه میکند که هر کسی میتواند آن را بازتولید کند. HMAC-SHA256 طبق RFC 2104 یک کلید محرمانه را در محاسبه هش ترکیب میکند، بنابراین تنها کسی که کلید را دارد میتواند خروجی صحیح را تولید یا تأیید کند. هش ساده صحت داده را اثبات میکند. HMAC هم صحت و هم اصالت را اثبات میکند.
import hmac, hashlib msg = b"transfer:9950:USD" key = b"api_secret_k8x2" plain_hash = hashlib.sha256(msg).hexdigest() # هر کسی میتواند این را محاسبه کند hmac_hash = hmac.new(key, msg, hashlib.sha256).hexdigest() # نیاز به کلید دارد
آیا میتوان از HMAC-SHA1 در Python 3 استفاده کرد؟
بله، hashlib.sha1 را به عنوان آرگومان digestmod ارسال کنید. HMAC-SHA1 در Python 3 همچنان کار میکند و ماژول hmac هیچ هشدار منسوخی برای آن ندارد. با این حال، SHA-1 برای پروژههای جدید ضعیف در نظر گرفته میشود — مقاومت برخورد آن زیر ۸۰ بیت است و NIST در سال ۲۰۱۵ آن را برای اکثر کاربردهای امضای دیجیتال منسوخ اعلام کرد. دلیل اصلی استفاده از HMAC-SHA1 امروزه سازگاری با پروتکلهای موجودی است که آن را الزامی میدانند، مانند OAuth 1.0a یا سیستمهای قدیمی وبهوک. وقتی هر دو طرف یکپارچهسازی را کنترل میکنید، برای تمام کارهای جدید HMAC-SHA256 یا HMAC-SHA512 را ترجیح دهید.
import hmac, hashlib key = b"oauth_consumer_secret" base_string = b"GET&https%3A%2F%2Fapi.example.com&oauth_nonce%3Dabc123" sig = hmac.new(key, base_string, hashlib.sha1).digest()
چطور میتوان یک کلید HMAC امن در Python تولید کرد؟
از secrets.token_bytes() از کتابخانه استاندارد استفاده کنید. برای HMAC-SHA256، کلید ۳۲ بایتی توصیه استاندارد است چون با اندازه بلوک هش مطابقت دارد. برای HMAC-SHA512، از ۶۴ بایت استفاده کنید. از random.random() یا os.urandom() برای تولید کلید در کد برنامه استفاده نکنید — ماژول secrets از Python 3.6 ماژول صحیح برای تصادفیسازی حساس به امنیت است.
import secrets hmac_sha256_key = secrets.token_bytes(32) # ۲۵۶ بیت hmac_sha512_key = secrets.token_bytes(64) # ۵۱۲ بیت # به صورت هگز برای فایلهای پیکربندی ذخیره کنید print(hmac_sha256_key.hex()) # مثلاً "a3f1b9c04e..."
چرا hmac.new() در Python 3 به digestmod نیاز دارد؟
قبل از Python 3.4، digestmod به طور پیشفرض MD5 بود که از نظر رمزنگاری شکسته شده — MD5 دارای حملات برخورد شناختهشده است و هرگز نباید در کد حساس به امنیت جدید استفاده شود. نگهدارندگان Python پیشفرض را حذف کردند تا انتخاب صریح الگوریتم را الزامی کنند و از ارسال ناخواسته MAC مبتنی بر MD5 جلوگیری شود. اگر hmac.new(key, msg) را بدون digestmod فراخوانی کنید، TypeError دریافت میکنید. همیشه الگوریتم را به صراحت مشخص کنید: hashlib.sha256، hashlib.sha512، یا هر سازنده hashlib دیگری. در صورت تردید، hashlib.sha256 انتخاب امن است — هیچ آسیبپذیری شناختهشدهای ندارد و برای هر بار کاری عملی به اندازه کافی سریع است.
import hmac, hashlib key = b"secret" msg = b"data" # این در Python 3.4+ خطای TypeError ایجاد میکند # hmac.new(key, msg) # TypeError: missing required argument: 'digestmod' # همیشه الگوریتم را مشخص کنید h = hmac.new(key, msg, hashlib.sha256)
برای بررسی سریع HMAC بدون راهاندازی یک اسکریپت Python، کلید و پیام را در مولد آنلاین HMAC جایگذاری کنید — از SHA-256، SHA-384، و SHA-512 با نتایج فوری پشتیبانی میکند.
ابزارهای مرتبط
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.