ليه الموقع مايعرفش كلمة مرورك أصلاً؟ Password Hashing للمبتدئ
المستوى: مبتدئ. لو بتبني أول نظام تسجيل دخول وعايز تخزّن كلمات المرور بطريقة صح، المقال ده ليك. مش محتاج خلفية أمنية، بس تعرف تكتب دالة بسيطة.
لو سرّب حد قاعدة بيانات موقعك بكرة، الفرق بين كارثة وخبر عابر هو سطر واحد في كود تسجيل التسجيل. الموقع المحترم مش بيعرف كلمة مرورك أصلاً، حتى صاحبه نفسه. هنا هتفهم ليه، وتكتب الكود الصح في 5 دقايق.
المشكلة باختصار
أبسط طريقة لتسجيل الدخول إنك تخزّن كلمة المرور في عمود password زي ما هي. تشتغل تمام. لكن يوم ما الجدول يتسرّب — وده بيحصل كل أسبوع لمواقع كبيرة — المهاجم بياخد كل كلمات المرور جاهزة. والأسوأ: ناس كتير بتستخدم نفس كلمة المرور في الإيميل والبنك. يعني تسريب موقعك بيفتح حسابات تانية مالكش دعوة بيها.
الحل مش إنك تشفّر كلمات المرور وتفك تشفيرها وقت الحاجة. الحل إنك تخزّنها بشكل مايرجعش لأصله أبداً. ده اللي اسمه Hashing.
الفكرة بمثال: مفرمة اللحمة
تخيّل مفرمة لحمة. بتحط قطعة لحمة وتطلع لحمة مفرومة. سهل في الاتجاه ده. لكن جرّب تعكس العملية: خد اللحمة المفرومة وارجّعها قطعة واحدة زي الأول. مستحيل.
دالة الهاش بالظبط كده. بتاخد كلمة مرورك P@ss123 وتطلّع سلسلة حروف ثابتة الطول زي a8f5f167.... تقدر تفرم بسهولة، بس متقدرش ترجّع المفروم لأصله. والموقع مش محتاج يرجّعه أصلاً.
طب إزاي بيتأكد إنك دخلت الباسوورد الصح وهو مايعرفهوش؟ بسيطة: وقت التسجيل بيفرم كلمتك ويخزّن المفروم. وقت الدخول بيفرم اللي كتبته دلوقتي، ويقارن المفروم بالمفروم المخزّن. لو اتطابقوا، يبقى نفس الكلمة. كل ده من غير ما يعرف الكلمة نفسها في أي لحظة.
الشرح العلمي الدقيق
دالة الهاش التشفيري (Cryptographic Hash) هي دالة رياضية بتحوّل أي مدخل مهما كان طوله إلى مخرج ثابت الطول، وبتتميز بخاصيتين: الأولى إنها أحادية الاتجاه (one-way) — حسابياً غير عملي إنك تستنتج المدخل من المخرج. التانية إنها حتمية (deterministic) — نفس المدخل بيدّي نفس المخرج دايماً، وده اللي بيخلّي المقارنة ممكنة.
هاش مش تشفير — الفرق اللي بيغلط فيه الناس
كتير بيخلطوا بين الاتنين. التشفير (Encryption) ليه مفتاح، وبيتعمل عشان ترجّع البيانات بعدين. الهاش مالوش مفتاح للرجوع، وبيتعمل عشان متترجّعش أبداً. كلمة المرور مش بيانات محتاج تقراها تاني، فالتشفير غلط هنا: أي حد يلاقي المفتاح يفك كل الباسووردات. الهاش مفيهوش مفتاح يتسرّب أصلاً.
الفخ الأول: نفس الكلمة = نفس الهاش
لأن الهاش حتمي، أي شخصين بنفس كلمة المرور هيطلعوا بنفس الهاش بالظبط. المهاجم بيستغل ده بجدول جاهز اسمه Rainbow Table: ملايين الكلمات الشائعة محسوب هاشها مسبقاً. يقارن جدولك بجدوله، ويكشف كل اللي استخدموا 123456 في ثواني.
الحل اسمه Salt (الملح): نص عشوائي فريد بيتولّد لكل مستخدم، وبيتضاف للكلمة قبل الفرم. كده حتى لو اتنين بنفس الكلمة، هاشهم هيطلع مختلف، وجداول Rainbow بتبقى بلا قيمة. المكتبات الحديثة بتولّد الـ salt وتخزّنه جوّه الهاش نفسه أوتوماتيك — مش محتاج تعمله بإيدك.
الكود الصح: bcrypt في Node.js
متكتبش دالة هاش بنفسك. استخدم مكتبة مجرّبة. ده مثال شغّال بالكامل:
// npm install bcrypt
const bcrypt = require("bcrypt");
// وقت التسجيل: افرم وخزّن
const saltRounds = 12; // عامل الكلفة — كل +1 بيضاعف الزمن
const hash = await bcrypt.hash("P@ss123", saltRounds);
// النتيجة (الـ salt مدمج جوّاها):
// $2b$12$Q9.../KzR... — ده اللي بتخزّنه في الداتابيز
// وقت الدخول: قارن بدون ما تفك أي حاجة
const ok = await bcrypt.compare("P@ss123", hash);
console.log(ok); // true لو الكلمة صح
لاحظ: مفيش دالة decrypt. مفيش طريقة ترجّع الكلمة. كل اللي تقدر تعمله هو compare.
الأفضل في 2026: argon2
توصية OWASP الحالية للمشاريع الجديدة هي Argon2id، لأنه «memory-hard» — بيستهلك ذاكرة كبيرة لكل محاولة، فبيصعّب الهجوم بكروت الشاشة (GPU) اللي بتجرّب مليارات المحاولات بالتوازي.
// npm install argon2
const argon2 = require("argon2");
const hash = await argon2.hash("P@ss123", {
type: argon2.argon2id,
memoryCost: 19456, // 19 ميجابايت (حد OWASP الأدنى)
timeCost: 2,
parallelism: 1,
});
const ok = await argon2.verify(hash, "P@ss123");
الأرقام اللي تخلّي ده مهم
ليه مانستخدمش دالة سريعة زي SHA-256؟ لأن السرعة هنا عيب مش ميزة. كارت شاشة واحد ممكن يجرّب أكتر من 10 مليار هاش SHA-256 في الثانية. يعني باسوورد من 8 حروف ممكن يتكسر في ساعات.
bcrypt و Argon2 بطيئين عمداً. عند cost = 12، الـ bcrypt بياخد حوالي 0.25 ثانية للهاش الواحد. ده مايفرقش معاك وقت تسجيل دخول واحد، لكنه بيخلّي المهاجم يجرّب آلاف قليلة في الثانية بدل مليارات — يعني تكسير نفس الباسوورد بياخد سنين بدل ساعات. توصية OWASP: عامل كلفة bcrypt لا يقل عن 10، والمفضّل 12 أو أكتر. و PBKDF2 (لو مضطر للالتزام بمعايير FIPS) لا يقل عن 600,000 تكرار مع SHA-256.
الـ trade-offs — مفيش حل ببلاش
- السرعة مقابل الأمان: كل ما رفعت عامل الكلفة، بقى أأمن، بس كل تسجيل دخول بياخد وقت أطول وCPU أكتر. المكسب: مقاومة تكسير. الخسارة: زمن استجابة + استهلاك سيرفر.
- Argon2 مقابل bcrypt: Argon2 أأمن نظرياً، لكنه بياكل ذاكرة (19 ميجا لكل محاولة). لو سيرفرك بيسجّل آلاف الدخول في الثانية، الذاكرة دي بتتراكم. الافتراض هنا إن حركة الدخول عندك معقولة (أقل من بضع مئات/ثانية)؛ فوق كده اضبط الـ parallelism وراقب الذاكرة.
- متلعبش يدوي: أي محاولة تكتب الهاش أو الـ salt بنفسك بتفتح ثغرات. اعتمد على المكتبة.
متى لا تستخدم هذه الطريقة
الهاش للكلمات اللي مش محتاج ترجّعها. لو عندك بيانات لازم تقراها تاني — زي رقم بطاقة بنكية أو توكن API — دي محتاجة تشفير بمفتاح، مش هاش. كمان: متستخدمش SHA-256 أو MD5 وحدهم لكلمات المرور أبداً، مهما ضفت salt — سريعين جداً ومش معمولين للغرض ده. واستخدم PBKDF2 فقط لو ملزَم بمعيار FIPS.
الخطوة التالية
افتح كود تسجيل المستخدمين عندك دلوقتي. لو لقيت أي مكان بيخزّن password كنص واضح أو بـ md5() أو sha256() من غير مكتبة باسوورد مخصصة، ده الإصلاح الأهم في مشروعك. ركّب argon2 أو bcrypt، وعند أول تسجيل دخول ناجح لكل مستخدم، أعد فرم كلمته بالطريقة الجديدة وحدّث العمود. كده بتهاجر تدريجياً بدون ما تطلب من حد يغيّر كلمته.
المصادر
- OWASP Password Storage Cheat Sheet — توصيات Argon2id و bcrypt و PBKDF2 ومعاملاتها.
- RFC 9106 — Argon2 Memory-Hard Function for Password Hashing.
- Provos & Mazières (1999), "A Future-Adaptable Password Scheme" — الورقة الأصلية لـ bcrypt.
- توثيق مكتبة node.bcrypt.js ومكتبة node-argon2 الرسمي.