HMAC في Python — دليل hmac.new() SHA-256 + أمثلة كود
استخدم مولّد HMAC المجاني مباشرةً في متصفحك — لا حاجة للتثبيت.
جرّب مولّد HMAC أونلاين ←كل استدعاء webhook وارد، وكل طلب 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، التحقق من webhooks، توقيع طلبات 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 — يُعيد بايتات خام دون كائن وسيط.
- ✓تحقق دائماً من التوقيعات باستخدام hmac.compare_digest() لمنع هجمات التوقيت — لا تستخدم == أبداً لمقارنة HMAC.
- ✓شفّر مخرجات .digest() الخام إلى Base64 لترويسات HTTP وتوقيعات webhooks: base64.b64encode(h.digest()).
- ✓تقبل وحدة hmac أي خوارزمية hashlib: sha1، sha256، sha384، sha512، md5، blake2b.
ما هو HMAC؟
HMAC (رمز مصادقة الرسائل المبني على التجزئة) هو بنية محددة في RFC 2104 تجمع مفتاحاً سرياً مع دالة تجزئة لإنتاج علامة مصادقة ذات حجم ثابت. خلافاً للتجزئة العادية (التي يمكن لأي شخص حسابها)، يتطلب HMAC معرفة المفتاح السري. هذا يعني أنه يمكن استخدامه للتحقق من سلامة الرسالة وأصالتها معاً. حتى تغيير بايت واحد في الرسالة أو المفتاح ينتج مخرجاً مختلفاً كلياً. تعمل البنية بخلط المفتاح مع ثابتَي حشو مختلفَين (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 hmac, hashlib. الدوال الرئيسية الثلاث هي hmac.new() (تُنشئ كائن HMAC)، hmac.digest() (دفعة واحدة، Python 3.7+)، و hmac.compare_digest() (مقارنة في وقت ثابت). يكفيك سطرَيْ استيراد للبدء، لا حاجة لـ pip install.
hmac.new(key, msg, digestmod) تأخذ ثلاث وسيطات. يجب أن يكون كلٌّ من key و msg كائنات شبيهة بـ bytes ( 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() تُعيد بايتات خام (32 بايتاً لـ SHA-256، و64 لـ 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 # تمثّلان البيانات ذاتها — السداسية مجرد ترميز نصي للبايتات assert raw_bytes.hex() == hex_string
إذا كان مفتاحك أو رسالتك سلسلة Python نصية، استدعِ .encode() لتحويلها إلى bytes قبل تمريرها إلى hmac.new(). هذا الخطأ يقع فيه الجميع تقريباً في المرة الأولى — نصوص Python 3 هي Unicode وليست bytes، ووحدة hmac ترفضها.
import hmac
import hashlib
# مفتاح ورسالة نصيتان — .encode() يحوّلهما إلى بايتات 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. قبل 3.4، كان يُعيَّن افتراضياً إلى MD5، ولهذا السبب أزاله مطورو Python — لإجبارك على اختيار صريح وآمن.HMAC-SHA256 بـ Base64، و SHA-1، و SHA-512، و MD5
تعمل دالة hmac.new() مع أي خوارزمية تجزئة متاحة في hashlib. معظم مزودي webhooks وبوابات API يستخدمون HMAC-SHA256، لكنك ستصادف SHA-1 في OAuth 1.0a، وSHA-512 في البروتوكولات التي تفرضه، وMD5 في الأنظمة القديمة التي لم تُحدَّث.
HMAC-SHA256 بمخرجات Base64
يُرسل كثير من مزودي webhooks التوقيع كسلسلة مشفّرة بـ Base64 في ترويسة HTTP. لإنتاج الصيغة ذاتها، مرّر بايتات .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 وتوقيعات webhooks)
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 وبعض تطبيقات webhooks القديمة. الكود متطابق — فقط بدّل الخوارزمية.
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..." — شفّره بـ URL لترويسة AuthorizationHMAC-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) # سلسلة سداسية من 32 حرفاً # "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
مرجع معاملات hmac.new()
توقيع المنشئ هو hmac.new(key, msg=None, digestmod). تشترك الدوال الثلاث في الوحدة في النمط ذاته لوسيطتَي المفتاح والخوارزمية.
منشئ hmac.new()
hmac.digest() لدفعة واحدة (Python 3.7+)
يقبل معامل digestmod إما دالة قابلة للاستدعاء (مثل hashlib.sha256) أو اسماً نصياً (مثل "sha256"). يُفضَّل الشكل القابل للاستدعاء لأنه يُتحقق منه عند الاستيراد — خطأ إملائي في الشكل النصي لا ينكشف إلا عند التشغيل.
hmac.digest() — HMAC سريع لدفعة واحدة (Python 3.7+)
أضافت Python 3.7 hmac.digest(key, msg, digest) كدالة على مستوى الوحدة. تحسب HMAC في استدعاء واحد دون إنشاء كائن HMAC وسيط. القيمة المُعادة بايتات خام (تعادل استدعاء .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() تُعيد بايتات خام فقط. إذا احتجت إلى السلسلة السداسية مباشرةً، لا تزال بحاجة إلى hmac.new() لاستخدام .hexdigest()، أو ربط .hex() على نتيجة البايتات.
hmac.digest() استدعاءات .update() التدريجية. إذا كنت تقرأ ملفاً كبيراً في أجزاء وتحتاج إلى حساب HMAC للمحتوى، استخدم hmac.new() واستدعِ .update(chunk) في حلقة.التحقق من توقيع HMAC من Webhook وطلب API
أكثر استخدامات HMAC شيوعاً في Python هو التحقق من توقيعات webhooks. كل مزود رئيسي (Stripe، GitHub، Shopify، Twilio) يوقّع الحمولات بـ HMAC-SHA256 ويُرسل التوقيع في ترويسة. النمط دائماً هو ذاته: أعِد حساب HMAC على جسم الطلب الخام، ثم قارن باستخدام hmac.compare_digest().
التحقق من توقيع 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():
# الجسم الخام — يجب أن يطابق تماماً ما جرى توقيعه
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, "توقيع غير صالح")
# التوقيع صالح — معالجة الحدث
event = request.get_json()
print(f"حدث مُتحقق منه: {event['type']} بمبلغ {event['data']['amount']}")
return "", 200تُقارن دالة hmac.compare_digest() سلسلتَين أو تسلسلَي bytes في وقت ثابت. مقارنة == العادية تتوقف عند أول بايت مختلف. يمكن للمهاجم قياس زمن الاستجابة عبر طلبات متعددة وإعادة بناء التوقيع المتوقع بايتاً بايتاً. المقارنة في وقت ثابت تُلغي هذه القناة الجانبية.
التحقق من GitHub Webhook
يُوضّح صيغة webhook الخاصة بـ GitHub النمط الكامل. يُرسل ترويسة X-Hub-Signature-256 تحتوي على sha256= يعقبها HMAC-SHA256 المُشفَّر سداسياً لجسم الطلب الخام، موقَّعاً بالسر الذي تُعدّه في إعدادات مستودعك. الفارق الرئيسي عن التحقق العام من webhooks هو أنك تجب إزالة البادئة sha256= قبل المقارنة، ويجب قراءة البايتات الخام لجسم الطلب — تحليل JSON أولاً يُغيّر تمثيل البايتات ويُفسد التحقق.
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, "ترويسة التوقيع غائبة أو مشوّهة")
received_hex = sig_header[len("sha256="):]
raw_body = request.get_data() # بايتات خام — لا تُحلّل JSON قبل هذا
expected_hex = hmac.new(
GITHUB_WEBHOOK_SECRET, raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected_hex, received_hex):
abort(403, "عدم تطابق التوقيع — الحمولة قد تكون عُبث بها")
event_type = request.headers.get("X-GitHub-Event", "unknown")
payload = request.get_json()
print(f"حدث GitHub مُتحقق منه {event_type}: {payload.get('action', '')}")
return "", 200ينطبق النمط ذاته على Shopify (X-Shopify-Hmac-SHA256) وTwilio (X-Twilio-Signature)، مع الاختلاف الوحيد في اسم الترويسة وما إذا كان التوقيع سداسياً أم مشفّراً بـ Base64. تحقق دائماً من توثيق المزود لتأكيد صيغة الترميز — الخلط بين السداسي وBase64 هو أكثر أسباب عدم تطابق التوقيعات شيوعاً.
مصادقة طلبات API بـ HMAC
تتطلب بعض APIs من العميل توقيع كل طلب بـ 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"فشل الطلب الموقَّع: {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() يُضيف مسافات بعد الفواصل مما يُغيّر تمثيل البايتات ويُفسد التحقق إذا سلسل الخادم بشكل مختلف. JSON المضغوط يمنحك صيغة متعارفاً عليها.
توليد HMAC من سطر الأوامر
أحياناً تحتاج إلى حساب HMAC دون كتابة سكريبت. خيار -c في Python و openssl يعالجان هذا من الطرفية.
python3 -c " import hmac, hashlib print(hmac.new(b'my_secret', b'message_to_sign', hashlib.sha256).hexdigest()) " # المخرجات: سلسلة سداسية من 64 حرفاً
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() القائمة على الاستثناءات — تُثير استثناءً عند عدم التطابق بدلاً من إعادة قيمة منطقية قد تُتجاهل.
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() # بايتات خام
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 أنها تُثير استثناءً عند عدم التطابق بدلاً من إعادة قيمة منطقية. هذا يجعل تجاهل فشل التحقق أصعب. تُعيد 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 تدريجي
للملفات التي يتجاوز حجمها 50 ميغابايت تقريباً، تحميل كل شيء في الذاكرة لحساب 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"تعذّر قراءة الملف '{filepath}': {e}") from e
return h.hexdigest()
# توقيع نسخة احتياطية لقاعدة بيانات بحجم 2 غيغابايت
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)
# التحقق من حزمة مُحمَّلة
is_valid = verify_file_hmac(
key=b"release_signing_key",
filepath="/tmp/release-v3.2.0.tar.gz",
expected_hex="8e3a1f9b2c4d6e7f0a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f",
)
print(f"سلامة الملف: {'صالح' if is_valid else 'تالف'}").update() مقداراً ثابتاً من الذاكرة مقدره chunk_size بغض النظر عن حجم الملف. أفضّل أجزاء 64 كيلوبايت — كبيرة بما يكفي لاستهلاك تكلفة استدعاء النظام، وصغيرة بما يكفي للبقاء داخل ذاكرة التخزين المؤقت L2 على معظم الأجهزة.توليد مفتاح HMAC آمن تشفيرياً في Python
مفتاح ضعيف أو قابل للتنبؤ يُقوّض بنية HMAC بالكامل. وحدة secrets (Python 3.6+) توفر بايتات عشوائية قوية تشفيرياً. لـ HMAC-SHA256، استخدم مفتاحاً من 32 بايتاً. لـ HMAC-SHA512، استخدم 64 بايتاً. هذه تطابق حجم الكتلة الداخلية لخوارزميات التجزئة المقابلة، وهو الطول الأمثل للمفتاح وفق RFC 2104.
import secrets
# توليد مفاتيح تطابق حجم كتلة خوارزمية التجزئة
sha256_key = secrets.token_bytes(32) # 256 بتاً — لـ HMAC-SHA256
sha512_key = secrets.token_bytes(64) # 512 بتاً — لـ HMAC-SHA512
# التمثيل السداسي — آمن لملفات الإعداد ومتغيرات البيئة
print(f"مفتاح HMAC-SHA256: {sha256_key.hex()}")
# مثال: "a3f1b9c04e7d2f8a1b3c5d7e9f0a2b4c6d8e0f1a2b3c4d5e6f7a8b9c0d1e2f"
print(f"مفتاح HMAC-SHA512: {sha512_key.hex()}")
# سلسلة سداسية من 128 حرفاً
# Base64 آمن لـ URL — مضغوط وآمن لترويسات HTTP
import base64
b64_key = base64.urlsafe_b64encode(sha256_key).decode("ascii")
print(f"مفتاح Base64: {b64_key}")
# مثال: "o_G5wE59L4obPF1-nwortG2ODwobPExdXnqLnA0dLi8="random.random() أو random.randbytes() من وحدة random لمفاتيح HMAC. تستخدم وحدة random الافتراضية مولّد Mersenne Twister للأرقام العشوائية الذي يمكن التنبؤ به بعد رصد 624 مخرجاً. استخدم دائماً secrets.token_bytes() للعشوائية الحساسة أمنياً.طول المفتاح ومتطلبات RFC 2104
يحدد RFC 2104 أن مفتاح HMAC يمكن أن يكون بأي طول، لكنه يوصي بمفتاح لا يقل عن L بايتاً — حيث L هو طول مخرجات دالة التجزئة الأساسية. لـ HMAC-SHA256 هذا يعني 32 بايتاً (256 بتاً). المفاتيح الأقصر من L بت تُقلّل هامش الأمان بشكل متناسب. المفاتيح الأطول من حجم كتلة الخوارزمية (64 بايتاً لـ SHA-256، و128 بايتاً لـ SHA-512) تُجزَّأ أولاً إلى حجم الكتلة، لذا لا فائدة من مفاتيح أطول من حجم الكتلة. التزم بـ 32 بايتاً لـ HMAC-SHA256 و64 بايتاً لـ 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 لمعاملَي المفتاح والرسالة. يعني هذا أنه يمكنك تمرير bytes، bytearray، أو memoryview مباشرةً دون نسخ أو تحويل. هذا مهم بشكل خاص في سيناريوَين: تطبيقات بروتوكول الشبكة حيث تكتب socket.recv_into() البيانات في مُخزَّن bytearray مُخصَّص مسبقاً، والأنظمة عالية الإنتاجية حيث تجنّب النسخ الوسيطة يُقلّل ضغط جامع المهملات. شريحة memoryview نسخها صفر: تكشف نافذة على المُخزَّن الأصلي دون تخصيص ذاكرة جديدة. عند عشرات الآلاف من الرسائل في الثانية، إزالة هذه التخصيصات تُحدث فرقاً قياساً في زمن الاستجابة والإنتاجية.
import hmac
import hashlib
# bytearray — بايتات قابلة للتعديل، مفيدة لمخزّنات بروتوكول ثنائي
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"توقيع الإطار: {sig[:32]}...")
# memoryview — شريحة بدون نسخ من مُخزَّن أكبر
large_buffer = bytearray(4096)
large_buffer[:20] = b"sensor_reading_12345"
# حساب HMAC للـ 20 بايت الأولى فقط دون نسخ
view = memoryview(large_buffer)[:20]
sig = hmac.new(key, view, hashlib.sha256).hexdigest()
print(f"توقيع المستشعر: {sig[:32]}...")الأخطاء الشائعة
الخطآن الأولان يظهران في كل مراجعة كود تقريباً تخص معالجات webhooks. من السهل الوقوع فيهما تحت ضغط الوقت ومن الصعب اكتشافهما دون معرفة ما تبحث عنه.
المشكلة: عامل == يتوقف عند أول بايت مختلف، مُسرِّباً معلومات توقيت تُمكّن المهاجم من إعادة بناء التوقيع المتوقع تدريجياً.
الحل: استخدم دائماً 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. تمرير str في Python يُثير TypeError: "key: expected bytes or bytearray, but got 'str'".
الحل: استدعِ .encode() على المفاتيح والرسائل النصية قبل تمريرها إلى hmac.new().
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 غائب — يُثير TypeError في Python 3.4+ sig = hmac.new(key, msg).hexdigest()
المشكلة: يُرسل كثير من مزودي webhooks (Stripe، Shopify) توقيعات مشفّرة بـ Base64 لا سداسية. مقارنة سلسلة سداسية بقيمة Base64 تفشل دائماً، مما يتسبب في رفض كل webhooks.
الحل: تحقق من توثيق المزود لصيغة التوقيع. إذا استخدموا Base64، شفّر بايتات .digest() الخام وليس سلسلة .hexdigest().
import base64
# طابق صيغة المزود: بايتات خام → 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
استخدم وحدة hmac القياسية للتحقق من webhooks وتوقيع API والعمليات العامة — لا تعتمد على أي تبعيات وتغطي كل خوارزمية معيارية. استخدم hmac.digest() للعمليات الدفعية حيث تهم سرعة الدفعة الواحدة. الجأ إلى مكتبة cryptography فقط حين تعتمد عليها أصلاً لعمليات أخرى (TLS، X.509، تشفير متماثل) وتريد واجهة .verify() القائمة على الاستثناءات. للتحقق السريع من التوقيعات دون كتابة أي Python، استخدم أداة مولّد HMAC لصق مفتاحك ورسالتك والحصول على النتيجة فوراً.
الأسئلة الشائعة
ما الفرق بين hmac.new() و hmac.digest() في Python؟
تُعيد hmac.new() كائن HMAC يدعم استدعاءات .update() التدريجية ويوفر كلاً من .digest() (بايتات خام) و .hexdigest() (سلسلة سداسية). أما hmac.digest() فهي دالة أُضيفت في Python 3.7 تُعيد البايتات الخام مباشرةً دون إنشاء كائن وسيط. استخدم hmac.digest() حين تملك الرسالة كاملةً وتحتاج فقط إلى النتيجة. استخدم hmac.new() حين تحتاج إلى تغذية البيانات في أجزاء أو تحتاج إلى مخرجات سداسية.
import hmac, hashlib
key = b"webhook_secret_2026"
body = b'{"event":"payment.completed","amount":9950}'
# دفعة واحدة — تُعيد بايتات خام
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 ضعيفاً للتصاميم الجديدة — مقاومته للتصادمات أقل من 80 بتاً وأهمله NIST لمعظم استخدامات التوقيع الرقمي عام 2015. السبب الرئيسي لاستخدام HMAC-SHA1 اليوم هو التوافق مع البروتوكولات القائمة التي تفرضه، كـ OAuth 1.0a أو بعض أنظمة webhook القديمة. حين تتحكم في الطرفين، فضّل 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، مفتاح من 32 بايتاً هو التوصية المعيارية لأنه يطابق حجم كتلة خوارزمية التجزئة. لـ HMAC-SHA512، استخدم 64 بايتاً. لا تستخدم random.random() أو أي دالة من وحدة random لتوليد المفاتيح — هي مولّد أرقام شبه عشوائية وليست آمنة تشفيرياً. secrets هي الواجهة الموصى بها منذ Python 3.6، وتعتمد داخلياً على os.urandom().
import secrets hmac_sha256_key = secrets.token_bytes(32) # 256 بتاً hmac_sha512_key = secrets.token_bytes(64) # 512 بتاً # تخزينها كـ hex لملفات الإعداد print(hmac_sha256_key.hex()) # مثال: "a3f1b9c04e..."
لماذا تتطلب hmac.new() تحديد digestmod في Python 3؟
قبل Python 3.4، كان digestmod يُعيّن افتراضياً إلى MD5 المكسورة تشفيرياً — إذ تعاني MD5 من هجمات تصادم معروفة ولا ينبغي استخدامها أبداً في كود حساس أمنياً. أزال مطورو Python الافتراضي لإجبار المطورين على اختيار خوارزمية صريحة، تفادياً لشحن MACs مبنية على MD5 دون علم. إذا استدعيت hmac.new(key, msg) بدون digestmod ستحصل على TypeError. حدد دائماً الخوارزمية صراحةً: hashlib.sha256، أو hashlib.sha512، أو أي منشئ hashlib آخر. عند الشك، hashlib.sha256 هو الاختيار الآمن — لا ثغرات معروفة وسرعة كافية لأي حمل عملي.
import hmac, hashlib key = b"secret" msg = b"data" # هذا يُثير TypeError في Python 3.4+ # 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.