الخطوات
-
1
لا تخزن النصوص الواضحة أو التشفير الضعيف
النص الواضح كارثي بوضوح. MD5 وSHA1 دوال تشفير مشفرة، ليست خوارزميات تشفير كلمات مرور — فهي سريعة بتصميم، وهذا بالضبط ما يجعلها خطيرة على كلمات المرور. المهاجم بـ GPU يمكنه اختبار مليارات من تشفيرات MD5 في الثانية. الجداول المحسوبة مسبقاً (rainbow tables) تغطي كل كلمة مرور شائعة. إذا استخدمت MD5 أو SHA1 لكلمات المرور، فأنت لا تشفر كلمات المرور — أنت تؤخر اختراقاً حتمياً بساعات.
SHA256 وSHA512 لهما نفس المشكلة. السرعة عدو تشفير كلمات المرور.
-
2
التشفير باستخدام password_hash()
password_hash()هي الدالة الوحيدة التي تحتاجها لإنشاء تشفيرات كلمات المرور. تولّد ملحاً (salt) عشوائياً مشفرياً تلقائياً وتضمّنه في الناتج — لا تدير الملاح يدوياً. استخدمPASSWORD_BCRYPTكافتراضي آمن، أوPASSWORD_ARGON2IDإذا كان خادمك يحتوي على امتداد Argon2 وتريد تشفيراً أقوى يعتمد على الذاكرة.php<?php $password = 'user_supplied_password'; // Bcrypt — the safe default (cost 10 by default) $hash = password_hash($password, PASSWORD_BCRYPT); // Argon2id — stronger, preferred if available $hash = password_hash($password, PASSWORD_ARGON2ID); // The hash includes algorithm, cost, salt, and hash — store this entire string // Example output: $2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi echo $hash; -
3
ضبط معامل التكلفة
معامل التكلفة يتحكم في كمية عمل CPU الذي يقوم به bcrypt. تكلفة أعلى = تشفير أبطأ = أصعب للقوة الغاشمة. الافتراضي هو 10. استهدف وقت تشفير 200-500 ميلي ثانية على أجهزتك الإنتاجية — شغّل المعيار أدناه واختر أعلى تكلفة تبقى تحت هذا الحد. أعد الضبط كل بضع سنوات مع تسارع الأجهزة.
php<?php // Benchmark: find the right cost for your server $targetMs = 300; // 300ms target $cost = 9; do { $cost++; $start = microtime(true); password_hash('benchmark', PASSWORD_BCRYPT, ['cost' => $cost]); $ms = (microtime(true) - $start) * 1000; } while ($ms < $targetMs); echo "Use cost: " . $cost . " ({$ms}ms)"; // Use the chosen cost when hashing real passwords $hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => $cost]); -
4
التحقق باستخدام password_verify()
password_verify()تستخرج الخوارزمية والتكلفة والملح من التشفير المخزن وتعيد حسابه مقابل كلمة المرور المقدمة. تعيدtrueأوfalse. والأهم، تستخدم مقارنة بوقت ثابت داخلياً — لا قناة جانبية للتوقيت. لا تستخدم===أوstrcmp()لمقارنة تشفيرات كلمات المرور؛ فهي ليست ذات وقت ثابت.php<?php $storedHash = '\$2y\$10\$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'; $isValid = password_verify($userInput, $storedHash); if ($isValid) { // Grant access } else { // Reject — do NOT reveal whether the email or password was wrong } -
5
ترقية التشفيرات بشفافية
password_needs_rehash()تتحقق مما إذا كان التشفير المخزن قد أُنشئ بخوارزمية أو معامل تكلفة مختلف عما تستخدمه حالياً. استدعها بعد تسجيل دخول ناجح — لديك كلمة المرور النصية بين يديك في تلك اللحظة. إذا أعادتtrue، أعد التشفير وحدّث قاعدة البيانات بصمت. المستخدم لا يعلم، وتهاجر جميع التشفيرات تدريجياً إلى المعيار الجديد.php<?php function loginUser(string $email, string $plainPassword): bool { $user = User::findByEmail($email); if (! $user) return false; if (! password_verify($plainPassword, $user->password)) { return false; } // Transparently upgrade if algorithm or cost changed if (password_needs_rehash($user->password, PASSWORD_BCRYPT, ['cost' => 12])) { $user->password = password_hash($plainPassword, PASSWORD_BCRYPT, ['cost' => 12]); $user->save(); } return true; } -
6
استخدام Hash Facade في Laravel
إذا كنت تستخدم Laravel، لا تستدعِ
password_hash()مباشرة. استخدمHash::make()وHash::check()— فهما يغلّفان نفس دوال PHP لكنهما يحترمان إعداداتconfig/hashing.php(برنامج التشغيل، التكلفة، حدود الذاكرة) ويسهّلان تبديل الخوارزمية على مستوى التطبيق دون لمس كل موقع استخدام.php<?php use Illuminate\Support\Facades\Hash; // Storing a password $user->password = Hash::make($request->password); $user->save(); // Verifying at login if (! Hash::check($request->password, $user->password)) { throw ValidationException::withMessages([ 'email' => ['These credentials do not match our records.'], ]); } // Check if rehash is needed (Laravel handles this automatically // in the built-in LoginController, but manual check looks like this) if (Hash::needsRehash($user->password)) { $user->password = Hash::make($plainPassword); $user->save(); } -
7
لماذا الـ Peppers نادراً ما تكون ضرورية
الـ pepper هو سلسلة سرية تُضاف إلى كلمة المرور قبل التشفير، مخزنة في ضبط التطبيق (لا في قاعدة البيانات). الفكرة أنه حتى لو سُرقت قاعدة البيانات، لا يستطيع المهاجم كسر التشفيرات بدون الـ pepper. عملياً، تشفيرات bcrypt أو Argon2id القوية غير قابلة للكسر حسابياً أصلاً — الـ peppers تضيف تعقيداً تشغيلياً (التدوير، التخزين) مقابل مكسب أمني هامشي. استخدم الـ pepper فقط إذا كان نموذج التهديد لديك يتضمن تحديداً اختراقات قاعدة البيانات فقط مع ضرورة التعامل بلطف مع كلمات المرور الضعيفة للمستخدمين.
نصائح ومحاذير
- خزّن دائماً الناتج الكامل لـ <code>password_hash()</code> — يتضمن الخوارزمية والتكلفة والملح. لا تخزن جزء التشفير فقط. نوع العمود: <code>VARCHAR(255)</code>.
- ارفع تكلفة bcrypt عند ترقية الخوادم. التكلفة التي تستغرق 250 ميلي ثانية على خادم 2020 قد تستغرق 80 ميلي ثانية على خادم 2025 — وهذا يجعل الكسر بالقوة الغاشمة أسهل بـ 3 أضعاف.
- لا تحدّد حد أقصى لطول كلمة المرور بطريقة تكشف خوارزمية التشفير. bcrypt يقطع بصمت عند 72 بايت — إذا احتجت كلمات مرور أطول، شفّر مسبقاً بـ SHA384 (لا SHA256 لتجنب امتداد الطول) قبل تمريرها إلى bcrypt.
- للأنظمة القديمة التي لا تزال تستخدم MD5، مسار الترحيل هو: عند تسجيل الدخول التالي تحقق من تشفير MD5، ثم أعد التشفير فوراً بـ bcrypt وحدّث العمود. تشفيرات MD5 القديمة تُستبدل واحداً تلو الآخر عند تسجيل دخول المستخدمين.
- المقارنة بوقت ثابت مهمة لمقارنة الرموز أيضاً. استخدم <code>hash_equals(\$knownHash, \$userHash)</code> في أي مكان تقارن فيه سلاسل حساسة أمنياً خارج <code>password_verify()</code>.
خاتمة
تشفير كلمات المرور في PHP محلول: password_hash() مع PASSWORD_BCRYPT أو PASSWORD_ARGON2ID، تحقق بـ password_verify()، وارقّ بصمت بـ password_needs_rehash(). لا يوجد سبب وجيه للجوء إلى أي شيء آخر.