أمن كلمات المرور: أفضل الممارسات وتخزينها بشكل آمن باستخدام Hashing و Salting
أمن كلمات المرور من أهم الأعمدة في أي نظام تسجيل دخول. تسريب واحد لقاعدة بيانات المستخدمين قد يدمّر سمعة مشروعك بالكامل، خصوصًا إذا كانت كلمات المرور مخزنة بطريقة غير آمنة. في هذا المقال من افهم صح سنشرح:
- لماذا لا يجب أبدًا تخزين كلمات المرور كنص عادي (Plain Text).
- ما هو hashing ولماذا هو مهم.
- ما هو salting وكيف يحمي من هجمات القواميس و rainbow tables.
- أهم الخوارزميات الآمنة مثل bcrypt / scrypt / Argon2.
- أفضل ممارسات إدارة إعادة تعيين كلمة المرور (Password Reset).
- نصائح عملية للحد من هجمات التخمين (Brute Force) وهجمات القواميس (Dictionary Attacks).
إذا كنت تهتم بأمان تطبيقك، فمن المهم أن تربط بين حماية كلمات المرور وباقي طبقات الأمان مثل حماية قاعدة البيانات من هجمات SQL Injection أو تأمين واجهات الـ APIs كما شرحنا في أفضل ممارسات تصميم RESTful APIs آمن.
لماذا لا يجب تخزين كلمات المرور كنص عادي؟
تخيل لديك قاعدة بيانات لموقعك تحتوي على جدول users، وكل كلمة مرور محفوظة كما كتبها المستخدم بالضبط:
id | email | password
---+--------------------+---------
1 | [email protected] | 123456
2 | [email protected] | admin123
لو تعرّضت قاعدة البيانات للاختراق لأي سبب (ثغرة SQL Injection، خطأ في إعدادات الخادم، تسريب نسخة احتياطية)، فإن المهاجم سيحصل فورًا على:
- كل الإيميلات.
- كل كلمات المرور بشكل مباشر.
- إمكانية تجربة هذه الكلمات على خدمات أخرى (بنوك، بريد، منصات اجتماعية) لأن غالبية المستخدمين يعيدون استخدام نفس كلمة المرور.
هنا لا نتحدث فقط عن فقدان بيانات، بل عن انتهاك خصوصية وثقة المستخدمين، ومسؤولية قانونية محتملة عليك في بعض الدول.
الحل ليس في تشفير كلمات المرور بمفتاح واحد، لأن أي شخص يحصل على المفتاح يستطيع فك التشفير. بل الحل الصحيح هو استخدام دوال تجزئة (Hash Functions) مصممة خصيصًا لكلمات المرور.
ما هو Hashing في أمن كلمات المرور؟
التجزئة (Hashing) هي عملية تحويل نص (مثل كلمة المرور) إلى قيمة ثابتة الطول تبدو عشوائية تسمى Hash باستخدام خوارزمية معينة.
- نفس المدخلات تعطي نفس الـ hash دائمًا.
- من الناحية العملية، لا يمكن استعادة النص الأصلي من الـ hash (وظيفة أحادية الاتجاه).
- تغيّر بسيط في المدخلات ينتج عنه hash مختلف تمامًا.
في أنظمة تسجيل الدخول، الفكرة كالتالي:
- المستخدم يختار كلمة مرور عند التسجيل.
- بدلًا من تخزين كلمة المرور نفسها، نقوم بحساب hash لها ونخزِن الـ hash فقط.
- عند تسجيل الدخول، نحسب الـ hash لكلمة المرور التي أدخلها المستخدم ونقارنها بالـ hash المخزن في قاعدة البيانات.
- إذا كانا متطابقين، نفترض أن الكلمة صحيحة بدون أن نعرف الكلمة الأصلية نفسها.
لماذا لا نستخدم MD5 أو SHA-1 لتجزئة كلمات المرور؟
دوال مثل MD5 وSHA-1 كانت مستخدمة قديمًا، لكن استخدامها اليوم في كلمات المرور يُعتبر ممارسة سيئة للأسباب التالية:
- سريعة جدًا، وهذا يجعلها مناسبة لهجمات التخمين باستخدام كروت الشاشة (GPU) أو حتى ASIC.
- لها قواعد بيانات جاهزة من القيم العكسية (Rainbow Tables) لمعظم الكلمات الضعيفة والشائعة.
- SHA-1 تحديدًا فيها مشاكل أمنية معروفة وتم كسرها نظريًا وعمليًا في بعض السيناريوهات.
كلمات المرور تحتاج إلى خوارزميات بطيئة وقابلة للتهيئة من ناحية التكلفة، بحيث يصبح تخمين ملايين الكلمات في الثانية صعبًا ومكلفًا.
ما هو Salting ولماذا هو ضروري؟
حتى مع استخدام دالة تجزئة قوية، هناك مشكلة أخرى: لو استخدم ألف مستخدم نفس كلمة المرور، سيكون الـ hash الناتج لهم متطابقًا تمامًا، وهذا يعطي المهاجم معلومات إضافية.
الـ Salt هو سلسلة عشوائية من البايتات تُضاف إلى كلمة المرور قبل تجزئتها. الفكرة الأساسية:
- لكل مستخدم، يتم توليد salt عشوائي وفريد.
- نحسب hash لكلمة المرور + salt.
- نخزن في قاعدة البيانات:
- الـ salt نفسه (غير سري).
- الـ hash الناتج.
- عند تسجيل الدخول، نسترجع salt المستخدم، ونطبّق نفس العملية ونقارن الـ hash.
فوائد استخدام salting:
- حتى لو استخدم أكثر من مستخدم نفس كلمة المرور، فإن الـ hash لكل واحد سيكون مختلفًا بسبب اختلاف الـ salt.
- يجعل هجمات Rainbow Tables غير فعّالة تقريبًا، لأن المهاجم يحتاج جداول جديدة لكل salt.
- يرفع تكلفة الهجوم بشكل كبير، خصوصًا مع خوارزميات بطيئة مثل bcrypt وArgon2.
غالبية مكتبات تجزئة كلمات المرور الحديثة (مثل bcrypt/Argon2) تتكفّل بإدارة الـ salt تلقائيًا، وتخزنه كجزء من الـ hash في شكل منسّق، لذلك في العادة لا تحتاج لإدارته يدويًا.
خوارزميات تجزئة آمنة لكلمات المرور: bcrypt و scrypt و Argon2
عندما نتحدث عن أمن كلمات المرور hashing salting اليوم، فإن أشهر الخوارزميات الموصى بها هي:
- bcrypt
- scrypt
- Argon2 (الفائز بمسابقة Password Hashing Competition)
1. خوارزمية bcrypt
bcrypt من أقدم خوارزميات تجزئة كلمات المرور المصممة خصيصًا لهذا الغرض، وما زالت مستخدمة على نطاق واسع.
مميزات bcrypt:
- تدعم عامل تكلفة (cost factor) يمكنك زيادته كل فترة ليصبح الحساب أبطأ مع تطور العتاد.
- تُستخدم في مكتبات جاهزة في معظم لغات البرمجة (PHP, Python, Node.js, Java, ...).
- تُدرِج الـ salt تلقائيًا ضمن سلسلة الـ hash.
المبدأ بسيط: كلما رفعت قيمة cost، زاد الوقت اللازم لحساب hash واحد، وبالتالي أصبحت هجمات التخمين الجماعي أبطأ.
2. خوارزمية scrypt
scrypt صُممت لمقاومة الهجمات باستخدام الهاردوير المتخصص (مثل GPU و ASIC)، لأنها لا تحتاج وقتًا فقط، بل تحتاج أيضًا ذاكرة كبيرة لكل عملية تجزئة.
- مناسبة للأنظمة التي تتوقع محاولات كبيرة لتخمين كلمات المرور.
- تسمى خوارزمية Memory-Hard؛ أي أن تنفيذها يحتاج ذاكرة ملحوظة، وهذا يجعل الهجمات واسعة النطاق أكثر تكلفة.
3. خوارزمية Argon2
Argon2 تُعتبر اليوم الخيار الأحدث والأكثر توصية في الكثير من المعايير، خصوصًا:
- Argon2id: مزيج بين Argon2i وArgon2d، وهو الموصى به في غالبية السيناريوهات.
ما يميز Argon2:
- قابلية ضبط عالية:
- زمن التنفيذ (time cost).
- كمية الذاكرة المطلوبة (memory cost).
- عدد الأنوية/الخيوط المستخدمة (parallelism).
- مصمم ليكون مقاومًا بدرجة عالية لهجمات الهاردوير المتوازي.
إن كانت بيئتك تدعم Argon2 (مثل PHP >= 7.2 مع دعم password_hash لـ Argon2)، فهو غالبًا الخيار الأفضل حاليًا.
أفضل الممارسات العملية لتخزين كلمات المرور بشكل آمن
1. لا تُخزَن كلمة المرور أبدًا كنص عادي
أول وأهم قاعدة: لا تخزّن كلمة المرور كما هي ولا حتى مشفرة بمفتاح واحد قابل للاسترجاع.
- المسموح فقط: تخزين hash ناتج عن خوارزمية آمنة متخصصة لكلمات المرور مع استخدام salt فريد لكل مستخدم.
2. استخدم مكتبات جاهزة ومعتمدة
لا تحاول كتابة نظام hashing خاص بك. دائمًا استخدم مكتبات قياسية متوفرة مع اللغة أو إطار العمل، مثل:
- في PHP: الدالة password_hash وpassword_verify مع PASSWORD_BCRYPT أو PASSWORD_ARGON2ID.
- في Python: مكتبات مثل passlib أو bcrypt أو argon2-cffi.
- في Node.js: مكتبة bcrypt أو argon2.
المكتبات الجيدة تتعامل تلقائيًا مع:
- توليد salt عشوائي تشفيرًا قويًا.
- تخزين معلومات الخوارزمية والتكلفة داخل الـ hash.
- التوافق بين الإصدارات المختلفة للخوارزمية.
3. اختَر معلمات تكلفة مناسبة (Cost Parameters)
استخدام خوارزمية آمنة لا يكفي؛ يجب ضبطها بشكل صحيح:
- في bcrypt: اختَر cost (مثل 10–14) بحيث يستغرق حساب hash واحد مثلًا 100–300ms على الخادم.
- في Argon2: اضبط time cost وmemory cost بحيث يكون الزمن والذاكرة مناسبين لبيئتك.
الفكرة: يجب أن يكون تسجيل الدخول للمستخدم العادي سريعًا بما يكفي، لكن في نفس الوقت يجعل تنفيذ ملايين المحاولات في الثانية مكلفًا جدًا للمهاجم.
4. لا تعِد استخدام نفس كلمة المرور داخليًا لأغراض أخرى
كلمة المرور هدفها الوحيد المصادقة (Authentication). لا تستخدمها:
- لتشفير بيانات أخرى مباشرة.
- كجزء من مفاتيح API أو توكنات جلسات.
إذا احتجت لمفتاح لتشفير بيانات المستخدم، استخدم مفتاحًا آمنًا خاصًا بالخادم ومخزّنًا في بيئة آمنة (مثل أدوات إدارة الأسرار).
إدارة إعادة تعيين كلمة المرور (Password Reset) بشكل آمن
ميزة "نسيت كلمة المرور" غالبًا تُستهدف من المهاجمين، لذلك يجب تصميمها بعناية.
1. لا ترسل كلمة مرور جديدة بالبريد الإلكتروني
- لا تولد كلمة مرور جديدة وترسلها للمستخدم.
- أفضل ممارسة: أرسل رابطًا مؤقتًا يسمح للمستخدم باختيار كلمة مرور جديدة بنفسه.
2. استخدم توكن (Token) عشوائي وقصير العمر
آلية آمنة ومبسطة:
- المستخدم يطلب إعادة تعيين كلمة المرور عن طريق البريد.
- تولّد توكن عشوائي قوي (مثلاً 32–64 بايت من مولّد أرقام عشوائية آمن).
- تخزّن hash للتوكن في قاعدة البيانات مع:
- تاريخ انتهاء (مثل 15–60 دقيقة).
- معرّف المستخدم.
- ترسل للمستخدم بريدًا يحتوي على رابط به التوكن في الـ URL.
- عند فتح الرابط:
- تحسب hash للتوكن المرسل.
- تقارن مع الموجود في قاعدة البيانات وتتأكد من عدم انتهاء صلاحيته وعدم استخدامه سابقًا.
- تسمح للمستخدم بإدخال كلمة مرور جديدة، ثم تعطل التوكن.
بهذه الطريقة، حتى لو تسربت قاعدة البيانات، المهاجم لن يحصل على التوكن الحقيقي لأنك تخزن hash له وليس قيمته الخام.
3. لا تكشف إن كان البريد موجودًا أم لا
لتقليل إمكانية جمع الإيميلات المسجّلة في النظام:
- عند طلب إعادة تعيين كلمة المرور، أعِد نفس الرسالة دائمًا مثل:
"إن كان البريد مسجلًا لدينا فستصلك رسالة لإعادة تعيين كلمة المرور".
الحد من هجمات التخمين والقواميس على كلمات المرور
حتى مع استخدام hashing و salting الصحيح، سيحاول المهاجمون تخمين كلمات المرور الضعيفة. هنا تأتي طبقة دفاع إضافية على مستوى التطبيق.
1. فرض سياسة كلمات مرور قوية
تنفيذ سياسة ذكية لكلمات المرور يقلل بشكل كبير من فعالية هجمات القواميس:
- حد أدنى للطول (مثلاً 10–12 حرفًا أو أكثر).
- تشجيع المستخدم على استخدام عبارات مرور (Passphrases) بدل كلمات بسيطة.
- تجنّب إجبار المستخدم على "تعقيد اصطناعي" فقط (حرف كبير + رقم + رمز) بدون طول كافٍ.
طول الكلمة مهم جدًا في مقاومة هجمات التخمين (Brute Force)، لأن عدد الاحتمالات يزيد أُسّيًا مع زيادة عدد الأحرف.
2. تقييد عدد محاولات تسجيل الدخول (Rate Limiting)
تحتاج لضوابط تمنع المهاجم من تجربة مئات أو آلاف الكلمات في الدقيقة لكل حساب:
- بعد عدد معين من المحاولات الفاشلة (مثلاً 5–10)، فعّل:
- تأخير متزايد بين المحاولات (مثلاً 30 ثانية، 1 دقيقة، 5 دقائق...).
- أو قفل مؤقت للحساب مع إرسال إخطار للمستخدم الحقيقي.
- استخدام Rate Limiting أو Captchas على مستوى IP أو الجلسة للحد من محاولات الآلة.
3. مراقبة النشاط المشبوه والتنبيهات
- سجّل محاولات تسجيل الدخول الفاشلة في logs مع IP ووقت المحاولة.
- استخدم أنظمة كشف الاختراق أو مراقبة (IDS/IPS) للتعامل مع الأنماط الغريبة.
- أضف إشعارات للمستخدم عند:
- تسجيل الدخول من جهاز أو موقع جغرافي جديد.
- تغيير كلمة المرور أو البريد الإلكتروني.
4. دعم المصادقة متعددة العوامل (MFA / 2FA)
حتى لو تمكن المهاجم من معرفة كلمة المرور، وجود عامل أمان إضافي مثل:
- رمز لمرة واحدة (TOTP) عبر تطبيق مثل Google Authenticator.
- مفاتيح أمان (Security Keys) مثل FIDO U2F.
يقلل احتمال الاستيلاء الكامل على الحساب بشكل كبير، خصوصًا في التطبيقات الحساسة.
حماية قاعدة البيانات نفسها
تأمين تخزين كلمات المرور يعتمد أيضًا على حماية قاعدة البيانات من الأساس؛ فإذا تمكن المهاجم من تنفيذ استعلامات عشوائية، سيصل للـ hash مهما كان قويًا.
- تأكد من حماية تطبيقك من SQL Injection عبر استخدام الاستعلامات المعلَّمة (Prepared Statements) كما شرحنا في كيف أحمي تطبيقي من هجمات SQL Injection في PHP؟.
- استخدم أقل صلاحيات ممكنة لمستخدم قاعدة البيانات (Least Privilege).
- تأكّد من تشفير الاتصالات مع قاعدة البيانات (SSL/TLS) خاصة إن كانت على خادم منفصل.
- لا تحتفظ بنسخ احتياطية (Backups) بدون تشفير في أماكن غير آمنة.
خلاصة: نموذج عملي لأمن كلمات المرور Hashing + Salting
لتلخيص ما سبق، إليك نموذج عملي لما يجب أن تفعله في تطبيقك:
- عند تسجيل مستخدم جديد:
- استقبل كلمة المرور عبر اتصال HTTPS فقط.
- استخدم مكتبة موثوقة (مثل bcrypt أو Argon2) لتوليد hash مع salt تلقائيًا.
- خزّن الـ hash (الذي يحتوي ضمنيًا على معلومات الخوارزمية والـ salt والتكلفة) في قاعدة البيانات.
- عند تسجيل الدخول:
- استقبل كلمة المرور عبر HTTPS.
- استخدم الدالة المناسبة (password_verify مثلاً في PHP) للتحقق من تطابق كلمة المرور مع الـ hash المخزن.
- طبّق قيود على عدد المحاولات الفاشلة.
- عند نسيان كلمة المرور:
- ولّد توكن عشوائي قوي.
- خزّن hash للتوكن مع وقت انتهاء.
- أرسل للمستخدم رابط إعادة تعيين يحتوي على التوكن.
- اسمح له بضبط كلمة مرور جديدة بعد التحقق من صحة التوكن.
- بشكل دوري:
- قيّم قوة إعدادات التكلفة للخوارزمية (cost, memory, time) وحدّثها عند الحاجة.
- راقب السجلات لمحاولات التخمين والهجمات.
- حسِّن سياسة كلمات المرور وشجّع المستخدمين على استخدام مصدّرات كلمات المرور وإدارة كلمات المرور (Password Managers).
تطبيق هذه الممارسات لن يجعل نظامك "غير قابل للاختراق" بشكل مطلق، لكن سيجعل أمن كلمات المرور hashing salting لديك في مستوى احترافي يقلل جدًا الأضرار المحتملة عند أي تسريب، ويرفع تكلفة الهجوم على المهاجمين لأقصى درجة ممكنة.