ملاحظات للعاملين بلغة php التشفير والتقطيع hashing في PHP


سارة محمد2

التشفير وفك التشفير المتناظر لملفات كبيرة باستخدام OpenSSL

لا توفر PHP دالة مضمنة لتشفير وفك تشفير الملفات الكبيرة، يمكن استخدام الدالة openssl_encrypt لتشفير السلاسل النصية لكن يعد تحميل ملف كبير جدًا في الذاكرة فكرةً سيئةً، لذا يجب كتابة دالة تقوم بهذا العمل، يستخدم هذا المثال خوارزمية AES-128-CBC المتناظرة لتشفير أجزاء صغيرة من ملف كبير وكتابتها في ملف آخر.

تشفير الملفات

// (1)
define('FILE_ENCRYPTION_BLOCKS', 10000);

/**
* ‫تشفير الملف الممرر وحفظ النتيجة في ملف جديد باللاحقة "‎.enc"
*
* @param string $source مسار الملف الذي نريد تشفيره
* @param string $key المفتاح المستخدم للتشفير
* @param string $dest اسم الملف الذي نريد أن نكتب فيه الملف المشفَّر
* @return string|false 
* ‫تعيد هذه الدالة اسم الملف المنشأ أو FALSE إذا حدث خطأ
*/
function encryptFile($source, $key, $dest)
{
    $key = substr(sha1($key, true), 0, 16);
    $iv = openssl_random_pseudo_bytes(16);
    $error = false;
    if ($fpOut = fopen($dest, 'w')) {
        // ضع شعاع التهيئة في بداية الملف‬
        fwrite($fpOut, $iv);
        if ($fpIn = fopen($source, 'rb')) {
            while (!feof($fpIn)) {
                $plaintext = fread($fpIn, 16 * FILE_ENCRYPTION_BLOCKS);
                $ciphertext = openssl_encrypt($plaintext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA,$iv);
                // ‫استخدم أول 16 بايت من النص المشفر كشعاع التهيئة التالي
                $iv = substr($ciphertext, 0, 16);
                fwrite($fpOut, $ciphertext);
            }
            fclose($fpIn);
        } else {
            $error = true;
        }
        fclose($fpOut);
    } else {
        $error = true;
    }
    return $error ? false : $dest;
}

نحدد في الموضع (1) عدد الكتل التي يجب قراءتها من الملف المصدري من أجل كل جزء، بحيث تتألف كل كتلة من 16 بايت من أجل الخوارزمية الآتية:

 'AES-128-CBC'

لهذا، فإذا قرأنا 10000 كتلة نحمّل 160 كيلوبايت في الذاكرة، يمكنك تعديل هذه القيمة لقراءة/كتابة أجزاء أصغر/أكبر.

فك تشفير الملفات

يمكنك استخدام هذه الدالة لفك تشفير الملفات المشفرة بالدالة السابقة.

/**
* ‫فك تشفير الملف الممرر وحفظ النتيجة في ملف جديد مع حذف آخر 4 محارف من اسم الملف
* @param string $source مسار الملف الذي نريد فك تشفيره
* @param string $key ‫المفتاح المستخدم لفك التشفير (ويجب أن يكون نفس المفتاح المستخدم للتشفير)
* @param string $dest اسم الملف حيث يجب أن نكتب الملف الجديد بعد فك التشفير
* @return string|false
* ‫تعيد هذه الدالة اسم الملف المنشأ أو FALSE إذا حدث خطأ
*/
function decryptFile($source, $key, $dest)
{
    $key = substr(sha1($key, true), 0, 16);
    $error = false;
    if ($fpOut = fopen($dest, 'w')) {
        if ($fpIn = fopen($source, 'rb')) {
            // الحصول على شعاع التهيئة من بداية الملف
            $iv = fread($fpIn, 16);
            while (!feof($fpIn)) {
                $ciphertext = fread($fpIn, 16 * (FILE_ENCRYPTION_BLOCKS + 1));
                // يجب أن نقرأ كتلة واحدة زيادة عن التشفير لفك التشفير
                $plaintext = openssl_decrypt($ciphertext, 'AES-128-CBC', $key, OPENSSL_RAW_DATA,$iv);
                // ‫استخدم أول 16 بايت من النص المشفر كشعاع التهيئة التالي
                $iv = substr($ciphertext, 0, 16);
                fwrite($fpOut, $plaintext);
            }
            fclose($fpIn);
        } else {
            $error = true;
        }
        fclose($fpOut);
    } else {
        $error = true;
    }
    return $error ? false : $dest;
}

