مولّد HMAC وفاحص التواقيع

يحسب HMAC-SHA-1 و‑SHA-256 و‑SHA-384 و‑SHA-512 من رسالة ومفتاح، ويحدّد من أيها جاء توقيع معيّن.

الرسالة
HMAC-SHA-1
HMAC-SHA-256
HMAC-SHA-384
HMAC-SHA-512

أدخل مفتاحًا لحساب التواقيع.

ما الذي تفعله هذه الأداة

يحوّل HMAC رسالةً وسرًّا مشتركًا إلى توقيع قصير. وكل من يملك السر نفسه يستطيع إعادة حسابه ليرى هل وصلت الرسالة دون تغيير وهل جاءت ممن يعرف السر أيضًا. وتحسب هذه الأداة الصيغ الأربع الشائعة دفعةً واحدة، وتخبرك — أمام توقيع وصلك — أيها أنتجه.

وهذا الاتجاه الثاني هو عادةً سبب وصول الناس إلى هنا. فيفشل تحقّق webhook، ويكون السؤال الحقيقي لا «هل هذا التوقيع صالح» بل «أي الأشياء المعقولة العديدة أفعله على نحو يخالف المرسِل».

المفتاح هو موضع الخلل

يوقّع HMAC بايتات ببايتات. والرسالة بديهية عادةً — جسم الطلب الخام — أما المفتاح فلا يكاد يكون كذلك، لأن السر يصل سلسلةً نصية، والسلسلة ليست بايتات حتى تقرّر كيف تقرؤها:

a3f2  ->  61 33 66 32   4 bytes   نصًا
a3f2  ->  a3 f2         2 bytes   ست عشريًا

كلتا القراءتين مشروعة، وكلتاهما تنتج توقيعًا سليم البنية تمامًا، وليس بين التوقيعين شيء مشترك. ولا شيء ينبّهك: لا خطأ ولا شكوى من الطول، بل قيمة لا تطابق ما حسبه المرسِل. ولهذا فترميز المفتاح هنا اختيار ظاهر لا تخمين — فحين لا يطابق توقيع، هذا أول ما ينبغي تبديله.

وكدليل تقريبي: السر الذي يحمل بادئة مثل whsec_ أو سلسلة من حروف كبيرة وصغيرة وأرقام يُقصد عادةً نصًا؛ والسلسلة المكوّنة من 32 أو 64 محرفًا بالضبط ولا تستعمل سوى 0-9 و‑a-f تكون عادةً ست عشرية؛ والمنتهية بـ‏=‏ هي شبه المؤكد Base64. لكن راجع توثيق المرسِل بدل الشكل، فالشكل ليس دليلًا.

الرسالة يجب أن تكون البايتات بالضبط

النصف الآخر من عدم التطابق هو الرسالة. فـHMAC معرّف فوق البايتات، ومن ثم فإن أي شيء يغيّر البايتات يغيّر التوقيع كليًا — فلا درجة جزئية ولا اقتراب.

  • إعادة تسلسل JSON. فتحليل جسم وإعادة توليده قد يعيد ترتيب المفاتيح أو يغيّر المسافات أو يوحّد صيغة الأرقام. وقّع وتحقّق من الجسم الخام الذي استقبلته، لا من نسخة ذهبت وعادت.
  • سطر جديد في النهاية. تضيفه بعض الأدوات عند حفظ الجسم في ملف؛ وهو بايت، وهو يغيّر كل شيء.
  • ترميز المحارف. الجسم الذي يحوي نصًا خارج ASCII يجب أن يُقرأ بالترميز نفسه في الطرفين — وعمليًا UTF-8.
  • الضغط أو الوسيطات البرمجية. فإن كان شيء أمام معالِجك يفك الضغط أو يعيد كتابة الجسم، فتحقّق قبل حدوث ذلك لا بعده.

كذلك لا يوقّع كثير من المزوّدين الجسم وحده. فـStripe توقّع طابعًا زمنيًا والجسم موصولين بنقطة؛ و‑AWS توقّع طلبًا قانونيًا مبنيًا من الطريقة والمسار والترويسات وتجزئة الحمولة. فإن فشل التحقّق أمام الجسم المجرد، فالسلسلة المُوقَّعة غالبًا ليست الجسم المجرد — وذلك موثّق عند المرسِل ويستحق القراءة قبل مواصلة التنقيح.

لماذا يُعرض SHA-1 هنا ولا يُعرض في أداة التجزئة

تَسِم أداة التجزئة SHA-1 بأنه مكسور. وهذه الأداة تُدرج HMAC-SHA-1 دون تحذير، وذلك متعمَّد لا سهو.

فـSHA-1 لا يصلح للتواقيع والشهادات لأن التصادمات يمكن بناؤها: إذ يمكن جعل مستندين مختلفين يتشاركان التجزئة نفسها. أما HMAC فلا يعتمد على تلك الخاصية. فأمنه يقوم على المفتاح السري، والبنية — تجزئة الرسالة مرتين والمفتاح ممزوج في المرتين — تصمد حتى حين يكون الهاش الأساسي ضعيفًا أمام التصادم. ويبقى HMAC-SHA-1 سليمًا وهو ما يستعمله OAuth 1.0a وتوقيع AWS القديم، ولذا فأداة ترفض حسابه كانت ستكون أقل نفعًا فحسب دون أن تكون أكثر أمانًا.

