الفرق بين random و secrets في بايثون: متى تستخدم كل مكتبة وما المخاطر الأمنية؟

الفرق بين random و secrets في بايثون: متى تستخدم كل مكتبة وما المخاطر الأمنية؟

مقدمة: لماذا توجد مكتبتان للتوليد العشوائي في بايثون؟

في عالم البرمجة، التوليد العشوائي هو أحد الأدوات الأساسية التي تُستخدم في الكثير من التطبيقات. سواءً كنت تطوّر لعبة تحتاج إلى اختيار عشوائي لحركات الخصم، أو كنت تُجري محاكاة لبيانات عشوائية، أو حتى تبني نظامًا لتسجيل الدخول باستخدام رموز تحقق مؤقتة، فإنك ستحتاج إلى نوع من أنواع العشوائية. لكن ليس كل عشوائي يُعتبر "عشوائيًا" بالمعنى نفسه.

بايثون توفّر مكتبتين رئيسيتين للتعامل مع العشوائية: random وsecrets. قد يظن البعض أن كلاهما يؤدي الغرض ذاته، لكنه في الواقع هناك فرق جوهري بينهما مرتبط بالغرض من الاستخدام.

مكتبة random مصممة للاستخدامات العامة التي لا تتطلب أمانًا عاليًا، مثل الألعاب والاختبارات البسيطة، وتعتمد على مولد أرقام "شبه عشوائي" (Pseudo-Random Number Generator). هذا يعني أن النتائج التي تنتجها يمكن التنبؤ بها إذا عرفت الحالة الأولية (seed) التي بدأ بها التوليد.

أما مكتبة secrets، فقد ظهرت في بايثون لتلبية حاجة حقيقية في التطبيقات الأمنية. فهي تولد أرقامًا عشوائية يصعب - بل يستحيل عمليًا - التنبؤ بها، وتُستخدم في توليد كلمات مرور، رموز تحقق، مفاتيح سرية، وكل ما يرتبط بالأمان الرقمي.

في هذا المقال، سنتعمق في الفرق بين random وsecrets، ونوضح متى تستخدم كل واحدة، ولماذا الاختيار الخاطئ بينهما قد يكون له تبعات أمنية خطيرة.

كيف يمكن للحاسوب أن يختار عشوائيًا؟

الحاسوب بطبيعته جهاز حتمي، أي أنه ينفذ التعليمات بنفس الطريقة كل مرة. لهذا، لا يستطيع أن يولّد "عشوائية حقيقية" مثل ما يحدث في الطبيعة. بدلاً من ذلك، يستخدم خوارزميات رياضية تُنتج أرقامًا تبدو عشوائية، لكنها في الواقع ناتجة عن عمليات حسابية تبدأ من قيمة ابتدائية تُعرف باسم seed. هذا ما يُسمى "العشوائية شبه الحقيقية" (Pseudo-Randomness)، وهي ما تستخدمه مكتبة random.

أما للحصول على عشوائية حقيقية أو قريبة جدًا منها، كما تفعل مكتبة secrets، فإن بايثون تعتمد على مصادر عشوائية على مستوى النظام مثل os.urandom، التي تسحب بيانات غير متوقعة من البيئة الفعلية للجهاز (مثل حركة الماوس، توقيتات الشبكة، إلخ)، مما يجعل التنبؤ بها صعبًا أو مستحيلاً.

مكتبة random: التوليد العشوائي للأغراض العامة

مكتبة random هي الأداة الافتراضية في بايثون لتوليد أرقام أو قيم عشوائية. وهي مصممة لتكون سريعة وسهلة الاستخدام في البرامج التي لا تتطلب أمانًا عاليًا. تعتمد random على خوارزمية تولد أرقامًا "شبه عشوائية"، أي أنها قد تبدو عشوائية للمستخدم، لكنها في الحقيقة ناتجة عن سلسلة حسابات تبدأ من نقطة معينة (seed). هذا يعني أنك إذا استخدمت نفس seed مرتين، ستحصل على نفس التسلسل العشوائي في كل مرة.

وهذا السلوك مفيد في بعض الحالات مثل الاختبارات البرمجية أو المحاكاة، حيث تريد أن تعيد نفس النتائج كل مرة لأغراض التصحيح أو التجربة.

أهم الوظائف في مكتبة random:

  • random.random(): يولّد عدد عشري بين 0 و1.

  • random.randint(a, b): يولد عددًا صحيحًا بين a وb.

  • random.choice(seq): يختار عنصرًا عشوائيًا من قائمة أو سلسلة.

  • random.shuffle(seq): يعيد ترتيب العناصر في القائمة عشوائيًا.

  • random.seed(value): يضبط نقطة البداية لتوليد العشوائية.

