توليد سلسلة عشوائية في Go: دليل كامل مع كود

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

كيف تولد سلسلة عشوائية في Go دون أن تصاب بالجنون؟

\n\n

هل سبق لك أن واجهت موقفًا تحتاج فيه إلى إنشاء معرف فريد بسرعة، أو كلمة مرور مؤقتة، أو رمز مميز لـ API؟ إذا كنت تكتب بلغة Go، فستواجه عاجلاً أم آجلاً السؤال: "كيف يمكن توليد سلسلة عشوائية بطول ثابت؟". هذا السؤال حصد آلاف المشاهدات على Stack Overflow، وليس من دون سبب - فالمهمة التي تبدو بسيطة تخفي في طياتها عدة مطبات.

\n\n

في هذه المقالة، سنشرح توليد سلسلة عشوائية في Go بدءًا من الحلول البسيطة وصولاً إلى الأساليب الجاهزة للإنتاج. ستتعرف على لماذا قد يكون math/rand خطيرًا، وكيفية استخدام crypto/rand للأمان، وكيفية كتابة كود أنيق دون تبعيات غير ضرورية.

\n\n

لماذا لا يجب استخدام math/rand لكلمات المرور؟

\n\n

لنبدأ بالحل الأكثر شيوعًا، ولكنه ليس دائمًا صحيحًا. يكتب العديد من المبتدئين شيئًا كهذا:

\n\n
package main\n\nimport (\n    "math/rand"\n    "time"\n)\n\nconst letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"\n\nfunc RandString(n int) string {\n    b := make([]byte, n)\n    for i := range b {\n        b[i] = letters[rand.Intn(len(letters))]\n    }\n    return string(b)\n}\n\nfunc main() {\n    rand.Seed(time.Now().UnixNano())\n    println(RandString(10))\n}
\n\n

هذا الكود يعمل، لكنه يعاني من عيب خطير: math/rand يستخدم خوارزمية حتمية. إذا عرف المهاجم الـ seed (أو وقت التوليد)، فسيتمكن من إعادة إنتاج التسلسل بأكمله. هذا مقبول لبيانات الاختبار أو أسماء الملفات، لكنه مرفوض تمامًا للرموز السرية وكلمات المرور.

\n\n

استخدام crypto/rand: توليد سلسلة عشوائية آمنة في Go

\n\n

للبيئات الإنتاجية، الخيار الصحيح هو حزمة crypto/rand. فهي تستخدم مولد أرقام عشوائية آمنًا تشفيريًا من نظام التشغيل. إليك كيف يبدو توليد سلسلة عشوائية في Go باستخدام crypto/rand:

\n\n
package main\n\nimport (\n    "crypto/rand"\n    "math/big"\n)\n\nconst charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()"\n\nfunc SecureRandomString(length int) (string, error) {\n    result := make([]byte, length)\n    for i := range result {\n        num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))\n        if err != nil {\n            return "", err\n        }\n        result[i] = charset[num.Int64()]\n    }\n    return string(result), nil\n}\n\nfunc main() {\n    str, err := SecureRandomString(16)\n    if err != nil {\n        panic(err)\n    }\n    println(str)\n}
\n\n

لاحظ أننا نستخدم rand.Int مع rand.Reader - وهذا يضمن اختيار كل حرف بشكل عشوائي وغير قابل للتنبؤ. بالنسبة لسلسلة عشوائية في Go، هذا هو المعيار الذهبي.

\n\n

تحسين الأداء: كيفية تسريع التوليد

\n\n

قد تكون الحلقة مع استدعاء rand.Int لكل حرف بطيئة عند توليد مئات الآلاف من السلاسل. الحل هو توليد جميع البايتات العشوائية دفعة واحدة، ثم تعيينها إلى أبجديتنا. هذه الحيلة غالبًا ما تُناقش على Stack Overflow في سياق Golang random string:

\n\n
package main\n\nimport (\n    "crypto/rand"\n    "encoding/binary"\n)\n\nconst charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"\n\nfunc FastRandomString(length int) string {\n    result := make([]byte, length)\n    // توليد أرقام عشوائية 64 بت\n    buf := make([]byte, 8*length)\n    if _, err := rand.Read(buf); err != nil {\n        panic(err)\n    }\n    for i := 0; i < length; i++ {\n        // أخذ 8 بايت لكل حرف\n        val := binary.LittleEndian.Uint64(buf[i*8 : (i+1)*8])\n        // تجنب الانحياز (bias) باستخدام المعامل\n        result[i] = charset[val%uint64(len(charset))]\n    }\n    return string(result)\n}\n\nfu                

المدونات

توصيات الكتب