تأمين كلمات المرور في بايثون باستخدام Bcrypt
- مفهوم التجزئة
- مفهوم القيمة العشوائية
- الفرق بين التشفير و التجزئة
- التجزئه عن طريق Bcrypt في بايثون
- مقارنة الكلمة المجزئة بكلمة المرور المدخلة
- هل تأمين كلمات المرور ضروري؟
مفهوم التجزئة
التجزئه ( Hashing ) هي عملية تحويل النصوص أو البيانات الى شكل غريب و عشوائي يحتوي على رموز و أرقام و أحرف بطول ثابت باستخدام خوارزمية رياضية. عادةً ما يتم استخدام التجزئة مع كلمات المرور حتى يتم تخزينها بشكل آمن لأنه عندها يستحيل إرجاع كلمة المرور إلى حالتها الأصليه.
الهدف من التجزئه هو حماية المستخدمين في حال تم اختراق قاعدة البيانات التي تحتوي بياناتهم.
خوارزميات التجزئة
خوارزميات التجزئة ( Hashing Algorithms ) تعمل بأسلوب يسمى الإتجاه الواحد ( One-way function ) و المقصود بذلك أنه على سبيل المثال في حال إدخال X
إلى الدالة فإنها سترجع Y
و لكن عند إدخال Y
فإنها لا ترجع القيمة الأصلية و التي هي X
.
فعلياً عندما تقوم ببرمجة تطبيق فيه تسجيل دخول و يحتوى على كلمات مرور المستخدمين فإن إطار العمل المُستَخدم في الغالب يتولى مهمة عمل التجزئه لكلمات المرور بشكل تلقائي إلا إذا أحببت تخصيص ذلك بإضافة خوارزمية أخرى غير المستخدمة.
في حال أردت تجزئة كلمات المرور بنفسك فإنه من الضروري استخدام خوارزمية قوية و معروفة لذلك.
مفهوم القيمة العشوائية
القيمة العشوائية ( Salt ) هي كلمة تتكون من أحرف عشوائية تضاف إلى كلمة المرور قبل تجزئتها بهدف إخراج ناتج غير متوقع و لإحباط هجمات التخمين على كلمات المرور.
Salt هو بمثابة مفهوم عام في التجزئة و لا يتم احتكاره على خوارزمية تجزئة معينة.
هناك خوارزميات تجزئة تضيف Salt بشكل عشوائي و منها تسمح لك باضافته بشكل يدوي تحت قيود معينة.
لا تستخدم خوارزميات تجزئه غير معروفة أو ضعيفة أو خاصة بك، بل استخدام خوارزميات معروفة و معتمدة مثل Bcrypt أو Scrypt أو Argon2.
الفرق بين التشفير و التجزئة
التشفير ( Encryption ) يعمل بالإتجاهين ( Two-way function ) عكس التجزئة حيث يمكن إعادة البيانات المشفرة إلى حالتها الأصلية من خلال المفتاح الذي تم تشفير البيانات به.
- الهدف من التشفير هو نقل البيانات من طرف إلى طرف آخر بشكل آمن و لكن حتى يتمكن الطرف الآخر من استخدامها فإنه يجب إرجاعها إلى حالتها الأصلية (أي فك التشفير عنها).
- الهدف من التجزئة هو تخزين كلمات المرور بدون إمكانيه إرجاع لحالتها الأصلية لأن كلمات المرور لا يمكن نقلها من طرف إلى طرف بل هي تخص المستخدم فقط.
التجزئه عن طريق Bcrypt في بايثون
bcrypt
هي أحد أشهر خوارزميات التجزئة التي تُستعمل في بايثون و سنعلمك كيف تستخدمها بشكل عملي.
لاستخدام خوارزمية Bcrypt قم بتحميل مكتبة bcrypt
بواسطة مدير الحزم pip باستعمال الأمر التالي.
pip install bcrypt
الآن سنقوم بتجزئة كلمة مرور mypass
على سبيل المثال.
ستتم التجزئة من خلال الدالة hashpw(password, salt)
حيث نمرر لها في البارميتر الأول كلمة السر الأصلية و لكن بعد تحويلها إلى Byte Code لكي تجري عليها الخوارزميات الرياضية التي ستؤدي إلى تجزئتها. و في البارميتر الثاني نمرر لها Salt و الذي سيتم توليده بشكل عشوائي بواسطة الدالة gensalt(rounds=12)
مع الإشارة إلى أن العدد 12 هو القيمة الإفتراضية التي تستعملها هذه الدالة و هي يمثّل رقم الجولات.
بمعنى آخر، الخوارزمية الرياضية ستعيد نفسها 2^12
و هذا يساوي 4096 مرة! بالطبع كلما كان عدد الجولات أكبر، كلما كانت العملية انتاج التجزئة أبطئ و هذا سيصاحبه إزدياد في الضغط على النظام مما يعني أن عملية تخمينه أو كسره تصبح أصعب.
مثال
# لأنها تحتوي على الدوال التي نحتاجها في عملية التجزئة bcrypt هنا قمنا باستدعاء المكتبة import bcrypt # و التي سنستعملها لتجزئة كلمات المرور hash_password هنا قمنا بتعريف دالة إسمها def hash_password(password): # حتى تستطيع الخوارزمية تطبيق العمليات الرياضية عليها Byte Code في البداية يجب تحويل كلمة المرور الاصلية إلى password = password.encode() # عشوائي بعدد 12 جولة salt و اضافة hashpw بعدها قمنا بتجزئة كلمة المرور باستخدام الدالة hashed_password = bcrypt.hashpw(password, bcrypt.gensalt(12)) # hashed_password في النهاية سيتم إرجاع التجزئة التي تم إنشاؤها لكلمة المرور و التي تم تخزينها في المتغير return hashed_password # هنا قمنا بتعريف متغير وضعنا فيه كلمة المرور المراد تجزئتها the_password = "mypass" # و من ثم قمنا بطباعة الناتج the_password لتجزئة كلمة المرور المخزنة في المتغير hash_password هنا قمنا بتجربة استخدام الدالة print(hash_password(the_password))
النتيجة: ستكون نص غير مفهوم كما يلي.
في كل مرة تشغّل فيها الكود ستكون النتيجة (أي الهاش الناتج) مختلفة و سبب ذلك أن الـ Salt يتم إنشاؤه بشكل عشوائي في كل مرة.
- الجزئية
$2b$
في الهاش تشير إلى أنه تم استخدام الإصدار رقم 2 منbcrypt
. - الجزئية
$12$
تشير إلى عدد الجولات التي تم إعادة الخوارزمية الرياضية فيها لإنتاج الهاش و هو يساوي2^12
و هذا يعادل 4096 مرة. - الجزئية
tdYwPWnEMx2GskcgcIDx3O
تمثل الـ Salt العشوائي الذي تم إنشاؤه و طوله 22 و هو مشفر بخوارزميةbase64
. - الجزئية
9SQBqCJwnFvl2spKfc8wG6P5dTkmbWK
هي الهاش الناتج بناءاً على كلمة المرور و تحويلها إلى Byte Code و إضافة Salt عشوائي و تكرار العملية 4096 مرة.
مقارنة الكلمة المجزئة بكلمة المرور المدخلة
بعد ان تم تجزئة كلمة المرور و تخزينها في قاعدة البيانات فإنه عندما يقوم المستخدم بإدخال كلمة مرور ليسجّل دخوله يتم تجزئة كلمة المرور التي يدخلها و مقارنتها بكلمة المرور المخزنة في قاعدة البيانات. حيث أنه لا يمكن أن يتم إرجاع كلمة المرور المخزنة في قاعدة البيانات لحالتها الأصلية.
كيف يتم تحديد ما إن كانت كلمة المرور صحيحة
أنت حتماً تتسائل الآن، كيف يتم تحديد ما إن كانت كلمة المرور صحيحة مع أن الناتج دائماً يكون مختلف في كل عملية تجزئة كما رأينا سابقاً!
ببساطة، عند تجزئة كلمة المرور الجديدة المدخلة من المستخدم فإنه يجب تجزئتها بنفس الإعدادات التي تم تجزئة كلمة المرور المخزنه في قاعدة البيانات. أي يجب استعمال نفس الـ Salt و نفس عدد الجولات للحصول على نفس الناتج و من ثم تتم المقارنة.
الآن للتحقق من كلمة المرور المدخلة فإننا نستعمل الدالة checkpw(password, hashed_password)
و التي نمرر لها قيمتين. مكان البارميتر الأول نضع كلمة المرور المدخلة و التي يجب أن نحولها إلى Byte Code أولاً. مكان البارميتر الثاني يتم إدخال كلمة المرور (المجزئة) المخزنة في قاعدة البيانات و يتم استخراج الـ Salt و عدد الجولات منها كما رأينا في شرح أجزاء الهاش الناتج. ثم ستقوم بتجزئة كلمة المرور المدخلة بنفس الإعدادات. و ان تطابقت ترجع True
غير ذلك False
.
مثال
# لأنها تحتوي على الدوال التي نحتاجها في عملية التجزئة bcrypt هنا قمنا باستدعاء المكتبة import bcrypt # و التي سنستعملها لتجزئة كلمات المرور hash_password هنا قمنا بتعريف دالة إسمها def hash_password(password): # حتى تستطيع الخوارزمية تطبيق العمليات الرياضية عليها Byte Code في البداية يجب تحويل كلمة المرور الاصلية إلى password = password.encode() # عشوائي بعدد 12 جولة salt و اضافة hashpw بعدها قمنا بتجزئة كلمة المرور باستخدام الدالة hashed_password = bcrypt.hashpw(password, bcrypt.gensalt(12)) # hashed_password في النهاية سيتم إرجاع التجزئة التي تم إنشاؤها لكلمة المرور و التي تم تخزينها في المتغير return hashed_password # و التي سنستعملها لفحص كلمات المرور check_password هنا قمنا بتعريف دالة إسمها def check_password(input_password, hashed_password): # حتى تستطيع الخوارزمية تطبيق العمليات الرياضية عليها Byte Code في البداية يجب تحويل كلمة المرور الأصلية إلى input_password = input_password.encode() # بعدها سيتم تجزئة كلمة المرور المدخلة و مقارنتها بكلمة المرور المخزنة matched = bcrypt.checkpw(input_password, hashed_password) # matched في النهاية سيتم إرجاع نتيجة التحقق التي تم تخزينها في المتغير return matched # hashed_password و من ثم قمنا بتخزينها في المتغير "mypass" لتجزئة كلمة المرور hash_password هنا قمنا بتجربة استخدام الدالة hashed_password = hash_password('mypass') # check_password باستخدام الدالة hashed_password هنا قمنا بالتحقق من كلمة المرور المجزئة و المخزنة في المتغير print(check_password('waleed', hashed_password)) # 'waleed' لأن الكلمة الأصلية ليست False هنا سيتم طباعة print(check_password('mypass', hashed_password)) # 'mypass' لأن الكلمة الأصلية هي True هنا سيتم طباعة
النتيجة
True
هل تأمين كلمات المرور ضروري؟
حماية كلمات المرور ليس خياراً بل ضرورة لأي مطور أو مسؤول نظام. استخدام خوارزميات تجزئة قوية مثل bcrypt،
مع تطبيق مفهوم الـ Salt و عدد جولات مناسب يوفّر طبقة أمان قوية تحمي المستخدمين حتى في أسوأ السيناريوهات.
لا تخزّن أبداً كلمات المرور بنص صريح، و لا تستخدم خوارزميات قديمة أو ضعيفة مثل MD5 أو SHA1.