البرمجة مبتدئ 7 دقيقة

كيفية تشفير كلمات المرور بشكل صحيح في PHP

تخزين كلمات المرور بشكل خاطئ هو أحد أكثر الأخطاء التي قد يرتكبها مطور تأثيراً وعواقب. اختراق قاعدة بيانات بكلمات مرور نصية أو بتشفير ضعيف يكشف مستخدميك فوراً وبشكل دائم. الطريقة الصحيحة معيارية في PHP منذ الإصدار 5.5 — لا مبرر للارتجال.

هذا الدليل يغطي الدوال المدمجة في PHP التي يجب عليك استخدامها، ولماذا MD5 وSHA1 مستبعدان، وكيف تتحقق من كلمات المرور دون هجمات التوقيت، وكيف ترقّي التشفيرات بشفافية عند تغيير الخوارزمية.

الخطوات

  1. 1

    لا تخزن النصوص الواضحة أو التشفير الضعيف

    النص الواضح كارثي بوضوح. MD5 وSHA1 دوال تشفير مشفرة، ليست خوارزميات تشفير كلمات مرور — فهي سريعة بتصميم، وهذا بالضبط ما يجعلها خطيرة على كلمات المرور. المهاجم بـ GPU يمكنه اختبار مليارات من تشفيرات MD5 في الثانية. الجداول المحسوبة مسبقاً (rainbow tables) تغطي كل كلمة مرور شائعة. إذا استخدمت MD5 أو SHA1 لكلمات المرور، فأنت لا تشفر كلمات المرور — أنت تؤخر اختراقاً حتمياً بساعات.

    SHA256 وSHA512 لهما نفس المشكلة. السرعة عدو تشفير كلمات المرور.

  2. 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. 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. 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. 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. 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. 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(). لا يوجد سبب وجيه للجوء إلى أي شيء آخر.

#PHP #Security #Auth
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.