التجزئة وسلامة البيانات
كيف نكشف أن بايتًا واحدًا تغيّر، ونثبت أن الرسالة ممن نثق به؟ ⏱ نحو 45 دقيقة.
- تشرح خصائص دالة التجزئة والفرق عن التشفير.
- تستخدم
HMACلإثبات السلامة والأصل. - تقارن الأسرار بمقارنة ثابتة الزمن.
- تختار الدالة المناسبة لكل مهمة.
١التجزئة: بصمة أحادية الاتجاه
دالة التجزئة تحوّل أي بيانات إلى بصمة ثابتة الطول، بثلاث خصائص: أحادية (لا تُعكس إلى الأصل)، وحتمية (المدخل نفسه يعطي البصمة نفسها)، وحسّاسة (تغيّر حرف يقلب البصمة كلها).
const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const crypto = require('crypto'); app.get('/hash', (req, res) => { const data = req.query.data || ''; res.json({ sha256: crypto.createHash('sha256').update(data).digest('hex'), });}); app.get('/hmac', (req, res) => { const key = 'shared-secret'; const msg = req.query.msg || ''; const tag = crypto.createHmac('sha256', key).update(msg).digest('hex'); res.json({ msg, tag }); // العلامة تثبت أن الرسالة من يملك المفتاح ولم تُعبث}); app.listen(3000);
حرف واحد اختلف (r كبيرة) فتغيّرت البصمة تمامًا. هذا «أثر الانهيار»: يجعل التجزئة مثاليّة للكشف عن أي تعديل.
| التجزئة Hash | التشفير Encryption | |
|---|---|---|
| الاتجاه | أحادي (لا يُعكس) | ثنائي (يُفكّ بالمفتاح) |
| المفتاح | لا يلزم (أو مفتاح في HMAC) | يلزم |
| الاستخدام | بصمة، سلامة، كلمات المرور | إخفاء المحتوى |
تذكّر الفرق: التجزئة تُنتج بصمة لا تُعكس؛ والتشفير يُخفي محتوى يُستعاد لاحقًا. خلطهما خطأ شائع.
٢HMAC: سلامة وأصل بمفتاح
التجزئة وحدها تكشف التعديل العشوائي، لكن مهاجمًا يغيّر الرسالة ويعيد حساب بصمتها. HMAC يحلّ هذا: تجزئة بمفتاح سرّي مشترك، فلا يحسب العلامة الصحيحة إلا من يملك المفتاح:
const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const crypto = require('crypto'); app.get('/hash', (req, res) => { const data = req.query.data || ''; res.json({ sha256: crypto.createHash('sha256').update(data).digest('hex'), });}); app.get('/hmac', (req, res) => { const key = 'shared-secret'; const msg = req.query.msg || ''; const tag = crypto.createHmac('sha256', key).update(msg).digest('hex'); res.json({ msg, tag }); // العلامة تثبت أن الرسالة من يملك المفتاح ولم تُعبث}); app.listen(3000);
لكل رسالة علامة HMAC. المهاجم الذي يبدّل 500 إلى 5000 لا يعرف المفتاح، فلا ينتج العلامة الصحيحة. المتلقّي يحسب العلامة ويقارن: إن اختلفت، رُفضت الرسالة.
| يقدّم HMAC | المعنى |
|---|---|
| السلامة | لم تُعدَّل الرسالة |
| الأصل | من يملك المفتاح هو من أرسلها |
يُستخدم HMAC في توقيع JWT (HS256 هو HMAC-SHA256)، وفي روابط موقّعة، وفي التحقق من الرسائل بين خدمتين تتشاركان مفتاحًا.
٣قارن بأمان
عند مقارنة علامة HMAC، لا تستخدم المقارنة العادية === على الأسرار الحسّاسة؛ زمنها يتسرّب معلومة. استخدم مقارنة ثابتة الزمن:
const a = Buffer.from(tag);const b = Buffer.from(expected);const ok = a.length === b.length && crypto.timingSafeEqual(a, b); // لا يتوقّف عند أول اختلاف
توقّع وتحقّق
دالة التجزئة:
HMAC يقدّم:
لمقارنة علامة سرّية:
لكلمات المرور نستخدم:
الخلاصة
- التجزئة بصمة أحادية حتمية حسّاسة؛ لا تُعكس إلى الأصل.
- التشفير يُخفي محتوى يُستعاد؛ لا تخلطه بالتجزئة.
HMACتجزئة بمفتاح تثبت السلامة والأصل، وأساسها فيHS256.- قارن الأسرار بمقارنة ثابتة الزمن، واستخدم
bcryptلكلمات المرور.
تحدٍّ برمجي: تحقّق من علامة HMAC
متقدمنقطة التحقق تقبل أي رسالة دون فحص علامتها، فيمكن العبث بها. أكملها لتحسب HMAC بالمفتاح المشترك وتقبل الصحيحة فقط.
- احسب
HMAC-SHA256للرسالة بالمفتاحshared-secret. - إن طابق
tagالمُرسَل: 200، وإلا 401.
👀 اعرض الاستجابات المطلوبة
POST /verify{"msg": "hello", "tag": "dad60f181b1f0d72f63b2dff4e9aa1de277989cb1e08a8b0e64e43f774ac036e"}POST /verify{"msg": "hacked", "tag": "dad60f181b1f0d72f63b2dff4e9aa1de277989cb1e08a8b0e64e43f774ac036e"}
- الخادم يعمل ويرد على كل الطلبات بلا أخطاء غير معالَجة
- الرسالة بعلامتها الصحيحة تُقبل ← 200
- رسالة معبوث بها بعلامة قديمة تُرفض ← 401
- يحسب HMAC
💡 تلميح 1
crypto.createHmac('sha256', key).update(req.body.msg).digest('hex').💡 تلميح 2
req.body.tag.💡 تلميح 3
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
const express = require('express');const app = express();app.use(express.json());app.use(express.urlencoded({ extended: false })); const crypto = require('crypto');app.post('/verify', (req, res) => { const key = 'shared-secret'; const expected = crypto.createHmac('sha256', key).update(req.body.msg || '').digest('hex'); const ok = req.body.tag === expected; res.status(ok ? 200 : 401).json({ accepted: ok });});app.listen(3000);