طريقة الاستخدام

إليك الشيفرة التالية لتعرف كيفية استخدام الدوال السابقة.

$fileName = __DIR__.'/testfile.txt';
$key = 'my secret key';
file_put_contents($fileName, 'Hello World, here I am.');
encryptFile($fileName, $key, $fileName . '.enc');
decryptFile($fileName . '.enc', $key, $fileName . '.dec');

ستنشئ هذه الشيفرة ثلاثة ملفات:

  • testfile.txt وفيه النص الأصلي.
  • testfile.txt.enc فيه الملف المشفر.
  • testfile.txt.dec فيه الملف بعد فك تشفيره ويجب أن يكون نفس محتويات الملف testfile.txt.

التشفير المتناظر

يوضح هذا المثال التشفير المتناظر باستخدام خوارزمية AES 256 بالنمط CBC وهو اختصار لـ Cipher Block Chaining، نحتاج شعاع تهيئة لذا نولّد واحدًا باستخدام دالة openssl، ويستخدم المتغير ‎$strong لتحديد فيما إذا كان شعاع التهيئة المولَّد قويًا من ناحية التشفير.

التشفير

// طريقة التشفير
$method = "aes-256-cbc";

// الحصول على طول شعاع التهيئة المطلوب
$iv_length = openssl_cipher_iv_length($method);

// ‫ضبط للقيمة false من أجل السطر التالي
$strong = false;

// توليد شعاع التهيئة
$iv = openssl_random_pseudo_bytes($iv_length, $strong);

/*
‫يحتاج شعاع التهيئة للاسترجاع لاحقًا لذا خزنه في قاعدة البيانات لكن لا تعيد استخدام نفس شعاع التهيئة لتشفير بيانات مرةً أخرى
*/
if(!$strong) {
    // رمي استثناء إذا لم يكن شعاع التهيئة قويًا من ناحية التشفير
throw new Exception("IV not cryptographically strong!");
}

// الرسالة السرية
$data = "This is a message to be secured.";
// كلمة المرور
$pass = "Stack0verfl0w";

/* ‫يجب أن تُرسل كلمة المرور بالطريقة POST عبر جلسة HTTPS، قمنا بتخزينها في متغير هنا لأغراض توضيحية *
*/
// التشفير
$enc_data = openssl_encrypt($data, $method, $password, true, $iv);

فك التشفير

// ‫استعادة شعاع التهيئة من قاعدة البيانات وكلمة المرور من الطلب POST
// فك التشفير
$dec_data = openssl_decrypt($enc_data, $method, $pass, true, $iv);

التشفير وفك التشفير بالأساس 64

إذا كانت البيانات المشفرة تحتاج للإرسال أو التخزين في نص قابل للطباعة عندها يمكن استخدام الدالتين ()base64_encode، و ()base64_decode على الترتيب.

// تشفير الترميز بالأساس 64
$enc_data = base64_encode(openssl_encrypt($data, $method, $password, true, $iv));

// فك الترميز وفك التشفير
$dec_data = openssl_decrypt(base64_decode($enc_data), $method, $password, true, $iv);

دوال تعمية كلمة المرور

بما أنّ خدمات الويب الأكثر أمنًا تتجنب تخزين كلمات المرور بصياغة نص واضح فإنّ بعض اللغات مثل PHP توفر دوال تعميةصعبة الاستخراج hashing، ومتنوعة لدعم معيار الصناعة الأكثر أمنًا. يوفر هذا المثال توثيقًا لعمليات التعمية المناسب باستخدام PHP.

إنشاء كلمة مرور معماة