مثال عملي:

import random

names = ['Ali', 'Sara', 'John', 'Lina']
winner = random.choice(names)
print("The winner is:", winner)

في هذا المثال، يتم اختيار اسم عشوائي من القائمة. النتيجة ستتغير في كل تشغيل للبرنامج، ما لم تستخدم random.seed().

لكن رغم سهولة استخدام random، يجب أن تتجنب استخدامها في أي تطبيق يتعامل مع الأمن أو الخصوصية، لأنها ليست مقاومة للهجمات التي تحاول التنبؤ بالقيم الناتجة.

مكتبة secrets: العشوائية الآمنة للتشفير

مكتبة secrets جاءت لتسد فجوة خطيرة في عالم التوليد العشوائي في بايثون، وهي الحاجة إلى "عشوائية آمنة تشفيريًا" (Cryptographically Secure Randomness). هذه العشوائية مطلوبة في التطبيقات التي تعتمد على أمان البيانات، مثل إنشاء كلمات مرور، رموز تحقق (OTP)، رموز الدخول المؤقتة (tokens)، أو مفاتيح المصادقة.

على عكس مكتبة random، التي يمكن التنبؤ بقيمها إذا عرف المهاجم قيمة seed أو تسلسل القيم السابقة، فإن secrets تعتمد على مصادر عشوائية حقيقية من نظام التشغيل، مثل os.urandom()، والتي توفر بيانات يصعب أو يستحيل توقعها.

أهم الوظائف في مكتبة secrets:

  • secrets.choice(seq): يختار عنصرًا عشوائيًا من تسلسل بطريقة آمنة.

  • secrets.randbelow(n): يُولد رقمًا عشوائيًا بين 0 وn-1 بأمان.

  • secrets.token_hex(nbytes=...): يولد سلسلة عشوائية من أحرف hex.

  • secrets.token_urlsafe(nbytes=...): يولد سلسلة آمنة للاستخدام في URLs أو التوثيق.

مثال عملي:

import secrets

alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
password = ''.join(secrets.choice(alphabet) for _ in range(12))
print("Generated secure password:", password)

في هذا المثال، يتم توليد كلمة مرور مكونة من 12 حرفًا باستخدام secrets.choice. كل اختيار يتم بطريقة عشوائية لا يمكن التنبؤ بها، حتى لو أعيد تشغيل البرنامج مئات المرات.

مكتبة secrets ضرورية في أي مشروع يتعامل مع بيانات حساسة أو مستخدمين حقيقيين. استخدام random بدلًا منها في هذه الحالات يعتبر خللاً أمنيًا قد يؤدي إلى اختراق النظام أو تجاوز التحقق.

مقارنة مباشرة بين random و secrets

من السهل الوقوع في خطأ استخدام مكتبة random في سيناريو يتطلب أمانًا عاليًا، خصوصًا أن random.choice وsecrets.choice يقدمان نفس الوظيفة من حيث المظهر. لكن عند النظر إلى الفرق العميق بين المكتبتين، يتضح أن كل واحدة صُممت لغرض محدد، ولا يجوز الخلط بينهما.

فيما يلي مقارنة تفصيلية توضح الفروقات الجوهرية بين random وsecrets:

الخاصية random secrets
مستوى الأمان غير آمن للتشفير آمن تشفيريًا
إمكانية إعادة النتائج (seed) نعم، عبر random.seed() لا، لا توجد إمكانية للتحكم في البداية
التنبؤ بالقيم ممكن، خاصة إذا عرف المهاجم الـ seed مستحيل تقريبًا
سرعة التنفيذ أسرع أبطأ قليلًا بسبب استخدام مصادر نظامية
مناسبة للألعاب والمحاكاة نعم لا
مناسبة لكلمات المرور/الرموز لا نعم
تعتمد على خوارزمية حسابية داخلية مصدر عشوائية من النظام (os.urandom)

خلاصة المقارنة:

  • random ممتازة للعمليات غير الأمنية: ألعاب، نماذج، اختبارات.

  • secrets ضرورية عندما يكون هناك تعامل مع بيانات حساسة أو توثيق أو تشفير.

  • الاختيار الخاطئ قد يؤدي إلى ثغرات أمنية حقيقية.

