TALAL ALMUTAIRI

Members
  • عدد منشوراتي

    2
  • تاريخ الإنضمام

  • تاريخ اخر زياره

السمعه بالموقع

3 Neutral

عن العضو TALAL ALMUTAIRI

  • الرتبه
    مبدع جديد
  1. بسم الله الرحمن الرحيم كيف تحمي بيانات المستخدمين من الإختراق عن طريق Salted Password Hashing مقدمة: إذا كنت مطور تطبيقات ففي الغالب أنك أنشأت في نظامك جزء خاص بإدارة حسابات المستخدمين (User Account Management). ما يهمنا هنا هو كيفية حماية بيانات المستخدمين في قاعدة البيانات. كما لا يخفى على الجميع أن الكثير من المواقع أو التطبيقات المشهورة تعرضت للإختراق وتم الحصول على بيانات المستخدمين. فما هي الطريقة المناسبة لحماية كلمات المرور أو بيانات المستخدمين البنكية. سنركز هنا على كلمات المرور والفكرة واحدة. الطرق المختلفة لحفظ المرور في قاعدة البيانات: - حفظ كلمة المرور كنص واضح (Plaintext) - تشفير كلمة المرور ثم حفظ النص المشفر (Ciphertext) - استخدام الـ Hashing. - استخدام Salted Password Hashing حفظ كلمة المرور كنص واضح (Plaintext) هذه الطريقة هي أبسط الطرق وهي عملية حفظ كلمة المرور كما أدخلها المستخدم. مثلا أدخل 123 فهي تحفظ في قاعدة البيانات كما هي (123). فإذا نسيت كلمة المرور وطلبت من الموقع الذي أنت عضو فيه استعادة كلمة المرور فتم إرسال كلمة المرور كما هي على بريدك الالكتروني فهذا الموقع يستخدم هذه الطريقة أو الطريقة الثانية. هذه الطريقة خطيرة جدا فأبسط عملية اطلاع على قاعدة البيانات يستطيع المخترق أن يعرف كلمة مرور أي مستخدم أو عضو مسجل. فأبتعد عن هذه الطريقة. تشفير كلمة المرور ثم حفظ النص المشفر (Ciphertext) كما نعرف جميعا أن التشفير في أبسط تعريف له هو عملية تحويل المعلومات من صيغتها الحقيقة المفهومة إلى صغية غير مفهومة. أو تحويلها من صيغها الحقيقية إلى صيغة أخرى. ويتم ذلك عن طريق أحد أنواع التشفير التالية: - التشفير المتناظر/ المتماثل (Symmetric key / Private key) حيث يتم التشفير (Encryption) وفك التشفير (Decryption) بنفس المفتاح. - التشفير الغير متناظر/ الغير متماثل (Asymmetric / Public key) هنا يتم التشفير بالمفتاح العام (Public Key) ويتم فك التشفير بالمفتاح الخاص (Private Key) ويكون المفتاح العام معروف عند الجميع أما المفتاح الخاص فلا يعرفه إلا المالك نفسه. أما إذا عكسنا العملية فتم التشفير بالمفتاح الخاص وتم فك التشفير بالمفتاح العام (التحقق من المرسل) فهذا يسمى التوقيع الإلكتروني (Digital Signature). موضوع التشفير وأنواعه لا تهمنا حاليا. ما يهمنا هنا هو عملية تشفير كلمة المرور ثم حفظها في قاعدة البيانات. مثال: لنفرض أن خوارزمية التشفير هي الإزاحة (The Shift Cipher) وأن مفتاح التشفير هو الازاحة 3 خانات. كما نلاحظ تتمت الإزاحة 3 خانات لكل حرف من النص الأصلي فمثلا 123 تصبح 456. ففي هذه الطريقة يتم حفظ 456 في قاعدة البيانات ولو تكرر أن مستخدم أخر يستخدم كلمة المرور 123 فتصبح أيضا 456 مخزنة في قاعدة البيانات. هذه الطريقة أصعب قليلا من الطريقة الأولى ولكن أيضا غير عملية فلو تم اكتشاف مفتاح التشفير فستصبح قاعدة البيانات واضحة ويمكن الحصول على كل كلمات المرور الخاصة بالمستخدمين. يمكن تعقيد هذه الطريقة في حال أصبح لكل مستخدم مفتاح عام (Public key) ومفتاح خاص (Private Key) ولكن يصعب تنفيذ هذه الطريقة فأنت تحتاج لنظام لإنشاء مفاتيح للمستخدمين. فعند تسجيل المستخدم يتم تشفير كلمة المرور الخاصة به بالمفتاح العام. وعندما يقوم المستخدم بعملية تسجيل الدخول فيجب أن يكتب أو يرفق المفتاح الخاص به وهذا ينافي مبدأ التشفير الغير متناظر حيث يمكن معرفة المفتاح الخاص. استخدام الـ Password Hashing هذه الطريقة مستخدمة في العديد من المواقع وهي طريقة عملية ويصعب نوعاً ما أكتشاف كلمات المرور المخزنة في قاعدة البيانات عندما نستخدم هذه الطريقة. ولكن تظل طريقة لها العديد من الطرق التي تخترقها خصوصا في ظل تقدم التقنيات والأجهزة ذات المواصفات العالية. ما هو الـ Hashing : هو أحد علوم الـ (Cryptography) ولكن ليس تشفير (Encryption). فهي طريقة وحيدة الإتجاه (One way function) والتي تقوم بتحويل النص مهما كان طوله إلى نص ثابت (fixed-length). ولا يمكن إرجاع النص الناتج إلى النص الأصلي. أي لا يوجد فك للتشفير. الفرق بين الـ Hashing و التشفير (Encryption) ؟ عندما نشفر الكلمة 123 فتصبح 456 فإننا نستطيع فك التشفير من 456 لتعود إلى 123. ولكن في الـ Hashing عندما نحول 123 إلى 456 فلا يمكن إعادة 456 إلى النص الأصلي 123. كما نلاحظ في الشكل السابق أن النص الناتج من عملية الـ Hashing نص ثابت الطول (10) خانات مهما أختلف طول النص في النص الأصلي في الجهة اليسار. أيضا نلاحظ أن 123 كانت عملية الـ Hashing لها واحدة (Fdeom83nyU) مهما تكررت يكون الناتج واحد. خوارزميات الـ Hashing: يوجد العديد من خوارزميات الـ Hashing من أشهرها: · MD5 Message Digest Algorithm 5 · SHA-1,SHA-2,SHA-3 Secure Hash Algorithm · BLAKE and BLAKE2 كيف يتم كسر كلمات المرور؟ في البداية يمكن إكتشاف كلمة المرور دون النظر إلى كيفية تخزينها في قاعدة البيانات هل هي بنص واضح أو مشفرة أو تم عمل Hashing لها. وهناك خيارين ليتم كسر أو كشف كلمة المرور أو أي بيانات حساسة. - أن يتم الدخول على خوادم أي موقع أي أنه تم تجاوز عدة تحصينات سابقة وتم الحصول على قاعدة البيانات خصوصا جدول المستخدمين مثلا. فهنا إما أن تكون الكلمة مخزنة بشكل واضح وهذه بسيطة. أو تكون مشفرة أو Hashed Password. فهنا يتم التعامل معها بإحدى الطرق التي بالأسفل. - أو يكون المخترق لديه اسم المستخدم ويحاول الحصول على كلمة المرور. بمعنى أنه لا يمتلك قاعدة البيانات ولا يعرف الشكل الذي به تم حفظ كلمة المرور. في الخيار الأول غالب يتم استخدام الأختراق الغير مباشر (Offline Attack) بمعنى أن المخترق لديه كلمة مشفرة أو Hashed ويريد معرفة الكلمة الأصلية (Plaintext) فهنا يتم استخدام برامج تعتمد على الطرق التي بالأسفل لكي يحصل على الكلمة الأصلية دون التعامل المباشر مع الموقع الأصلي الذي تم اختراقه مسبقاً. وغالبا في الخيار الثاني يتم استخدام الاختراق المباشر (Online Attack) حيث يكون لدى المخترق اسم المستخدم ويقوم بإستخدام برامج أيضا تعتمد على الطرق التي بالأسفل ويتم إرسال طلبات إلى الموقع المسجل لديه بيانات المستخدم. كما عرفنا سابقا أن Hashing وحيدة الإتجاه (One way function) أي لا يمكن إعادة النص الأصلي (Plaintext). ومع ذلك فإنه يمكن أن تكتشف الكلمة الأصلي ويتم ذلك بعدة طرق. وهذه الطرق إما أن تعتمد على مبدأ التخمين أو البحث في قاعدة بيانات كبيرة (جدول) تحتوي على العديد من كلمات المرور مع الـ hash المقابل لها. ويتم ذلك ضمن خورزميات وعمليات حسابية طويلة ومقعدة لتضمن فعالية هذه الطرق. ومن هذه الطرق: 1- Dictionary Attack هذه الطريقة تعتمد على مبدأ التخمين (Guessing ) حيث يكون هناك ملف يحتوي على قائمة كبيرة من الكلمات المستخدمة أو المشهورة بإنها تستخدم ككلمة مرور. أو تكون هذه القائمة متوقعة بالنسبة لشخص محدد مثل تاريخ الميلاد رقم الهاتف الخ. ويمكن استخدام هذه الطريقة من خلال (Online Attack) أو (Offline Attack) وفي الـ Offline Attack تقوم بعض البرامج أثناء التنفيذ بعمل الـ Hash المقابل لكل كلمة في الملف ويتم مقارنتها مع الHashed Password التي يمتلكها المخترق وفي حال التطابق يتم معرفة كلمة المرور الأصلية. مثال: لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي Car ( بالتأكيد لا يعرفها المخترق). ستتم هذه الطريقة حسب الملف الذي يمتلكه المخترق (الشكل بالأسفل) ستتم تجربة كلمات الملف كالتالي 2- Brute Force Attack هذه الطريقة شبيهه بالطريقة السابقة حيث أنها تعتمد أيضا على التخمين ولكن التخمين بالاعتماد على عمليات حسابية حيث يتم تجربة كل الاحتمالات الممكنة مثلا تجربة كل الاحتمالات لكلمة مرور ذات طول 6 خانات عبارة عن أرقام أو أحرف. فهنا الموضوع لا يعتمد على جدول أو ملف إنما تجربة أثناء وقت التنفيذ. وهذه الطريقة تأخذ وقت طويل يزيد وينقص حسب طول كلمة المرور. مثال : لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي 111333 ( بالتأكيد لا يعرفها المخترق). تعمل هذه الطريقة بهذا الشكل: سيتم تجربة كل الاحتمالات الممكنة حتى يصل إلى كلمة المرور. وقد تستمر إلى وقت طويل جداً حتى يتم إيجاد كلمة المرور. أيضا هنا تم استخدام (Online Attack) حيث أن المخترق يرسل للخادم في كل مرة اسم المستخدم مع احتمال لكلمة المرور بشكل متسلسل. ويمكن استخدام الـ (Offline Attack) إذا كانت كلمة المرور محفوظة كـ Hash حيث يكون لدى المخترق الـ Hashed Password فيمكن اكتشاف كلمة المرور بنفس الطريقة ولكن يتم أولا عمل Hash أُثناء التنفيذ لكل سلسلة من الحروف ثم مقارنتها مع الـ Hashed Password. لنفرض أن المخترق لديه هذه الكلمة (Hashed Password) التالية: 62b37844f7adeb0df6f180126327dca339fb7003 3- Lookup Tables أو ما يسمى (Pre-Computed dictionary attack) لنعرف أولا معنى Pre-Computed أو Precomputation يقصد بها: عملية تجهيز الجدول قبل وقت التنفيذ (Run Time) ففي الطريقة السابقة (Dictionary attack) لدينا قائمة كبيرة من الكلمات وفي الـ (Offline Attack) يتم عمل Hash لكل كلمة من القائمة أثناء التنفيذ ومقارنتها مع الكلمة التي يتملتكها المخترق (Hashed Password) فهذه الطريقة تأخذ وقت كبير ولكن هنا يتم إعداد جدول أو قائمة يكون مقابل كل كلمة الـ Hash الخاص بها حسب خوارزمية الـ Hashing. وهذا الجدول يستفيد منه المخترق دائما دون عمل الـHash مرة أخرى لكل كلمة أثناء التنفيذ. وتكون العملية فقط بحث عن الـ Hash المطابقة للكلمة التي لدى المخترق ففي حالة التطابق يتكون كلمة المرور هي المقابلة للـ Hash مثال: لنفرض أن لدى المخترق هذه الـ Hashed Password e9989db5dabeea617f40c8dbfd07f5fb ولديه هذا الجدول ( هذا الجدول تم إنشائه مسبقا Pre-Computed ) وليس أثناء وقت التنفيذ. فالعملية هي عملية بحث أو استعلام عادية للحصول على الـ Hash الموجودة في الجدول والمطابقة لما لدى المخترق (e9989db5dabeea617f40c8dbfd07f5fb). وهنا تكون كلمة المرور الأصلية هي Car. وطبعا هذه الطريقة تختصر الوقت لكن تعتمد كفائتها على كمية الكلمات الموجودة في الجدول. يوجد بعض البرامج التي تقوم بإنشاء هذه الجداول حسب بعض المعطيات لدى المخترق ( أي لا يشترط أن يكون هناك قائمة جاهزة للكلمات المشهورة إنما يمكن إنشائها) من هذه المعطيات أن يتم إنشاء جدول (Lookup tables) للكلمات ذات الطول من 1 إلى 7 خانات وتكون عبارة عن أرقام فقط. ويمكن أن تكون أرقام أو حروف أو رموز. كلما زادت المعطيات زاد وقت إنشاء هذه الجداول وزادت مساحة التخزين. 4- Rainbow Tables هو نوع مخصص من Lookup table بنفس الفكرة والاعتماد على (precomputation ). ولكن تعمل تحت مبدأ (space/time trade-off) وهذا يعني زيادة مساحة التخزين في سبيل تقليل وقت التنفيذ أو وقت الحصول على النتيجة. ولكن وقت التنفيذ أقل من Brute Force Attack وكذلك مساحة التخزين أقل من Lookup table. الفرق بين الـ Rainbow table و Lookup table هو في طريقة إنشاء جدول الكلمات حيث يعتمد الـ Rainbow table على دالة الاختزال أو التقليص (Reduction function) والتي تعمل على تقليل حجم الجدول الناشئ حيث يتم استبعاد حفظ بعض كلمات المرور والـ Hash المقابل لها. شرح التفاصيل الخاصة بالـ Rainbow table and Reduction function يحتاج موضوع مستقل. لكن سأذكر في النهاية بعض المواضيع التي تشرحها بشئ من التفصيل. ملاحظة: تم توضيح الطرق السابقة في أبسط صورها وقد تم إضافة الكثير من التحسينات وتم استخدام خوارزميات معقدة تزيد من فعاليتها. ومعرفة هذه الطرق وتفاصيلها أحد الأسباب الرئيسية لحماية بياناتك أو حماية بيانات المستخدمين لديك وتساعدك في فرض عدة قيود صلبة تجعل عملية الإختراق صعبة أو شبه مستحيلة. استخدام الـ Salted Password Hashing هذه الطريقة هي الهدف الرئيسي من هذا الموضوع. كما ذكرنا سابقاً أن عملية الـ Hashing للكلمة 123 هو Fdeom83nyU ( مثال فقط) فهنا كلما تكررت 123 ككلمة مرور لأي مستخدم ستكون النتيجة هي Fdeom83nyU وهذه النقطة تساعد المخترق على محاولة إستنتاج كلمة المرور من النص Fdeom83nyU بإستخدام الطرق السابقة أو غيرها. أما طريقة Salted Password تعمل على أن يكون الـ Hash للكلمة 123 مختلف في كل مرة وذلك بإضافة نص عشوائي للكلمة الأصلية 123 ثم عمل الـ Hashing لها كما في الشكل التالي: نلاحظ في الشكل السابق إختلاف الناتج من عملية الـ Hashing في كلمة مرور حتى وإن كانت كلمة المرور واحدة. الهدف من الكلمة Salt والتي تعنى ملح هو شيء مشابه للفائدة من الملح وهو تحسين الطعام أيضا هنا النص المضاف Salt يعني إضافة نص عشوائي إلى كلمة المرور الأصلية لتحسينها حتى تكون قوية يصعب كسرها أو إكتشافها. طريقة عمل Salted Password Hashing: أولا في مرحلة تسجيل البيانات حيث يقوم المستخدم بتعبئة بياناته على الموقع ثم يقوم بالحفظ. حيث تتم الخطوات التالية: 1- يدخل المستخدم كلمة المرور لنفرض 123 2- يقوم البرنامج بإنشاء نص مضاف (Salt) بشكل عشوائي لنفرض Nd29kd63w1po 3- يتم دمج كلمة المرور مع النص العشوائي المضاف لتصبح 123Nd29kd63w1po 4- يتم عمل Hash للنص 123Nd29kd63w1po وسيكون الناتج jdhsi3jf92 ( طبعا النص أطول من ذلك حسب خوارزمية الـ Hash ). 5- يتم حفظ الناتج jdhsi3jf92 و النص العشوائي المضاف Nd29kd63w1po في قاعدة البيانات مع بيانات المستخدم الأخرى كالبريد الالكتروني والاسم ..الخ. ثانيا عند تسجيل الدخول يتم التالي: 1- يُدخل المستخدم اسم المستخدم وكلمة المرور لنفرض 123 2- يتم الاستعلام عن بيانات المستخدم بواسطة اسم المستخدم من قاعدة البيانات للحصول على الـ Salt و الـ Hashed Password ونفرض أن Salt هو Nd29kd63w1po والـ Hashed Password هي jdhsi3jf92. 3- يتم دمج كلمة المرور المدخلة 123 مع النص Salt وهو Nd29kd63w1po لتصبح 123Nd29kd63w1po ثم يتم عمل Hashing لها ليصبح الناتج jdhsi3jf92 4- ثم يتم مطابقة النص الناتج jdhsi3jf92 مع كلمة المرور المخزنة في قاعدة البيانات Hashed Password وهي jdhsi3jf92 وفي حال التطابق يتم تسجيل الدخول. نصائح مهمة عند عمل Salted Password Hashing: 1- عدم إعادة استخدام الـ Salt نفسه مع كل كلمة مرور حيث يجب أن يكون عشوائي مع كل كلمة مرور. 2- لا تستخدم Salt قصير يجب أن يكون نص عشوائي طويل على الأقل 16 حرف ( أيضا يمكنك الاعتماد على نوع خوارزمية الـ Hash لتحديد طول الـ Salt ) 3- استخدام خوارزميات الـ Hash مثل (MD5,SHA1,SHA2,SHA3) في Salted Password Hashing يعتبر آمن ولكن هذه الخورزميات تعتبر قديمة والمخترقين لديهم عدة طرق لكسر كلمات المرور التي تم إنشائها بواسطتها وحتى مع وجود الـ Salt هي مجرد مسألة وقت ويمكن كسرها. لذلك يفضل عدم استخدامها بل استخدام (password-based key derivation function) مثل PBKDF2, bcrypt, scrypt 4- لا تقوم بإنشاء دالة خاصة تقوم بتوليد (Generate) نص عشوائي Salt بل يفضل استخدام الدوال الجاهزة حسب لغة البرمجة كما في الشكل التالي: تطبيق: بلغة (ASP.Net (C#,VB)) مع قاعدة البيانات SQL Server الفكرة: سيتم إن شاء الله إنشاء تطبيق بسيط لعملية تسجيل أو إنشاء المستخدمين أيضا تسجيل الدخول. حيث يقوم المستخدم بتعبئة بياناته في صفحة التسجيل (Registration) وهي اسم المستخدم و الاسم كاملأ وكلمة المرور. ثم يقوم البرنامج بعمل Salted Password ( الـ Salt و Hashed Password ) ثم يتم حفظ البيانات في قاعدة البيانات. وعندما تتم عملية التسجيل يمكن للمستخدم تسجيل الدخول عن طريق صفحة الدخول (Login) حيث يتم كتابة اسم المستخدم وكلمة المرور حسب الخطوات التي تم ذكرها بالأعلى في جزئية طريقة عمل Salted Password Hashing. التطبيق يعتمد على PBKDF2. وهي خورازمية للـ (Password-Based Key Derivation Function) وليست للـ Hashing بشكل مباشر مثل (MD5,SHA) والهدف منها استخدام إحدى خوارزميات الـ Hashing المباشرة ولكن بطريقة تعمل على زيادة قوة الـ Hash الناتج من كلمة المرور الأصلية بحيث يصعب كسر أو إكتشاف كلمة المرور من خلال Rainbow tables أو Brute force attack. ويتم ذلك من خلال عدة نقاط مثل طول الـ Salt أيضا عدد الـ Iterations والذي يعمل على تحسين الـ Hash الناتج من كلمة المرور الأصلية. لنفرض أن عدد الـ هو Iterations 30000 هنا تتم عملية التحسين 30000 مرة وهذا مما يزيد في صعوبة إكتشاف كلمة المرور ولكن من الطبيعي يكون هناك بطئ أثناء وقت التفيذ حسب عدد الـ Iterations. سأذكر إن شاء الله في نهاية الموضوع بعض المصادر التي تشرح key derivation function و PBKDF2 وكذلك Key stretching. الـ PBKDF2 في الـ .Net يمكن استخدام الـ Rfc2898DeriveBytes والذي يعتمد على SHA-1. قاعدة البيانات: تم إنشاء جدول للمستخدمين يحتوى على عدة حقول كما في الشكل أدناه. CREATE TABLE UsersAccounts ( Username nvarchar(30) PRIMARY KEY, FullName nvarchar(200) NOT NULL, Salt nvarchar(100) NOT NULL, HashedPassword nvarchar(100) NOT NULL ) الفئات (Classes) نحتاج لـ Two Classes الأول خاص بقاعدة البيانات (SQLHelper) والثاني خاص بالـ Hashing وهو (SaltedPassword) وهذا الأخير و لب هذا التطبيق. فئة (Class) للتعامل مع قاعدة البيانات (SQLHelper) يحتوي على دالتين - دالة الاستعلام حيث يتم إرسال جملة الإستعلام ويتم حفظ النتائج في جدول (DataTable) - دالة تنفيذ جمل الإضافة والتعديل والحذف ويتم من خلالها إرسال البيانات وحفظها في قاعدة البيانات. SQLHelper Class C# using System; using System.Data; using System.Data.SqlClient; public class SQLHelper { public SQLHelper() { // // TODO: Add constructor logic here // } public static readonly string ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["connectionString1"].ConnectionString; // Select Queries public static DataTable ExecuteQuery(string commandText, CommandType commandType, SqlParameter[] parameters) { DataTable table = new DataTable(); try { // ConnectionString كائن الإتصال مسؤول عن فتح وإغلاق الاتصال بقاعدة البيانات حسب المسار في using (SqlConnection connection = new SqlConnection(ConnectionString)) { // كائن الأوامر يتم من خلاله تجهيزة جملة الاستعلام والإضافة ليتم تنفيذها على قاعدة البيانات using (SqlCommand command = new SqlCommand(commandText, connection)) { // تحديد نوع كائن الأوامر // Text: like (Select * from table ...) // StoredProcedure: will be the name of StoredProcedure command.CommandType = commandType; // هي القيم التي يتم تمريرها مثل رقم الطالب والتي تكون مدخله من المستخدم parameters الـ if (parameters != null) command.Parameters.AddRange(parameters); connection.Open(); // DataSet مسؤول عن تعبئة النتائج في جدول أو SqlDataAdapter adapter = new SqlDataAdapter(command); adapter.Fill(table); } } } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); return null; } return table; } // Insert,Update and Delete public static bool ExecuteNonQuery(string commandText, CommandType commandType, SqlParameter[] parameters) { try { using (SqlConnection connection = new SqlConnection(ConnectionString)) { using (SqlCommand command = new SqlCommand(commandText, connection)) { command.CommandType = commandType; if (parameters != null) command.Parameters.AddRange(parameters); connection.Open(); if (command.ExecuteNonQuery() > 0) { return true; } else return false; } } } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); return false; } } } VB.Net Imports Microsoft.VisualBasic Imports System.Data Imports System.Data.SqlClient Public Class SQLHelper Private Shared ConnectionString As String = System.Configuration.ConfigurationManager.ConnectionStrings("connectionString1").ConnectionString 'Select Queries Public Shared Function ExecuteQuery(commandText As String, commandType As CommandType, parameters As SqlParameter()) As DataTable Dim table As New DataTable() Try Using connection As New SqlConnection(ConnectionString) Using command As New SqlCommand(commandText, connection) command.CommandType = commandType If parameters IsNot Nothing Then command.Parameters.AddRange(parameters) End If connection.Open() Dim adapter As New SqlDataAdapter(command) adapter.Fill(table) End Using End Using Catch ex As Exception EventsLogger.SaveToLog(ex.Message) Return Nothing End Try Return table End Function ' Insert,Update and Delete Public Shared Function ExecuteNonQuery(commandText As String, commandType As CommandType, parameters As SqlParameter()) As Boolean Try Using connection As New SqlConnection(ConnectionString) Using command As New SqlCommand(commandText, connection) command.CommandType = commandType If parameters IsNot Nothing Then command.Parameters.AddRange(parameters) End If connection.Open() If command.ExecuteNonQuery() > 0 Then Return True Else Return False End If End Using End Using Catch ex As Exception EventsLogger.SaveToLog(ex.Message) Return False End Try End Function End Class فئة (Class) للتعامل مع الـ (Salted Password Hashing) يحتوي على ثلاثة دوال: - دالة لإنشاء النص العشوائي المضاف Salt .وهي GenerateSalt - دالة لعمل الـ Hash . وهي HashPasswordUsingPBKDF2 - دالة للمطابقة عند تسجيل الدخول للتحقق من اسم المستخدم وكلمة المرور. وهي VerifyPassword SaltedPassword Class C# using System; using System.Security.Cryptography; public class SaltedPassword { public SaltedPassword() { // // TODO: Add constructor logic here // } private const int pbkdf2NoOfIterations = 30000; private const int hashSize = 32; private const int saltSize = 32; /// <summary> /// Hashing دالة لإنشاء نص عشوائي ليتم إضافته إلى كلمة المرور قبل عملية الـ /// </summary> /// <returns></returns> public static string GenerateSalt() { RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] salt = new byte[saltSize]; rng.GetBytes(salt); return Convert.ToBase64String(salt); } /// <summary> /// Hash دالة تقوم بإنشاء كلمة المرور كـ /// </summary> /// <param name="Password">كلمة المرور الأصلية التي أدخلها المستخدم</param> /// <param name="Salt"> النص المضاف التي تم إنشائه سابقا</param> /// <returns></returns> public static string HashPasswordUsingPBKDF2(string Password,string Salt) { byte[] bSalt = Convert.FromBase64String(Salt); Rfc2898DeriveBytes PBKDF2 = new Rfc2898DeriveBytes(Password, bSalt, pbkdf2NoOfIterations); byte[] key = PBKDF2.GetBytes(hashSize); return Convert.ToBase64String(key); } /// <summary> /// دالة للتحقق من كلمة المرور المدخلة أثناء تسجيل الدخول /// </summary> /// <param name="UserPassword">كلمة المرور المدخلة من قبل المستخدم أثناء تسجيل الدخول</param> /// <param name="Salt">النص المضاف المحفوظ في قاعدة البيانات</param> /// <param name="HashedPassword">Hash كلمة المرور التي تم حفظها في قاعدة البيانات كـ </param> /// <returns></returns> public static bool VerifyPassword(string UserPassword, string Salt,string HashedPassword) { string hash = HashPasswordUsingPBKDF2(UserPassword, Salt); // المقارنة بين القيمتين للتأكد من صحة كلمة المرور // New Hash with Hashed Password (from Database) if (String.Compare(hash, HashedPassword, false) == 0 ) return true; // كلمة المرور صحيحة else return false; } } VB.Net Imports Microsoft.VisualBasic Imports System.Security.Cryptography Public Class SaltedPassword Private Const pbkdf2NoOfIterations As Integer = 30000 Private Const hashSize As Integer = 32 Private Const saltSize As Integer = 32 ''' <summary> ''' Hashing دالة لإنشاء نص عشوائي ليتم إضافته إلى كلمة المرور قبل عملية الـ ''' </summary> ''' <returns></returns> Public Shared Function GenerateSalt() As String Dim rng As New RNGCryptoServiceProvider() Dim salt As Byte() = New Byte(saltSize - 1) {} rng.GetBytes(salt) Return Convert.ToBase64String(salt) End Function ''' <summary> ''' Hash دالة تقوم بإنشاء كلمة المرور كـ ''' </summary> ''' <param name="Password">كلمة المرور الأصلية التي أدخلها المستخدم</param> ''' <param name="Salt"> النص المضاف التي تم إنشائه سابقا</param> ''' <returns></returns> Public Shared Function HashPasswordUsingPBKDF2(Password As String, Salt As String) As String Dim bSalt As Byte() = Convert.FromBase64String(Salt) Dim PBKDF2 As New Rfc2898DeriveBytes(Password, bSalt, pbkdf2NoOfIterations) Dim key As Byte() = PBKDF2.GetBytes(hashSize) Return Convert.ToBase64String(key) End Function ''' <summary> ''' دالة للتحقق من كلمة المرور المدخلة أثناء تسجيل الدخول ''' </summary> ''' <param name="UserPassword">كلمة المرور المدخلة من قبل المستخدم أثناء تسجيل الدخول</param> ''' <param name="Salt">النص المضاف المحفوظ في قاعدة البيانات</param> ''' <param name="HashedPassword">Hash كلمة المرور التي تم حفظها في قاعدة البيانات كـ </param> ''' <returns></returns> Public Shared Function VerifyPassword(UserPassword As String, Salt As String, HashedPassword As String) As Boolean Dim hash As String = HashPasswordUsingPBKDF2(UserPassword, Salt) ' المقارنة بين القيمتين للتأكد من صحة كلمة المرور ' New Hash with Hashed Password (from Database) If [String].Compare(hash, HashedPassword, False) = 0 Then Return True Else ' كلمة المرور صحيحة Return False End If End Function End Class صفحة التسجيل (Registration.aspx) Design <div class="center" dir="rtl"> <table class="center"> <tr> <td class="td">اسم المستخدم:</td> <td> <input id="txtUsername" type="text" runat="server" class="textBox" maxlength="20" required="required" /></td> </tr> <tr> <td class="td">الاسم:</td> <td> <input id="txtName" type="text" runat="server" class="textBox" maxlength="200" required="required" /></td> </tr> <tr> <td class="td">كلمة المرور:</td> <td> <input id="txtPassword" type="password" runat="server" class="textBox" maxlength="20" required="required" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="كلمة المرور يجب أن تكون 8 خانات وخليط من الارقام والحروف الصغيرة والكبيرة والرموز" /></td> </tr> <tr> <td colspan="2"> <asp:Button ID="btnRegister" runat="server" Text="تسجيل" CssClass="button" OnClick="btnRegister_Click" /></td> </tr> </table> <br /> <asp:Label ID="lbMessage" runat="server" CssClass="lableMsg"></asp:Label> </div> الكود بداخل زر التسجيل (btnRegister) C# protected void btnRegister_Click(object sender, EventArgs e) { try { string salt = SaltedPassword.GenerateSalt(); string hashedPassword = SaltedPassword.HashPasswordUsingPBKDF2(txtPassword.Value, salt); // Save Into database // نستخدم هذه الطريقة للحماية من // Sql Injection // command حيث نمرر القيم داخل SqlParameter[] parameters = new SqlParameter[]{ new SqlParameter("@Username",txtUsername.Value), new SqlParameter("@FullName",txtName.Value), new SqlParameter("@Salt",salt), new SqlParameter("@HashedPWD",hashedPassword), }; // ويفضل استخدام الإجراءات المخزنة // Stored Procedures if (SQLHelper.ExecuteNonQuery("INSERT INTO UsersAccounts VALUES(@Username,@FullName,@Salt,@HashedPWD)", CommandType.Text, parameters)) lbMessage.Text = "تم تسجيل البيانات بنجاح"; else lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات"; } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات"; } } VB.Net Protected Sub btnRegister_Click(sender As Object, e As EventArgs) Try Dim salt As String = SaltedPassword.GenerateSalt() Dim hashedPassword As String = SaltedPassword.HashPasswordUsingPBKDF2(txtPassword.Value, salt) ' Save Into database ' نستخدم هذه الطريقة للحماية من ' Sql Injection ' command حيث نمرر القيم داخل Dim parameters As SqlParameter() = New SqlParameter() {New SqlParameter("@Username", txtUsername.Value), New SqlParameter("@FullName", txtName.Value), New SqlParameter("@Salt", salt), New SqlParameter("@HashedPWD", hashedPassword)} ' ويفضل استخدام الإجراءات المخزنة ' Stored Procedures If SQLHelper.ExecuteNonQuery("INSERT INTO UsersAccounts VALUES(@Username,@FullName,@Salt,@HashedPWD)", CommandType.Text, parameters) Then lbMessage.Text = "تم تسجيل البيانات بنجاح" Else lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات" End If Catch ex As Exception EventsLogger.SaveToLog(ex.Message) lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات" End Try End Sub صفحة تسجيل الدخول (Login.aspx) Design <div class="center" dir="rtl"> <table class="center"> <tr> <td class="td">اسم المستخدم:</td> <td> <input id="txtUsername" type="text" runat="server" class="textBox" maxlength="20" required="required" /></td> </tr> <tr> <td class="td">كلمة المرور:</td> <td> <input id="txtPassword" type="password" runat="server" class="textBox" maxlength="20" required="required" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="كلمة المرور يجب أن تكون 8 خانات وخليط من الارقام والحروف الصغيرة والكبيرة والرموز" /></td> </tr> <tr> <td colspan="2"> <asp:Button ID="btnLogin" runat="server" Text="تسجيل دخول" CssClass="button" OnClick="btnLogin_Click"/></td> </tr> </table> <br /> <asp:Label ID="lbMessage" runat="server" CssClass="lableMsg"></asp:Label> </div> الكود بداخل زر تسجيل الدخول (btnLogin) C# protected void btnLogin_Click(object sender, EventArgs e) { try { SqlParameter[] parameters = new SqlParameter[]{ new SqlParameter("@Username",txtUsername.Value), }; // الحصول أولا على معلومات المستخدم من خلال الاستعلام بإسم المستخدم DataTable userInfo = SQLHelper.ExecuteQuery("SELECT * FROM UsersAccounts WHERE Username=@Username", CommandType.Text, parameters); if (userInfo == null || userInfo.Rows.Count <= 0) lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة"; else { string salt = userInfo.Rows[0]["Salt"].ToString(); string hashedPassword = userInfo.Rows[0]["HashedPassword"].ToString(); if (SaltedPassword.VerifyPassword(txtPassword.Value, salt, hashedPassword)) lbMessage.Text = "تم تسجيل الدخول بنجاح"; else lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة"; } } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); lbMessage.Text = "حدث خطأ أثناء تسجيل الدخول"; } } VB.Net Protected Sub btnLogin_Click(sender As Object, e As EventArgs) Try Dim parameters As SqlParameter() = New SqlParameter() {New SqlParameter("@Username", txtUsername.Value)} ' الحصول أولا على معلومات المستخدم من خلال الاستعلام بإسم المستخدم Dim userInfo As DataTable = SQLHelper.ExecuteQuery("SELECT * FROM UsersAccounts WHERE Username=@Username", CommandType.Text, parameters) If userInfo Is Nothing OrElse userInfo.Rows.Count <= 0 Then lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة" Else Dim salt As String = userInfo.Rows(0)("Salt").ToString() Dim hashedPassword As String = userInfo.Rows(0)("HashedPassword").ToString() If SaltedPassword.VerifyPassword(txtPassword.Value, salt, hashedPassword) Then lbMessage.Text = "تم تسجيل الدخول بنجاح" Else lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة" End If End If Catch ex As Exception EventsLogger.SaveToLog(ex.Message) lbMessage.Text = "حدث خطأ أثناء تسجيل الدخول" End Try End Sub ويكون شكل البيانات في الجدول كالتالي: الخلاصة: موضوع حماية بيانات المستخدمين متشعب كما نلاحظ في بعض النقاط السابقة. حاولت التركيز على الموضوع الرئيسي وهو استخدام الـ Salted Password Hashing وتم المرور على بعض المفاهيم الأخرى التي بعضها الحقيقة يحتاج إلى موضوع مستقل ولكن نسأل الله التيسر حتى استطيع شرح بعض هذه المفاهيم في مواضيع أخرى مثل Rainbow table أو password-based key derivation function. خلاصة هذه الموضوع التركيز على هذه النقاط. - عند تسجيل بيانات المستخدمين مثل كلمة المرور يجب أن لا تُقبل كلمات المرور القصيرة أو السهلة يفضل إجبار المستخدم على كلمة مرور معقدة. - استخدم الـ Salted Password Hashing. - العمل بالنقاط الموجودة في جزئية نصائح مهمة عند عمل Salted Password Hashingالموجودة بالاعلى. مراجع للإطلاع: - Salted Password Hashing - Doing it Right https://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right - How Rainbow Tables work http://kestas.kuliukas.com/RainbowTables - Rainbow Tables https://stichintime.wordpress.com/2009/04/09/rainbow-tables-part-1-introduction - PBKDF2 https://en.wikipedia.org/wiki/PBKDF2 - Key Derivation Function https://en.wikipedia.org/wiki/Key_derivation_function تمت بحمد الله. التطبيق في المرفقات Demo.rar Talal Almutairi https://twitter.com/talalsql
  2. بسم الله الرحمن الرحيم كيف تحمي بيانات المستخدمين من الإختراق عن طريق Salted Password Hashing مقدمة: إذا كنت مطور تطبيقات ففي الغالب أنك أنشأت في نظامك جزء خاص بإدارة حسابات المستخدمين (User Account Management). ما يهمنا هنا هو كيفية حماية بيانات المستخدمين في قاعدة البيانات. كما لا يخفى على الجميع أن الكثير من المواقع أو التطبيقات المشهورة تعرضت للإختراق وتم الحصول على بيانات المستخدمين. فما هي الطريقة المناسبة لحماية كلمات المرور أو بيانات المستخدمين البنكية. سنركز هنا على كلمات المرور والفكرة واحدة. الطرق المختلفة لحفظ المرور في قاعدة البيانات: - حفظ كلمة المرور كنص واضح (Plaintext) - تشفير كلمة المرور ثم حفظ النص المشفر (Ciphertext) - استخدام الـ Hashing. - استخدام Salted Password Hashing حفظ كلمة المرور كنص واضح (Plaintext) هذه الطريقة هي أبسط الطرق وهي عملية حفظ كلمة المرور كما أدخلها المستخدم. مثلا أدخل 123 فهي تحفظ في قاعدة البيانات كما هي (123). فإذا نسيت كلمة المرور وطلبت من الموقع الذي أنت عضو فيه استعادة كلمة المرور فتم إرسال كلمة المرور كما هي على بريدك الالكتروني فهذا الموقع يستخدم هذه الطريقة أو الطريقة الثانية. هذه الطريقة خطيرة جدا فأبسط عملية اطلاع على قاعدة البيانات يستطيع المخترق أن يعرف كلمة مرور أي مستخدم أو عضو مسجل. فأبتعد عن هذه الطريقة. تشفير كلمة المرور ثم حفظ النص المشفر (Ciphertext) كما نعرف جميعا أن التشفير في أبسط تعريف له هو عملية تحويل المعلومات من صيغتها الحقيقة المفهومة إلى صغية غير مفهومة. أو تحويلها من صيغها الحقيقية إلى صيغة أخرى. ويتم ذلك عن طريق أحد أنواع التشفير التالية: - التشفير المتناظر/ المتماثل (Symmetric key / Private key) حيث يتم التشفير (Encryption) وفك التشفير (Decryption) بنفس المفتاح. - التشفير الغير متناظر/ الغير متماثل (Asymmetric / Public key) هنا يتم التشفير بالمفتاح العام (Public Key) ويتم فك التشفير بالمفتاح الخاص (Private Key) ويكون المفتاح العام معروف عند الجميع أما المفتاح الخاص فلا يعرفه إلا المالك نفسه. أما إذا عكسنا العملية فتم التشفير بالمفتاح الخاص وتم فك التشفير بالمفتاح العام (التحقق من المرسل) فهذا يسمى التوقيع الإلكتروني (Digital Signature). موضوع التشفير وأنواعه لا تهمنا حاليا. ما يهمنا هنا هو عملية تشفير كلمة المرور ثم حفظها في قاعدة البيانات. مثال: لنفرض أن خوارزمية التشفير هي الإزاحة (The Shift Cipher) وأن مفتاح التشفير هو الازاحة 3 خانات. كما نلاحظ تتمت الإزاحة 3 خانات لكل حرف من النص الأصلي فمثلا 123 تصبح 456. ففي هذه الطريقة يتم حفظ 456 في قاعدة البيانات ولو تكرر أن مستخدم أخر يستخدم كلمة المرور 123 فتصبح أيضا 456 مخزنة في قاعدة البيانات. هذه الطريقة أصعب قليلا من الطريقة الأولى ولكن أيضا غير عملية فلو تم اكتشاف مفتاح التشفير فستصبح قاعدة البيانات واضحة ويمكن الحصول على كل كلمات المرور الخاصة بالمستخدمين. يمكن تعقيد هذه الطريقة في حال أصبح لكل مستخدم مفتاح عام (Public key) ومفتاح خاص (Private Key) ولكن يصعب تنفيذ هذه الطريقة فأنت تحتاج لنظام لإنشاء مفاتيح للمستخدمين. فعند تسجيل المستخدم يتم تشفير كلمة المرور الخاصة به بالمفتاح العام. وعندما يقوم المستخدم بعملية تسجيل الدخول فيجب أن يكتب أو يرفق المفتاح الخاص به وهذا ينافي مبدأ التشفير الغير متناظر حيث يمكن معرفة المفتاح الخاص. استخدام الـ Password Hashing هذه الطريقة مستخدمة في العديد من المواقع وهي طريقة عملية ويصعب نوعاً ما أكتشاف كلمات المرور المخزنة في قاعدة البيانات عندما نستخدم هذه الطريقة. ولكن تظل طريقة لها العديد من الطرق التي تخترقها خصوصا في ظل تقدم التقنيات والأجهزة ذات المواصفات العالية. ما هو الـ Hashing : هو أحد علوم الـ (Cryptography) ولكن ليس تشفير (Encryption). فهي طريقة وحيدة الإتجاه (One way function) والتي تقوم بتحويل النص مهما كان طوله إلى نص ثابت (fixed-length). ولا يمكن إرجاع النص الناتج إلى النص الأصلي. أي لا يوجد فك للتشفير. الفرق بين الـ Hashing و التشفير (Encryption) ؟ عندما نشفر الكلمة 123 فتصبح 456 فإننا نستطيع فك التشفير من 456 لتعود إلى 123. ولكن في الـ Hashing عندما نحول 123 إلى 456 فلا يمكن إعادة 456 إلى النص الأصلي 123. كما نلاحظ في الشكل السابق أن النص الناتج من عملية الـ Hashing نص ثابت الطول (10) خانات مهما أختلف طول النص في النص الأصلي في الجهة اليسار. أيضا نلاحظ أن 123 كانت عملية الـ Hashing لها واحدة (Fdeom83nyU) مهما تكررت يكون الناتج واحد. خوارزميات الـ Hashing: يوجد العديد من خوارزميات الـ Hashing من أشهرها: · MD5 Message Digest Algorithm 5 · SHA-1,SHA-2,SHA-3 Secure Hash Algorithm · BLAKE and BLAKE2 كيف يتم كسر كلمات المرور؟ في البداية يمكن إكتشاف كلمة المرور دون النظر إلى كيفية تخزينها في قاعدة البيانات هل هي بنص واضح أو مشفرة أو تم عمل Hashing لها. وهناك خيارين ليتم كسر أو كشف كلمة المرور أو أي بيانات حساسة. - أن يتم الدخول على خوادم أي موقع أي أنه تم تجاوز عدة تحصينات سابقة وتم الحصول على قاعدة البيانات خصوصا جدول المستخدمين مثلا. فهنا إما أن تكون الكلمة مخزنة بشكل واضح وهذه بسيطة. أو تكون مشفرة أو Hashed Password. فهنا يتم التعامل معها بإحدى الطرق التي بالأسفل. - أو يكون المخترق لديه اسم المستخدم ويحاول الحصول على كلمة المرور. بمعنى أنه لا يمتلك قاعدة البيانات ولا يعرف الشكل الذي به تم حفظ كلمة المرور. في الخيار الأول غالب يتم استخدام الأختراق الغير مباشر (Offline Attack) بمعنى أن المخترق لديه كلمة مشفرة أو Hashed ويريد معرفة الكلمة الأصلية (Plaintext) فهنا يتم استخدام برامج تعتمد على الطرق التي بالأسفل لكي يحصل على الكلمة الأصلية دون التعامل المباشر مع الموقع الأصلي الذي تم اختراقه مسبقاً. وغالبا في الخيار الثاني يتم استخدام الاختراق المباشر (Online Attack) حيث يكون لدى المخترق اسم المستخدم ويقوم بإستخدام برامج أيضا تعتمد على الطرق التي بالأسفل ويتم إرسال طلبات إلى الموقع المسجل لديه بيانات المستخدم. كما عرفنا سابقا أن Hashing وحيدة الإتجاه (One way function) أي لا يمكن إعادة النص الأصلي (Plaintext). ومع ذلك فإنه يمكن أن تكتشف الكلمة الأصلي ويتم ذلك بعدة طرق. وهذه الطرق إما أن تعتمد على مبدأ التخمين أو البحث في قاعدة بيانات كبيرة (جدول) تحتوي على العديد من كلمات المرور مع الـ hash المقابل لها. ويتم ذلك ضمن خورزميات وعمليات حسابية طويلة ومقعدة لتضمن فعالية هذه الطرق. ومن هذه الطرق: 1- Dictionary Attack هذه الطريقة تعتمد على مبدأ التخمين (Guessing ) حيث يكون هناك ملف يحتوي على قائمة كبيرة من الكلمات المستخدمة أو المشهورة بإنها تستخدم ككلمة مرور. أو تكون هذه القائمة متوقعة بالنسبة لشخص محدد مثل تاريخ الميلاد رقم الهاتف الخ. ويمكن استخدام هذه الطريقة من خلال (Online Attack) أو (Offline Attack) وفي الـ Offline Attack تقوم بعض البرامج أثناء التنفيذ بعمل الـ Hash المقابل لكل كلمة في الملف ويتم مقارنتها مع الHashed Password التي يمتلكها المخترق وفي حال التطابق يتم معرفة كلمة المرور الأصلية. مثال: لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي Car ( بالتأكيد لا يعرفها المخترق). ستتم هذه الطريقة حسب الملف الذي يمتلكه المخترق (الشكل بالأسفل) ستتم تجربة كلمات الملف كالتالي 2- Brute Force Attack هذه الطريقة شبيهه بالطريقة السابقة حيث أنها تعتمد أيضا على التخمين ولكن التخمين بالاعتماد على عمليات حسابية حيث يتم تجربة كل الاحتمالات الممكنة مثلا تجربة كل الاحتمالات لكلمة مرور ذات طول 6 خانات عبارة عن أرقام أو أحرف. فهنا الموضوع لا يعتمد على جدول أو ملف إنما تجربة أثناء وقت التنفيذ. وهذه الطريقة تأخذ وقت طويل يزيد وينقص حسب طول كلمة المرور. مثال : لنفرض أن المخترق لديه هذا الحساب User@mail.com وكلمة المرور هي 111333 ( بالتأكيد لا يعرفها المخترق). تعمل هذه الطريقة بهذا الشكل: سيتم تجربة كل الاحتمالات الممكنة حتى يصل إلى كلمة المرور. وقد تستمر إلى وقت طويل جداً حتى يتم إيجاد كلمة المرور. أيضا هنا تم استخدام (Online Attack) حيث أن المخترق يرسل للخادم في كل مرة اسم المستخدم مع احتمال لكلمة المرور بشكل متسلسل. ويمكن استخدام الـ (Offline Attack) إذا كانت كلمة المرور محفوظة كـ Hash حيث يكون لدى المخترق الـ Hashed Password فيمكن اكتشاف كلمة المرور بنفس الطريقة ولكن يتم أولا عمل Hash أُثناء التنفيذ لكل سلسلة من الحروف ثم مقارنتها مع الـ Hashed Password. لنفرض أن المخترق لديه هذه الكلمة (Hashed Password) التالية: 62b37844f7adeb0df6f180126327dca339fb7003 3- Lookup Tables أو ما يسمى (Pre-Computed dictionary attack) لنعرف أولا معنى Pre-Computed أو Precomputation يقصد بها: عملية تجهيز الجدول قبل وقت التنفيذ (Run Time) ففي الطريقة السابقة (Dictionary attack) لدينا قائمة كبيرة من الكلمات وفي الـ (Offline Attack) يتم عمل Hash لكل كلمة من القائمة أثناء التنفيذ ومقارنتها مع الكلمة التي يتملتكها المخترق (Hashed Password) فهذه الطريقة تأخذ وقت كبير ولكن هنا يتم إعداد جدول أو قائمة يكون مقابل كل كلمة الـ Hash الخاص بها حسب خوارزمية الـ Hashing. وهذا الجدول يستفيد منه المخترق دائما دون عمل الـHash مرة أخرى لكل كلمة أثناء التنفيذ. وتكون العملية فقط بحث عن الـ Hash المطابقة للكلمة التي لدى المخترق ففي حالة التطابق يتكون كلمة المرور هي المقابلة للـ Hash مثال: لنفرض أن لدى المخترق هذه الـ Hashed Password e9989db5dabeea617f40c8dbfd07f5fb ولديه هذا الجدول ( هذا الجدول تم إنشائه مسبقا Pre-Computed ) وليس أثناء وقت التنفيذ. فالعملية هي عملية بحث أو استعلام عادية للحصول على الـ Hash الموجودة في الجدول والمطابقة لما لدى المخترق (e9989db5dabeea617f40c8dbfd07f5fb). وهنا تكون كلمة المرور الأصلية هي Car. وطبعا هذه الطريقة تختصر الوقت لكن تعتمد كفائتها على كمية الكلمات الموجودة في الجدول. يوجد بعض البرامج التي تقوم بإنشاء هذه الجداول حسب بعض المعطيات لدى المخترق ( أي لا يشترط أن يكون هناك قائمة جاهزة للكلمات المشهورة إنما يمكن إنشائها) من هذه المعطيات أن يتم إنشاء جدول (Lookup tables) للكلمات ذات الطول من 1 إلى 7 خانات وتكون عبارة عن أرقام فقط. ويمكن أن تكون أرقام أو حروف أو رموز. كلما زادت المعطيات زاد وقت إنشاء هذه الجداول وزادت مساحة التخزين. 4- Rainbow Tables هو نوع مخصص من Lookup table بنفس الفكرة والاعتماد على (precomputation ). ولكن تعمل تحت مبدأ (space/time trade-off) وهذا يعني زيادة مساحة التخزين في سبيل تقليل وقت التنفيذ أو وقت الحصول على النتيجة. ولكن وقت التنفيذ أقل من Brute Force Attack وكذلك مساحة التخزين أقل من Lookup table. الفرق بين الـ Rainbow table و Lookup table هو في طريقة إنشاء جدول الكلمات حيث يعتمد الـ Rainbow table على دالة الاختزال أو التقليص (Reduction function) والتي تعمل على تقليل حجم الجدول الناشئ حيث يتم استبعاد حفظ بعض كلمات المرور والـ Hash المقابل لها. شرح التفاصيل الخاصة بالـ Rainbow table and Reduction function يحتاج موضوع مستقل. لكن سأذكر في النهاية بعض المواضيع التي تشرحها بشئ من التفصيل. ملاحظة: تم توضيح الطرق السابقة في أبسط صورها وقد تم إضافة الكثير من التحسينات وتم استخدام خوارزميات معقدة تزيد من فعاليتها. ومعرفة هذه الطرق وتفاصيلها أحد الأسباب الرئيسية لحماية بياناتك أو حماية بيانات المستخدمين لديك وتساعدك في فرض عدة قيود صلبة تجعل عملية الإختراق صعبة أو شبه مستحيلة. استخدام الـ Salted Password Hashing هذه الطريقة هي الهدف الرئيسي من هذا الموضوع. كما ذكرنا سابقاً أن عملية الـ Hashing للكلمة 123 هو Fdeom83nyU ( مثال فقط) فهنا كلما تكررت 123 ككلمة مرور لأي مستخدم ستكون النتيجة هي Fdeom83nyU وهذه النقطة تساعد المخترق على محاولة إستنتاج كلمة المرور من النص Fdeom83nyU بإستخدام الطرق السابقة أو غيرها. أما طريقة Salted Password تعمل على أن يكون الـ Hash للكلمة 123 مختلف في كل مرة وذلك بإضافة نص عشوائي للكلمة الأصلية 123 ثم عمل الـ Hashing لها كما في الشكل التالي: نلاحظ في الشكل السابق إختلاف الناتج من عملية الـ Hashing في كلمة مرور حتى وإن كانت كلمة المرور واحدة. الهدف من الكلمة Salt والتي تعنى ملح هو شيء مشابه للفائدة من الملح وهو تحسين الطعام أيضا هنا النص المضاف Salt يعني إضافة نص عشوائي إلى كلمة المرور الأصلية لتحسينها حتى تكون قوية يصعب كسرها أو إكتشافها. طريقة عمل Salted Password Hashing: أولا في مرحلة تسجيل البيانات حيث يقوم المستخدم بتعبئة بياناته على الموقع ثم يقوم بالحفظ. حيث تتم الخطوات التالية: 1- يدخل المستخدم كلمة المرور لنفرض 123 2- يقوم البرنامج بإنشاء نص مضاف (Salt) بشكل عشوائي لنفرض Nd29kd63w1po 3- يتم دمج كلمة المرور مع النص العشوائي المضاف لتصبح 123Nd29kd63w1po 4- يتم عمل Hash للنص 123Nd29kd63w1po وسيكون الناتج jdhsi3jf92 ( طبعا النص أطول من ذلك حسب خوارزمية الـ Hash ). 5- يتم حفظ الناتج jdhsi3jf92 و النص العشوائي المضاف Nd29kd63w1po في قاعدة البيانات مع بيانات المستخدم الأخرى كالبريد الالكتروني والاسم ..الخ. ثانيا عند تسجيل الدخول يتم التالي: 1- يُدخل المستخدم اسم المستخدم وكلمة المرور لنفرض 123 2- يتم الاستعلام عن بيانات المستخدم بواسطة اسم المستخدم من قاعدة البيانات للحصول على الـ Salt و الـ Hashed Password ونفرض أن Salt هو Nd29kd63w1po والـ Hashed Password هي jdhsi3jf92. 3- يتم دمج كلمة المرور المدخلة 123 مع النص Salt وهو Nd29kd63w1po لتصبح 123Nd29kd63w1po ثم يتم عمل Hashing لها ليصبح الناتج jdhsi3jf92 4- ثم يتم مطابقة النص الناتج jdhsi3jf92 مع كلمة المرور المخزنة في قاعدة البيانات Hashed Password وهي jdhsi3jf92 وفي حال التطابق يتم تسجيل الدخول. نصائح مهمة عند عمل Salted Password Hashing: 1- عدم إعادة استخدام الـ Salt نفسه مع كل كلمة مرور حيث يجب أن يكون عشوائي مع كل كلمة مرور. 2- لا تستخدم Salt قصير يجب أن يكون نص عشوائي طويل على الأقل 16 حرف ( أيضا يمكنك الاعتماد على نوع خوارزمية الـ Hash لتحديد طول الـ Salt ) 3- استخدام خوارزميات الـ Hash مثل (MD5,SHA1,SHA2,SHA3) في Salted Password Hashing يعتبر آمن ولكن هذه الخورزميات تعتبر قديمة والمخترقين لديهم عدة طرق لكسر كلمات المرور التي تم إنشائها بواسطتها وحتى مع وجود الـ Salt هي مجرد مسألة وقت ويمكن كسرها. لذلك يفضل عدم استخدامها بل استخدام (password-based key derivation function) مثل PBKDF2, bcrypt, scrypt 4- لا تقوم بإنشاء دالة خاصة تقوم بتوليد (Generate) نص عشوائي Salt بل يفضل استخدام الدوال الجاهزة حسب لغة البرمجة كما في الشكل التالي: تطبيق: بلغة (ASP.Net (C#,VB)) مع قاعدة البيانات SQL Server الفكرة: سيتم إن شاء الله إنشاء تطبيق بسيط لعملية تسجيل أو إنشاء المستخدمين أيضا تسجيل الدخول. حيث يقوم المستخدم بتعبئة بياناته في صفحة التسجيل (Registration) وهي اسم المستخدم و الاسم كاملأ وكلمة المرور. ثم يقوم البرنامج بعمل Salted Password ( الـ Salt و Hashed Password ) ثم يتم حفظ البيانات في قاعدة البيانات. وعندما تتم عملية التسجيل يمكن للمستخدم تسجيل الدخول عن طريق صفحة الدخول (Login) حيث يتم كتابة اسم المستخدم وكلمة المرور حسب الخطوات التي تم ذكرها بالأعلى في جزئية طريقة عمل Salted Password Hashing. التطبيق يعتمد على PBKDF2. وهي خورازمية للـ (Password-Based Key Derivation Function) وليست للـ Hashing بشكل مباشر مثل (MD5,SHA) والهدف منها استخدام إحدى خوارزميات الـ Hashing المباشرة ولكن بطريقة تعمل على زيادة قوة الـ Hash الناتج من كلمة المرور الأصلية بحيث يصعب كسر أو إكتشاف كلمة المرور من خلال Rainbow tables أو Brute force attack. ويتم ذلك من خلال عدة نقاط مثل طول الـ Salt أيضا عدد الـ Iterations والذي يعمل على تحسين الـ Hash الناتج من كلمة المرور الأصلية. لنفرض أن عدد الـ هو Iterations 30000 هنا تتم عملية التحسين 30000 مرة وهذا مما يزيد في صعوبة إكتشاف كلمة المرور ولكن من الطبيعي يكون هناك بطئ أثناء وقت التفيذ حسب عدد الـ Iterations. سأذكر إن شاء الله في نهاية الموضوع بعض المصادر التي تشرح key derivation function و PBKDF2 وكذلك Key stretching. الـ PBKDF2 في الـ .Net يمكن استخدام الـ Rfc2898DeriveBytes والذي يعتمد على SHA-1. قاعدة البيانات: تم إنشاء جدول للمستخدمين يحتوى على عدة حقول كما في الشكل أدناه. CREATE TABLE UsersAccounts ( Username nvarchar(30) PRIMARY KEY, FullName nvarchar(200) NOT NULL, Salt nvarchar(100) NOT NULL, HashedPassword nvarchar(100) NOT NULL ) الفئات (Classes) نحتاج لـ Two Classes الأول خاص بقاعدة البيانات (SQLHelper) والثاني خاص بالـ Hashing وهو (SaltedPassword) وهذا الأخير و لب هذا التطبيق. فئة (Class) للتعامل مع قاعدة البيانات (SQLHelper) يحتوي على دالتين - دالة الاستعلام حيث يتم إرسال جملة الإستعلام ويتم حفظ النتائج في جدول (DataTable) - دالة تنفيذ جمل الإضافة والتعديل والحذف ويتم من خلالها إرسال البيانات وحفظها في قاعدة البيانات. SQLHelper Class C# using System; using System.Data; using System.Data.SqlClient; public class SQLHelper { public SQLHelper() { // // TODO: Add constructor logic here // } public static readonly string ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["connectionString1"].ConnectionString; // Select Queries public static DataTable ExecuteQuery(string commandText, CommandType commandType, SqlParameter[] parameters) { DataTable table = new DataTable(); try { // ConnectionString كائن الإتصال مسؤول عن فتح وإغلاق الاتصال بقاعدة البيانات حسب المسار في using (SqlConnection connection = new SqlConnection(ConnectionString)) { // كائن الأوامر يتم من خلاله تجهيزة جملة الاستعلام والإضافة ليتم تنفيذها على قاعدة البيانات using (SqlCommand command = new SqlCommand(commandText, connection)) { // تحديد نوع كائن الأوامر // Text: like (Select * from table ...) // StoredProcedure: will be the name of StoredProcedure command.CommandType = commandType; // هي القيم التي يتم تمريرها مثل رقم الطالب والتي تكون مدخله من المستخدم parameters الـ if (parameters != null) command.Parameters.AddRange(parameters); connection.Open(); // DataSet مسؤول عن تعبئة النتائج في جدول أو SqlDataAdapter adapter = new SqlDataAdapter(command); adapter.Fill(table); } } } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); return null; } return table; } // Insert,Update and Delete public static bool ExecuteNonQuery(string commandText, CommandType commandType, SqlParameter[] parameters) { try { using (SqlConnection connection = new SqlConnection(ConnectionString)) { using (SqlCommand command = new SqlCommand(commandText, connection)) { command.CommandType = commandType; if (parameters != null) command.Parameters.AddRange(parameters); connection.Open(); if (command.ExecuteNonQuery() > 0) { return true; } else return false; } } } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); return false; } } } VB.Net Imports Microsoft.VisualBasic Imports System.Data Imports System.Data.SqlClient Public Class SQLHelper Private Shared ConnectionString As String = System.Configuration.ConfigurationManager.ConnectionStrings("connectionString1").ConnectionString 'Select Queries Public Shared Function ExecuteQuery(commandText As String, commandType As CommandType, parameters As SqlParameter()) As DataTable Dim table As New DataTable() Try Using connection As New SqlConnection(ConnectionString) Using command As New SqlCommand(commandText, connection) command.CommandType = commandType If parameters IsNot Nothing Then command.Parameters.AddRange(parameters) End If connection.Open() Dim adapter As New SqlDataAdapter(command) adapter.Fill(table) End Using End Using Catch ex As Exception EventsLogger.SaveToLog(ex.Message) Return Nothing End Try Return table End Function ' Insert,Update and Delete Public Shared Function ExecuteNonQuery(commandText As String, commandType As CommandType, parameters As SqlParameter()) As Boolean Try Using connection As New SqlConnection(ConnectionString) Using command As New SqlCommand(commandText, connection) command.CommandType = commandType If parameters IsNot Nothing Then command.Parameters.AddRange(parameters) End If connection.Open() If command.ExecuteNonQuery() > 0 Then Return True Else Return False End If End Using End Using Catch ex As Exception EventsLogger.SaveToLog(ex.Message) Return False End Try End Function End Class فئة (Class) للتعامل مع الـ (Salted Password Hashing) يحتوي على ثلاثة دوال: - دالة لإنشاء النص العشوائي المضاف Salt .وهي GenerateSalt - دالة لعمل الـ Hash . وهي HashPasswordUsingPBKDF2 - دالة للمطابقة عند تسجيل الدخول للتحقق من اسم المستخدم وكلمة المرور. وهي VerifyPassword SaltedPassword Class C# using System; using System.Security.Cryptography; public class SaltedPassword { public SaltedPassword() { // // TODO: Add constructor logic here // } private const int pbkdf2NoOfIterations = 30000; private const int hashSize = 32; private const int saltSize = 32; /// <summary> /// Hashing دالة لإنشاء نص عشوائي ليتم إضافته إلى كلمة المرور قبل عملية الـ /// </summary> /// <returns></returns> public static string GenerateSalt() { RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] salt = new byte[saltSize]; rng.GetBytes(salt); return Convert.ToBase64String(salt); } /// <summary> /// Hash دالة تقوم بإنشاء كلمة المرور كـ /// </summary> /// <param name="Password">كلمة المرور الأصلية التي أدخلها المستخدم</param> /// <param name="Salt"> النص المضاف التي تم إنشائه سابقا</param> /// <returns></returns> public static string HashPasswordUsingPBKDF2(string Password,string Salt) { byte[] bSalt = Convert.FromBase64String(Salt); Rfc2898DeriveBytes PBKDF2 = new Rfc2898DeriveBytes(Password, bSalt, pbkdf2NoOfIterations); byte[] key = PBKDF2.GetBytes(hashSize); return Convert.ToBase64String(key); } /// <summary> /// دالة للتحقق من كلمة المرور المدخلة أثناء تسجيل الدخول /// </summary> /// <param name="UserPassword">كلمة المرور المدخلة من قبل المستخدم أثناء تسجيل الدخول</param> /// <param name="Salt">النص المضاف المحفوظ في قاعدة البيانات</param> /// <param name="HashedPassword">Hash كلمة المرور التي تم حفظها في قاعدة البيانات كـ </param> /// <returns></returns> public static bool VerifyPassword(string UserPassword, string Salt,string HashedPassword) { string hash = HashPasswordUsingPBKDF2(UserPassword, Salt); // المقارنة بين القيمتين للتأكد من صحة كلمة المرور // New Hash with Hashed Password (from Database) if (String.Compare(hash, HashedPassword, false) == 0 ) return true; // كلمة المرور صحيحة else return false; } } VB.Net Imports Microsoft.VisualBasic Imports System.Security.Cryptography Public Class SaltedPassword Private Const pbkdf2NoOfIterations As Integer = 30000 Private Const hashSize As Integer = 32 Private Const saltSize As Integer = 32 ''' <summary> ''' Hashing دالة لإنشاء نص عشوائي ليتم إضافته إلى كلمة المرور قبل عملية الـ ''' </summary> ''' <returns></returns> Public Shared Function GenerateSalt() As String Dim rng As New RNGCryptoServiceProvider() Dim salt As Byte() = New Byte(saltSize - 1) {} rng.GetBytes(salt) Return Convert.ToBase64String(salt) End Function ''' <summary> ''' Hash دالة تقوم بإنشاء كلمة المرور كـ ''' </summary> ''' <param name="Password">كلمة المرور الأصلية التي أدخلها المستخدم</param> ''' <param name="Salt"> النص المضاف التي تم إنشائه سابقا</param> ''' <returns></returns> Public Shared Function HashPasswordUsingPBKDF2(Password As String, Salt As String) As String Dim bSalt As Byte() = Convert.FromBase64String(Salt) Dim PBKDF2 As New Rfc2898DeriveBytes(Password, bSalt, pbkdf2NoOfIterations) Dim key As Byte() = PBKDF2.GetBytes(hashSize) Return Convert.ToBase64String(key) End Function ''' <summary> ''' دالة للتحقق من كلمة المرور المدخلة أثناء تسجيل الدخول ''' </summary> ''' <param name="UserPassword">كلمة المرور المدخلة من قبل المستخدم أثناء تسجيل الدخول</param> ''' <param name="Salt">النص المضاف المحفوظ في قاعدة البيانات</param> ''' <param name="HashedPassword">Hash كلمة المرور التي تم حفظها في قاعدة البيانات كـ </param> ''' <returns></returns> Public Shared Function VerifyPassword(UserPassword As String, Salt As String, HashedPassword As String) As Boolean Dim hash As String = HashPasswordUsingPBKDF2(UserPassword, Salt) ' المقارنة بين القيمتين للتأكد من صحة كلمة المرور ' New Hash with Hashed Password (from Database) If [String].Compare(hash, HashedPassword, False) = 0 Then Return True Else ' كلمة المرور صحيحة Return False End If End Function End Class صفحة التسجيل (Registration.aspx) Design <div class="center" dir="rtl"> <table class="center"> <tr> <td class="td">اسم المستخدم:</td> <td> <input id="txtUsername" type="text" runat="server" class="textBox" maxlength="20" required="required" /></td> </tr> <tr> <td class="td">الاسم:</td> <td> <input id="txtName" type="text" runat="server" class="textBox" maxlength="200" required="required" /></td> </tr> <tr> <td class="td">كلمة المرور:</td> <td> <input id="txtPassword" type="password" runat="server" class="textBox" maxlength="20" required="required" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="كلمة المرور يجب أن تكون 8 خانات وخليط من الارقام والحروف الصغيرة والكبيرة والرموز" /></td> </tr> <tr> <td colspan="2"> <asp:Button ID="btnRegister" runat="server" Text="تسجيل" CssClass="button" OnClick="btnRegister_Click" /></td> </tr> </table> <br /> <asp:Label ID="lbMessage" runat="server" CssClass="lableMsg"></asp:Label> </div> الكود بداخل زر التسجيل (btnRegister) C# protected void btnRegister_Click(object sender, EventArgs e) { try { string salt = SaltedPassword.GenerateSalt(); string hashedPassword = SaltedPassword.HashPasswordUsingPBKDF2(txtPassword.Value, salt); // Save Into database // نستخدم هذه الطريقة للحماية من // Sql Injection // command حيث نمرر القيم داخل SqlParameter[] parameters = new SqlParameter[]{ new SqlParameter("@Username",txtUsername.Value), new SqlParameter("@FullName",txtName.Value), new SqlParameter("@Salt",salt), new SqlParameter("@HashedPWD",hashedPassword), }; // ويفضل استخدام الإجراءات المخزنة // Stored Procedures if (SQLHelper.ExecuteNonQuery("INSERT INTO UsersAccounts VALUES(@Username,@FullName,@Salt,@HashedPWD)", CommandType.Text, parameters)) lbMessage.Text = "تم تسجيل البيانات بنجاح"; else lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات"; } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات"; } } VB.Net Protected Sub btnRegister_Click(sender As Object, e As EventArgs) Try Dim salt As String = SaltedPassword.GenerateSalt() Dim hashedPassword As String = SaltedPassword.HashPasswordUsingPBKDF2(txtPassword.Value, salt) ' Save Into database ' نستخدم هذه الطريقة للحماية من ' Sql Injection ' command حيث نمرر القيم داخل Dim parameters As SqlParameter() = New SqlParameter() {New SqlParameter("@Username", txtUsername.Value), New SqlParameter("@FullName", txtName.Value), New SqlParameter("@Salt", salt), New SqlParameter("@HashedPWD", hashedPassword)} ' ويفضل استخدام الإجراءات المخزنة ' Stored Procedures If SQLHelper.ExecuteNonQuery("INSERT INTO UsersAccounts VALUES(@Username,@FullName,@Salt,@HashedPWD)", CommandType.Text, parameters) Then lbMessage.Text = "تم تسجيل البيانات بنجاح" Else lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات" End If Catch ex As Exception EventsLogger.SaveToLog(ex.Message) lbMessage.Text = "حدث خطأ أثناء تسجيل البيانات" End Try End Sub صفحة تسجيل الدخول (Login.aspx) Design <div class="center" dir="rtl"> <table class="center"> <tr> <td class="td">اسم المستخدم:</td> <td> <input id="txtUsername" type="text" runat="server" class="textBox" maxlength="20" required="required" /></td> </tr> <tr> <td class="td">كلمة المرور:</td> <td> <input id="txtPassword" type="password" runat="server" class="textBox" maxlength="20" required="required" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="كلمة المرور يجب أن تكون 8 خانات وخليط من الارقام والحروف الصغيرة والكبيرة والرموز" /></td> </tr> <tr> <td colspan="2"> <asp:Button ID="btnLogin" runat="server" Text="تسجيل دخول" CssClass="button" OnClick="btnLogin_Click"/></td> </tr> </table> <br /> <asp:Label ID="lbMessage" runat="server" CssClass="lableMsg"></asp:Label> </div> الكود بداخل زر تسجيل الدخول (btnLogin) C# protected void btnLogin_Click(object sender, EventArgs e) { try { SqlParameter[] parameters = new SqlParameter[]{ new SqlParameter("@Username",txtUsername.Value), }; // الحصول أولا على معلومات المستخدم من خلال الاستعلام بإسم المستخدم DataTable userInfo = SQLHelper.ExecuteQuery("SELECT * FROM UsersAccounts WHERE Username=@Username", CommandType.Text, parameters); if (userInfo == null || userInfo.Rows.Count <= 0) lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة"; else { string salt = userInfo.Rows[0]["Salt"].ToString(); string hashedPassword = userInfo.Rows[0]["HashedPassword"].ToString(); if (SaltedPassword.VerifyPassword(txtPassword.Value, salt, hashedPassword)) lbMessage.Text = "تم تسجيل الدخول بنجاح"; else lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة"; } } catch (Exception ex) { EventsLogger.SaveToLog(ex.Message); lbMessage.Text = "حدث خطأ أثناء تسجيل الدخول"; } } VB.Net Protected Sub btnLogin_Click(sender As Object, e As EventArgs) Try Dim parameters As SqlParameter() = New SqlParameter() {New SqlParameter("@Username", txtUsername.Value)} ' الحصول أولا على معلومات المستخدم من خلال الاستعلام بإسم المستخدم Dim userInfo As DataTable = SQLHelper.ExecuteQuery("SELECT * FROM UsersAccounts WHERE Username=@Username", CommandType.Text, parameters) If userInfo Is Nothing OrElse userInfo.Rows.Count <= 0 Then lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة" Else Dim salt As String = userInfo.Rows(0)("Salt").ToString() Dim hashedPassword As String = userInfo.Rows(0)("HashedPassword").ToString() If SaltedPassword.VerifyPassword(txtPassword.Value, salt, hashedPassword) Then lbMessage.Text = "تم تسجيل الدخول بنجاح" Else lbMessage.Text = "اسم المستخدم أو كلمة المرور غير صحيحة" End If End If Catch ex As Exception EventsLogger.SaveToLog(ex.Message) lbMessage.Text = "حدث خطأ أثناء تسجيل الدخول" End Try End Sub ويكون شكل البيانات في الجدول كالتالي: الخلاصة: موضوع حماية بيانات المستخدمين متشعب كما نلاحظ في بعض النقاط السابقة. حاولت التركيز على الموضوع الرئيسي وهو استخدام الـ Salted Password Hashing وتم المرور على بعض المفاهيم الأخرى التي بعضها الحقيقة يحتاج إلى موضوع مستقل ولكن نسأل الله التيسر حتى استطيع شرح بعض هذه المفاهيم في مواضيع أخرى مثل Rainbow table أو password-based key derivation function. خلاصة هذه الموضوع التركيز على هذه النقاط. - عند تسجيل بيانات المستخدمين مثل كلمة المرور يجب أن لا تُقبل كلمات المرور القصيرة أو السهلة يفضل إجبار المستخدم على كلمة مرور معقدة. - استخدم الـ Salted Password Hashing. - العمل بالنقاط الموجودة في جزئية نصائح مهمة عند عمل Salted Password Hashingالموجودة بالاعلى. مراجع للإطلاع: - Salted Password Hashing - Doing it Right https://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right - How Rainbow Tables work http://kestas.kuliukas.com/RainbowTables - Rainbow Tables https://stichintime.wordpress.com/2009/04/09/rainbow-tables-part-1-introduction - PBKDF2 https://en.wikipedia.org/wiki/PBKDF2 - Key Derivation Function https://en.wikipedia.org/wiki/Key_derivation_function تمت بحمد الله. التطبيق في المرفقات Demo.rar Talal Almutairi https://twitter.com/talalsql
  3. بسم الله الرحمن الرحيم مقدمة: المقصود ب (Subquery) استعلام داخل استعلام. ويسمى أيضا استعلام داخلي (Inner query) أو استعلام متداخل (Nested query) A Subquery or Inner query or a Nested query is a query within another SQL query and embedded within the WHERE clause (1). مثال: SELECT * FROM Students WHERE CollegeID IN (SELECT CollegeID FROM Universities WHERE Location = 'Saudi Arabia') حتى نعرف الفرق بين (Simple subquery) و (Correlated subquery) نحتاج أن نعرف معنى (Inner query and Outer query). في المثال السابق الجزء: SELECT * FROM Students WHERE CollegeID IN يسمى الاستعلام الخارجي (Outer query) والجزء (SELECT CollegeID FROM Universities WHERE Location = 'Saudi Arabia') يسمى الاستعلام الداخلي (Inner query) الفرق بين Simple subquery and Correlated subquery حتى يتضح الفرق لنفرض أن لدينا جدول خاص بالعملاء ( الرقم – الاسم – العمر – الدولة) وأيضا جدول للدول (الرقم – اسم الدولة) كما في الصورة الاستعلام الفرعي البسيط (Simple subquery) يكون فيه الاستعلام الداخلي (Inner query) غير معتمد على الاستعلام الخارجي (Outer query) بمعنى أن يكون الاستعلام الداخلي مستقل ويمكن تنفيذه بشكل مستقل. مثال: استعلام يعرض لنا كامل بيانات العملاء من دولة المملكة العربية السعودية. SELECT * FROM Customers WHERE CountryID IN (SELECT CountryID FROM Countries WHERE CountryName ='KSA') ويكون الناتج: توضيح آلية تنفيذ الـ Simple subquery: في البداية يتم تنفيذ الاستعلام الداخلي (Inner query) مرة واحدة فقط. حيث يتم الحصول على رقم دولة (السعودية KSA ). ثم يتم تمرير الناتج للاستعلام الخارجي (Outer query) ويتم الاستفادة منه في الشرط. بمعنى لا يتم تكرار تنفيذ الاستعلام الداخلي. الاستعلام الفرعي المترابط (Correlated subquery) يكون فيه الاستعلام الداخلي (Inner query) معتمد على الاستعلام الخارجي (Outer query) أي أنه يوجد في الاستعلام الداخلي جزء يعتمد على الاستعلام الخارجي. مثال: استعلام يعرض كامل بيانات الدول المرتبط بها عملاء SELECT * FROM Countries cnt WHERE cnt.CountryID IN (SELECT cus.CountryID From Customers cus WHERE cus.CountryID = cnt.CountryID) ويكون الناتج: هنا نلاحظ في الجزئية الأخيرة (WHERE cus.CountryID = cnt.CountryID) الموجودة في الاستعلام الداخلي أن الحقل (CountryID) في جدول الدول (Countries) موجود في الاستعلام الخارجي وهنا تم الاعتماد عليه في الاستعلام الداخلي. توضيح آلية تنفيذ الـ Correlated subquery: هنا الموضوع يختلف عن الـ Simple subquery حيث يتم أولا تنفيذ الاستعلام الخارجي حيث يبدأ بالسجل الأول من جدول الدول وهو السعودية ثم يتم تمرير رقم الدولة (1) إلى الاستعلام الداخلي ويتم مطابقة الرقم (1) القادم من الاستعلام الخارجي مع رقم الدولة الموجود في جدول العملاء والموجود في الاستعلام الداخلي (WHERE cus.CountryID = cnt.CountryID) للتأكد من هل العميل مرتبط بهذه الدولة فإذا لم يكون مرتبط ينتقل للسجل الثاني في جدول العملاء ( نحن الآن في السجل الآول من جدول الدول والسجل الثاني من جدول العملاء) وهكذا حتى يتأكد من كامل العملاء. ثم يتنقل للسجل الثاني من جدول الدول وهو دولة الإمارات رقم (2) ثم يمرره إلى الاستعلام الداخلي ويتم البحث في جدول العملاء ويبدأ المقارنة بنفس الطريقة. بمعنى أن إذا كان لدينا 3 سجلات في جدول الدول ولدينا 4 سجلات في جدول العملاء في كل سجل من جدول الدول يتم المرور على 4 سجلات من جدول العملاء وهذا يشكل حلقة (loop). الخلاصة: الفرق بين الاستعلام الفرعي البسيط (Simple subquery) والاستعلام الفرعي المترابط (Correlated subquery) يكون في النقاط التالية: الاستعلام الداخلي في Simple subquery لا يعتمد على الاستعلام الخارجي ويتم تنفيذه بشكل مستقل. أما في Correlated subquery الاستعلام الداخلي يعتمد على الاستعلام الخارجي. في الـ Simple subquery يتم أولاً تنفيذ الاستعلام الداخلي مرة واحدة فقط ولا يشكل حلقة (loop). أما في Correlated subquery يتم تنفيذ الاستعلام الخارجي أولاً ومع كل سجل من الاستعلام الخارجي يتم تنفيذ الاستعلام الداخلي وذلك يشكل حلقة (Loop). الـ Simple subquery أسرع من حيث الأداء بناء على النقطة الثانية.(2) (1): https://www.tutorialspoint.com/sql/sql-sub-queries.htm (2): http://stackoverflow.com/a/21036822 تمت بحمد الله. Talal Almutairi https://twitter.com/talalsql
  4. بسم الله الرحمن الرحيم مقدمة: المقصود ب (Subquery) استعلام داخل استعلام. ويسمى أيضا استعلام داخلي (Inner query) أو استعلام متداخل (Nested query) A Subquery or Inner query or a Nested query is a query within another SQL query and embedded within the WHERE clause (1). مثال: SELECT * FROM Students WHERE CollegeID IN (SELECT CollegeID FROM Universities WHERE Location = 'Saudi Arabia') حتى نعرف الفرق بين (Simple subquery) و (Correlated subquery) نحتاج أن نعرف معنى (Inner query and Outer query). في المثال السابق الجزء: SELECT * FROM Students WHERE CollegeID IN يسمى الاستعلام الخارجي (Outer query) والجزء (SELECT CollegeID FROM Universities WHERE Location = 'Saudi Arabia') يسمى الاستعلام الداخلي (Inner query) الفرق بين Simple subquery and Correlated subquery حتى يتضح الفرق لنفرض أن لدينا جدول خاص بالعملاء ( الرقم – الاسم – العمر – الدولة) وأيضا جدول للدول (الرقم – اسم الدولة) كما في الصورة الاستعلام الفرعي البسيط (Simple subquery) يكون فيه الاستعلام الداخلي (Inner query) غير معتمد على الاستعلام الخارجي (Outer query) بمعنى أن يكون الاستعلام الداخلي مستقل ويمكن تنفيذه بشكل مستقل. مثال: استعلام يعرض لنا كامل بيانات العملاء من دولة المملكة العربية السعودية. SELECT * FROM Customers WHERE CountryID IN (SELECT CountryID FROM Countries WHERE CountryName ='KSA') ويكون الناتج: توضيح آلية تنفيذ الـ Simple subquery: في البداية يتم تنفيذ الاستعلام الداخلي (Inner query) مرة واحدة فقط. حيث يتم الحصول على رقم دولة (السعودية KSA ). ثم يتم تمرير الناتج للاستعلام الخارجي (Outer query) ويتم الاستفادة منه في الشرط. بمعنى لا يتم تكرار تنفيذ الاستعلام الداخلي. الاستعلام الفرعي المترابط (Correlated subquery) يكون فيه الاستعلام الداخلي (Inner query) معتمد على الاستعلام الخارجي (Outer query) أي أنه يوجد في الاستعلام الداخلي جزء يعتمد على الاستعلام الخارجي. مثال: استعلام يعرض كامل بيانات الدول المرتبط بها عملاء SELECT * FROM Countries cnt WHERE cnt.CountryID IN (SELECT cus.CountryID From Customers cus WHERE cus.CountryID = cnt.CountryID) ويكون الناتج: هنا نلاحظ في الجزئية الأخيرة (WHERE cus.CountryID = cnt.CountryID) الموجودة في الاستعلام الداخلي أن الحقل (CountryID) في جدول الدول (Countries) موجود في الاستعلام الخارجي وهنا تم الاعتماد عليه في الاستعلام الداخلي. توضيح آلية تنفيذ الـ Correlated subquery: هنا الموضوع يختلف عن الـ Simple subquery حيث يتم أولا تنفيذ الاستعلام الخارجي حيث يبدأ بالسجل الأول من جدول الدول وهو السعودية ثم يتم تمرير رقم الدولة (1) إلى الاستعلام الداخلي ويتم مطابقة الرقم (1) القادم من الاستعلام الخارجي مع رقم الدولة الموجود في جدول العملاء والموجود في الاستعلام الداخلي (WHERE cus.CountryID = cnt.CountryID) للتأكد من هل العميل مرتبط بهذه الدولة فإذا لم يكون مرتبط ينتقل للسجل الثاني في جدول العملاء ( نحن الآن في السجل الآول من جدول الدول والسجل الثاني من جدول العملاء) وهكذا حتى يتأكد من كامل العملاء. ثم يتنقل للسجل الثاني من جدول الدول وهو دولة الإمارات رقم (2) ثم يمرره إلى الاستعلام الداخلي ويتم البحث في جدول العملاء ويبدأ المقارنة بنفس الطريقة. بمعنى أن إذا كان لدينا 3 سجلات في جدول الدول ولدينا 4 سجلات في جدول العملاء في كل سجل من جدول الدول يتم المرور على 4 سجلات من جدول العملاء وهذا يشكل حلقة (loop). الخلاصة: الفرق بين الاستعلام الفرعي البسيط (Simple subquery) والاستعلام الفرعي المترابط (Correlated subquery) يكون في النقاط التالية: الاستعلام الداخلي في Simple subquery لا يعتمد على الاستعلام الخارجي ويتم تنفيذه بشكل مستقل. أما في Correlated subquery الاستعلام الداخلي يعتمد على الاستعلام الخارجي. في الـ Simple subquery يتم أولاً تنفيذ الاستعلام الداخلي مرة واحدة فقط ولا يشكل حلقة (loop). أما في Correlated subquery يتم تنفيذ الاستعلام الخارجي أولاً ومع كل سجل من الاستعلام الخارجي يتم تنفيذ الاستعلام الداخلي وذلك يشكل حلقة (Loop). الـ Simple subquery أسرع من حيث الأداء بناء على النقطة الثانية.(2) (1): https://www.tutorialspoint.com/sql/sql-sub-queries.htm (2): http://stackoverflow.com/a/21036822 تمت بحمد الله. Talal Almutairi https://twitter.com/talalsql

عالم البرمجة

عالم البرمجة مقالات برمجة و دورات مجانية لإحتراف البرمجة هدفنا تبسيط البرمجة ونشرها بيد الكل بشكل ممتع ومتطور ومحدث بإستمرار لمواكبة جديد تطورات البرمجة الحديثة و المتقدمة بدون مقابل