ننشئ نسخة معماة لكلمة المرور باستخدام الدالة password_hash()‎ لاستخدام تعمية معيارية بأفضل ممارسة للصناعة الحالية أو لاشتقاق المفتاح، في وقت كتابة هذا النص المعيار هو bcrypt مما يعني أنّ PASSWORD_DEFAULT له نفس قيمة PASSWORD_BCRYPT.

$options = [
    'cost' => 12,
];

$hashedPassword = password_hash($plaintextPassword, PASSWORD_DEFAULT, $options);

المعامل الثالث ليس إجباريًا.

يجب أن نختار القيمة 'cost' بالاعتماد على تجهيزات خادم الإنتاج، ستجعل زيادتها كلمة المرور أكثر تكلفةً عند توليدها، كلما زادت تكلفة استخراج وفك الكلمة المعماة، استغرق الأمر وقتًا أطول عند محاولة شخص ما استخراجها؛ ومثاليًا يجب أن تكون التكلفة أعلى ما يمكن᠎ لكن من الناحية العملية يجب ضبطها بحيث لا تؤدي إلى بطء شديد في كل شيء، من المناسب أن تكون بين 0.1 و0.4 ثانية، إذا كنت محتارًا استخدم القيمة الافتراضية.

في الإصدارات السابقة للإصدار 5.5، الدوال password_*‎ غير متوفرة، يجب أن تستخدم حزمة التوافق للحصول على بديل لهذه الدوال، لاحظ أنّ حزمة التوافق تتطلب الإصدار PHP 5.3.7 أو أعلى أو إصدار يحتوي على التصحيح ‎$2y (مثل ريدهات).

إذا لم تكن قادرًا على استخدامها فيمكنك تنفيذ عملية تعمية على كلمة المرور باستخدام الدالة crypt()‎، وبما أنّ password_hash()‎ تُنفَّذ كغلاف حول الدالة crypt()‎ فلن تحتاج لفقدان أي وظيفة.

المثال التالي هو تنفيذ بسيط للتعمية بالمعيار bcrypt والتوافق مع password_hash()‎ ومن غير المضمون أن يحافظ على نفس قوة تشفير التنفيذ الكامل للدالة password_hash()‎.

إضافة غُفْل في عملية تعمية كلمة المرور

على الرغم من موثوقية خوارزمية التشفير إلا أنّه ما يزال يوجد ثغرة تستهدف جداول قوس قزح ولذا ينصح باستخدام غُفْل salt، والغفل بالعربية هو شيء ما يُضاف لكلمة المرور قبل تعميتها لجعل السلسلة النصية المصدر فريدة (انظر كتاب «علم التعمية واستخراج المعمى عند العرب»)، بالنظر إلى كلمتي مرور متطابقتين فإنّ نتيجة التعمية لهما ستكون فريدة أيضًا لأن الأغْفَال المضافة إليها فريدة.

يعد إضافة الأغفال العشوائية أحد أهم أجزاء أمان كلمة المرور الخاصة بك، وهذا يعني أنّه حتى مع جدول البحث lookup table لكلمات المرورة المعماة المعروفة فإنّ المهاجم لن يتمكن من مطابقة كلمة المرور المعماة الخاصة بالمستخدم مع كلمة المرور المعماة في قاعدة البيانات بسبب استخدام أغفال مختلفة، يجب أن تستخدم دائمًا أغفال عشوائية وقوية من ناحية التشفير اقرأ المزيد.

باستخدام خوارزمية bcrypt ودالة password_hash()‎ تُخزَّن أغفال النص الأصلي وأغفال النص المعمى الناتج مما يعني أنّه يمكن نقل النص المعمى عبر أنظمة ومنصات مختلفة وستبقى متطابقة مع كلمة المرور الأصلية.

في الإصدارات السابقة للإصدار 7.0 يمكنك استخدام الخيار salt لتعريف الأغفال العشوائية الخاصة بك على الرغم من عدم التشجيع على هذا الإجراء.

$options = [
    'salt' => $salt,
];

ملاحظة: إذا أهملت هذا الخيار ستولّد الدالة password_hash()‎ غُفْلًا عشوائيًا لكل كلمة مرور مقطعة.