الفهم الصحيح لهذه الفروقات ليس مجرد مسألة كفاءة برمجية، بل أحيانًا يكون الفاصل بين تطبيق آمن وآخر يمكن كسره بسهولة.

متى تستخدم random؟

تُستخدم مكتبة random عندما يكون التوليد العشوائي مطلوبًا لأغراض غير أمنية، أي عندما لا يهم إذا استطاع شخص ما التنبؤ بالقيم الناتجة. هذا النوع من الاستخدام شائع جدًا في البرمجة اليومية، خاصة في مجالات مثل:

  • تطوير الألعاب: مثل اختيار حركة عشوائية للخصم، أو توزيع عناصر على الخريطة.

  • المحاكاة: توليد بيانات شبه واقعية لاختبار نظام أو خوارزمية.

  • اختبارات الأداء: إدخال بيانات عشوائية إلى البرنامج لتجربته تحت ظروف مختلفة.

  • النماذج الإحصائية: اختيار عينات عشوائية من بيانات كبيرة.

  • خلط ترتيب العناصر: كأن تعيد ترتيب قائمة من الأسئلة في اختبار.

مثال عملي:

import random

questions = ['Q1', 'Q2', 'Q3', 'Q4']
random.shuffle(questions)
print("Shuffled questions:", questions)

في المثال أعلاه، يتم خلط ترتيب الأسئلة عشوائيًا. لا يهم إن كانت النتائج قابلة للتكرار أو يمكن التنبؤ بها، لأن الاستخدام غير حساس أمنيًا.

متى تستخدم secrets؟

تُستخدم مكتبة secrets في كل حالة يكون فيها الأمان مطلبًا أساسيًا. أي تطبيق أو نظام يعتمد على التشفير أو حماية المستخدمين أو التحكم في الوصول يجب أن يتجنب تمامًا استخدام random، ويعتمد بدلاً منها على secrets.

الاستخدامات الشائعة لـ secrets:

  • توليد كلمات مرور للمستخدمين: يجب أن تكون غير قابلة للتنبؤ بأي شكل.

  • إنشاء رموز تحقق (OTP): تُستخدم في التحقق بخطوتين أو استعادة الحسابات.

  • توليد رموز الدخول (access tokens): التي تُرسل في الروابط أو رؤوس الطلبات (HTTP headers).

  • بناء معرفات آمنة للجلسات (session IDs): التي تُستخدم لتحديد هوية المستخدم عند تصفحه للموقع.

  • توقيع الطلبات أو التوثيق الداخلي: أي حالة تعتمد على "سر" يجب ألا يكون قابلًا للتخمين.

مثال عملي:

import secrets

token = secrets.token_urlsafe(16)
print("Secure token:", token)

في هذا المثال، يتم توليد رمز دخول آمن يمكن إرساله للمستخدم كجزء من رابط تحقق عبر البريد الإلكتروني. هذا الرمز لا يمكن التنبؤ به حتى لو حاول المهاجم توليد ملايين القيم.

لماذا لا تستخدم random هنا؟

لأن أي رمز تحقق أو كلمة مرور يمكن التنبؤ بها – حتى لو بنسبة ضئيلة – هي ثغرة حقيقية. المهاجم يمكنه كتابة سكربت يجرب القيم الممكنة حتى ينجح، وإذا كانت العشوائية ضعيفة فسيكفيه وقت قصير.

ماذا يحدث لو استخدمت random بدلاً من secrets في تطبيق حساس؟

استخدام مكتبة random في تطبيقات تتعلق بالأمان هو خطأ برمجي جسيم قد يؤدي إلى كارثة أمنية حقيقية. السبب في ذلك أن random تعتمد على خوارزميات يمكن التنبؤ بها إذا عرف المهاجم أو خمّن القيمة الأولية (seed). هذه القابلية للتنبؤ تجعل من السهل تنفيذ هجمات مثل:

  • تخمين كلمات المرور: إذا تم توليدها باستخدام random، يمكن للمهاجم إعادة توليد نفس التسلسل.

  • تزوير رموز التحقق أو الدخول: إذا عرف المستخدم نمط القيم الناتجة، قد يتمكن من توليد رموز صالحة بنفسه.

  • اختراق الجلسات (Session Hijacking): إذا تم إنشاء معرفات الجلسة عبر random، يمكن التنبؤ بها والوصول إلى حسابات المستخدمين.

مثال كارثي:

افترض أنك كتبت الكود التالي:

import random

def generate_token():
    return ''.join(random.choice('abcdef0123456789') for _ in range(16))

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

ماذا يجب أن تفعل بدلًا من ذلك؟

استخدم مكتبة secrets كما في المثال الآمن التالي:

import secrets

def generate_token():
    return secrets.token_hex(16)

بهذا الشكل، تضمن أن الرمز غير قابل للتنبؤ أو التكرار، حتى من قبل مطورين آخرين أو مستخدمين خبيثين.

القاعدة الذهبية: إذا كان يمكن استغلال العشوائية في اختراق نظامك، لا تستخدم random.

علاقة secrets بـ os.urandom

مكتبة secrets في بايثون ليست "سحرية"، بل هي مجرد واجهة سهلة الاستخدام تُبسط التعامل مع مصادر العشوائية الآمنة المتوفرة في نظام التشغيل. في خلفيتها، تعتمد secrets غالبًا على دالة os.urandom()، والتي بدورها تطلب من نظام التشغيل توليد بايتات عشوائية باستخدام مصادر فيزيائية أو شبه فيزيائية يصعب التنبؤ بها.

ما هو os.urandom؟

os.urandom(n) هي دالة تُرجع n بايت من البيانات العشوائية التي تأتي من مصدر موثوق مثل:

  • /dev/urandom في أنظمة Linux وUnix.

  • Crypto API في Windows.

  • SecureRandom في أنظمة أخرى.

هذه البيانات تُستخدم في تطبيقات التشفير لأنها لا تعتمد على خوارزميات حسابية فقط، بل على مصادر خارجية مثل التوقيتات الدقيقة، حرارة المعالج، تردد الشبكة، وغيرها من المتغيرات غير المتوقعة.

مثال باستخدام os.urandom مباشرة:

import os
token = os.urandom(16).hex()
print("Secure token:", token)

لكن os.urandom ترجع بايتات خام، وهو ما يجعل استخدامها غير مريح في معظم السيناريوهات. لذلك جاءت مكتبة secrets لتغلف هذه العملية وتوفر وظائف مثل:

  • secrets.token_hex(n) ← تعتمد على os.urandom(n)

  • secrets.token_urlsafe(n) ← تعتمد على base64 لتمثيل نتيجة os.urandom(n)

  • secrets.randbelow(n) ← تعتمد على تحويل بايتات os.urandom() إلى أرقام.

بالتالي، يمكن القول إن مكتبة secrets هي مجرد واجهة موثوقة وسهلة لاستغلال قوة os.urandom دون الحاجة للتعامل مع التفاصيل المنخفضة المستوى.

استخدام secrets يضمن لك أفضل ممارسات الأمان، دون الحاجة لفهم البنية العميقة لأنظمة التشغيل أو كيفية توليد العشوائية فيها.

الخلاصة

الفهم الصحيح للفروقات بين مكتبة random ومكتبة secrets في بايثون ليس مجرد تمييز تقني، بل هو قرار أمني قد يحمي مشروعك من اختراق محتمل أو كارثة أمنية.

  • مكتبة random ممتازة وسريعة وتؤدي الغرض في كل ما هو غير أمني: ألعاب، اختبارات، محاكاة، ترتيب عشوائي… لكنها غير مناسبة إطلاقًا في أي سياق يحتوي على كلمات مرور أو رموز تحقق.

  • مكتبة secrets أبطأ قليلًا لكنها توفر عشوائية آمنة لا يمكن التنبؤ بها، وهي الخيار الصحيح لتوليد الرموز، المفاتيح، كلمات المرور، وكل ما له علاقة بحماية البيانات أو هوية المستخدم.

أي خلط بين المكتبتين قد يؤدي إلى عواقب خطيرة، ليس فقط على مستوى الأمان بل على مصداقية مشروعك ككل.

القاعدة البسيطة:
– استخدم random إذا كنت لا تخشى أن يخمن أحد القيم الناتجة.
– استخدم secrets إذا كنت لا تريد أن يتمكن أحد من التنبؤ بما ولّدته، حتى لو حاول آلاف أو ملايين المرات.

التشفير لا يُبنى على "يبدو جيدًا"، بل على رياضيات صارمة ومصادر عشوائية موثوقة. ومكتبة secrets هي السبيل لذلك في بايثون.

حول المحتوى:

تعرف على الفرق الجوهري بين مكتبة random ومكتبة secrets في بايثون، واكتشف متى تستخدم كل واحدة، ولماذا قد يؤدي الاستخدام الخاطئ إلى ثغرات أمنية خطيرة. المقال يشمل أمثلة عملية ومقارنة مباشرة.