وأما لكل جديد فـSHA-256 هو الخيار المعقول. فالبصمات الأطول ليست أقوى بقدر يُذكر هنا — إذ سقف الأمان هو المفتاح لا طول البصمة — ومن ثم لا يستحق SHA-384 و‑SHA-512 الاختيار إلا حين يطلبهما شيء عليك التعامل معه.

مقارنة التواقيع بأمان

تقارن هذه الصفحة بمقارنة سلاسل عادية، وهذا مقبول هنا: فالمفتاح بين يديك، ولا شيء يتسرّب. أما في خادوم يتحقّق من طلب وارد فغير مقبول. إذ تتوقف المقارنة العادية عند أول بايت مختلف، فيكشف الزمن الذي تستغرقه أي جزء من التخمين كان صحيحًا، ومن يستطيع إرسال طلبات كثيرة يستعيد توقيعًا صالحًا بايتًا ببايت.

استعمل المقارنة ثابتة الزمن التي توفرها منصتك — ‏crypto.timingSafeEqual في Node، و‑hmac.compare_digest في Python، و‑hash_equals في PHP — على البايتات الخام لا على سلاسل ست عشرية. وإلى جانب ذلك: قارِن بتوقيع حسبته أنت بدل الوثوق بخوارزمية مسمّاة داخل الطلب، وارفض رسالةً طابعها الزمني بعيد عن الآن كي لا يُعاد تشغيل توقيع صالح قديم.

HMAC ليس تجزئة ولا توقيعًا رقميًا

تختلط هنا ثلاثة أمور، والفرق مهم لما يمكن ادعاؤه بعد ذلك:

  • التجزئة تأخذ رسالةً وتنتج بصمة. وكل أحد يستطيع حسابها، فهي تثبت أن الرسالة لم تتغير عرضًا — لا أنها جاءت من أحد بعينه.
  • و‑HMAC يأخذ رسالةً وسرًّا مشتركًا. والطرفان يملكان المفتاح نفسه، فهو يثبت أن المرسِل كان يعرف السر. ولا يستطيع إثبات أي الطرفين أرسل، لأن كليهما كان قادرًا على إنتاجه.
  • والتوقيع يستعمل مفتاحًا خاصًا لا يملكه إلا المرسِل، ومفتاحًا عامًا يستطيع أي أحد التحقق به. وهذا ما يمنح عدم الإنكار: فلا يستطيع المرسِل التنصّل منه لاحقًا.

فـHMAC إذن هو الأداة الصحيحة بين نظامين يتشاركان سرًّا أصلًا — الـwebhooks وواجهات API الداخلية ورموز الجلسات — والخاطئة حين تحتاج إثبات من أرسل شيئًا لطرف ثالث. ولاحظ أيضًا أنه يوثّق ولا يخفي: فالرسالة تسافر مكشوفة، ولا يقول HMAC شيئًا عن السرية.

الأسئلة الشائعة

توقيعي لا يطابق. بماذا أبدأ الفحص؟
بترميز المفتاح، ثم ببايتات الرسالة. فالسر الذي يبدو ست عشريًا كثيرًا ما يُقصد ست عشريًا لا نصًا، وكلاهما ينتج توقيعين مختلفين تمامًا دون أي خطأ من أي جهة. ثم تأكّد أنك توقّع الجسم الخام كما ورد لا نسخةً أُعيد تسلسلها.
لماذا يُعرض SHA-1 هنا مع أن أداة التجزئة تقول إنه مكسور؟
لأن HMAC لا يعتمد على مقاومة التصادم، وهي الخاصية التي فقدها SHA-1. فأمنه يأتي من المفتاح. ولا يزال HMAC-SHA-1 سليمًا ومستعملًا في OAuth 1.0a وتوقيع AWS القديم، وإن كان SHA-256 هو المبدئي الصحيح لكل جديد.
هل البصمة الأطول أكثر أمانًا؟
ليست كذلك بقدر يُذكر. فقوة HMAC محدودة بالسر لا بطول البصمة، ومن ثم فـSHA-512 ليس أفضل أربع مرات من SHA-256. اختر ما يتوقعه الطرف الآخر.
ما الفرق بين HMAC والتوقيع الرقمي؟
يستعمل HMAC سرًّا يعرفه الطرفان، فيثبت أن المرسِل كان يعرف السر لا أي الطرفين أرسل. أما التوقيع الرقمي فيستعمل مفتاحًا خاصًا لا يملكه إلا المرسِل، فيستطيع طرف ثالث التحقق منه ولا يستطيع المرسِل إنكاره. فإن احتجت هذه الخاصية الأخيرة فـHMAC هو الأداة الخاطئة.
هل أقارن التواقيع بـ ===؟
ليس في خادوم. فالمقارنة العادية تعود بمجرد اختلاف البايتات، وذلك الزمن يكشف أي جزء من توقيع مخمَّن كان صحيحًا. استعمل المقارنة ثابتة الزمن في منصتك على البايتات الخام. أما في هذه الصفحة فلا يهم، لأن المفتاح عندك أصلًا.
هل يُرسَل مفتاحي إلى أي مكان؟
لا. كل شيء يُحسب في متصفحك عبر Web Crypto API؛ ولا تغادر الرسالة ولا المفتاح جهازك أبدًا.