بدءًا من الإصدار PHP 7.0.0 أُهمل خيار إضافة الغُفْل ومن المفضل الآن استخدام الغُفْل المولّد افتراضيًا.

ترقية كلمة مرور معماة موجودة إلى خوارزمية أقوى

إذا كنت تستخدم طريقة PASSWORD_DEFAULT لتجعل النظام يختار الخوارزمية الأفضل لتعمية كلمات المرور بها، مع زيادة قوة الخوارزمية الافتراضية قد ترغب في إعادة تعمية كلمات المرور القديمة عندما يسجل المستخدمون الدخول.

<?php
// حدد أولًا إذا كانت كلمة المرور الموفرة صحيحة
if (password_verify($plaintextPassword, $hashedPassword)) {

    // حدد الآن إذا كانت النسخة المعماة الموجود قد أُنشئت بخوارزمية لم تعد افتراضية بعد الآن
    if (password_needs_rehash($hashedPassword, PASSWORD_DEFAULT)) {
        // أنشئ كلمة معماة جديدة مع الخوارزمية الافتراضية الجديدة
        $newHashedPassword = password_hash($plaintextPassword, PASSWORD_DEFAULT);

        // ثم احفظه في مخزن بياناتك
        // $db->update(...);
    }
}
?>

إذا لم تكن الدوال password_*‎ متوفرةً في نظامك ولا تستطيع استخدام حزمة التوافق، فعندها يمكنك تحديد الخوارزمية واستخدامها لإنشاء التعمية الأصلية بطريقة مشابهة للتالي:

<?php
if (substr($hashedPassword, 0, 4) == '$2y$' && strlen($hashedPassword) == 60) {
    echo 'Algorithm is Bcrypt';
    // ‫يحدد "cost" مدى قوة إصدار Bcrypt 
    preg_match('/\$2y\$(\d+)\$/', $hashedPassword, $matches);
    $cost = $matches[1];
    echo 'Bcrypt cost is '.$cost;
}
?>

التحقق من كلمة المرور مقابل كلمة معماة

توفر الدالة password_verify()‎ المدمجة بدءًا من الإصدار PHP 5.5، إمكانية التحقق من صحة كلمة مرور مقابل كلمة معماة مقابلة لها، أو غير مقابلة.

<?php
if (password_verify($plaintextPassword, $hashedPassword)) {
    echo 'Valid Password';
}
else {
    echo 'Invalid Password.';
}
?>

تخزن كل خوارزميات عملية التعمية المدعومة معلومات تحدد آلية التعمية المستخدمة، لذا لا حاجة للإشارة إلى الخوارزمية المستخدمة لتعمية كلمة المرور الأصلية.

إذا لم تكن الدوال password_*‎ متوفرةً في نظامك ولا تستطيع استخدام حزمة التوافق، فيمكنك التحقق من كلمة المرور باستخدام الدالة crypt()‎، لاحظ أنه يجب اتخاذ احتياطات محددة لتجنب هجمات التوقيت.

<?php
// ‫غير مضمون أن يحافظ على نفس قوة تشفير تنفيذ password_hash()‎ الكامل
if (CRYPT_BLOWFISH == 1) {
    // ‫تتجاهل crypt()‎ كل المحارف التي تتجاوز طول الغفل، لذا يمكننا تمرير ?كامل كلمة المرور المعماة
    $hashedCheck = crypt($plaintextPassword, $hashedPassword);
    // ‫هذه موازنة وقت ثابت أساسية تعتمد على التنفيذ الكامل المستخدم في `password_hash()`
    $status = 0;

for ($i=0; $i<strlen($hashedCheck); $i++) {
        $status |= (ord($hashedCheck[$i]) ^ ord($hashedPassword[$i]));
    }
    if ($status === 0) {
        echo 'Valid Password';
    }
    else {
        echo 'Invalid Password';
    }
}
?>

ترجمة -وبتصرف- للفصول Cryptography - Password Hashing Functions من كتاب PHP Notes for Professionals book





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن