مولّد الرموز العشوائية
يولّد أسرارًا من مولّد العشوائية المعمّى في المتصفح — hex أو base64url أو أبجدية من عندك، وعبارات مرور من قوائم EFF — مع الإنتروبيا بالبتات.
192.0 بتًّاالزمن الوسطي للعثور عليه عند ثلاثة معدلات تخمين. والفارق بينها أكبر من أي فرق يحدثه السرّ نفسه.
- أمام تسجيل دخول يحدّ المعدل — 10 تخمينات/ث
10^49 سنة - أمام بصمة بطيئة مسرّبة (bcrypt، argon2) — 10⁴/ث
10^46 سنة - أمام بصمة سريعة مسرّبة (SHA-256، MD5) — 10¹²/ث
10^38 سنة
السحب من 64 محرفًا، فيحمل كل واحد 6.00 بتًّا.
ما الذي يفعله هذا الأداة
يُنتج أسرارًا: قيمًا دفاعها الوحيد أن أحدًا لا يستطيع تخمينها. اختر أبجدية وطولًا لمفتاح API أو لكلمة سر قاعدة بيانات، أو انتقل إلى عبارة مرور تُسحب من قائمة منشورة إن كان الذي سيكتبها إنسانًا. وكل نتيجة تُولَّد في متصفحك وتُعرض مع الرقم الوحيد الذي يصف قوتها بصدق.
وهذه مهمة غير مهمة مولّد UUID في هذا الموقع. فالـ UUID يجب أن يكون فريدًا كي لا يتصادم سجلّان أبدًا؛ أما السرّ فيجب أن يكون غير قابل للتخمين، وذلك شرط أقوى وعطب مختلف. المعرّف المتوقَّع غير ضارّ. أما السرّ المتوقَّع فبابٌ مفتوح.
من أين تأتي العشوائية
كل ما هنا يُسحب من crypto.getRandomValues، وهو مولّد العشوائية المعمّى الآمن في المتصفح، ويُبذَر من مخزون الإنتروبيا في نظام التشغيل — المصدر نفسه الذي يستعمله openssl rand و‑/dev/urandom.
أما البديل، Math.random، فليس دالة أمان ولم يدّعِ ذلك قط. هو مولّد شبه عشوائي سريع ذو حالة داخلية صغيرة، وفي كل محرّك حديث يمكن استرجاع تلك الحالة من سلسلة قصيرة من المخرجات، وعندها تُعرف كل قيمة ماضية وآتية. والرمز المبنيّ عليه يبدو عشوائيًّا تمامًا كالحقيقي؛ ولا يظهر الفرق إلا حين يتكلّف أحدهم النظر.
ومن المعقول أن يُسأل: أصفحة الوِب أصلًا هي المكان الصحيح لتوليد سرّ إنتاجي؟ التوليد نفسه متين: فهو CSPRNG المنصّة، والصفحة ساكنة، ولا شيء يُرسَل. أما ما يستحق التفكير فهو ما يحيط به — فالسرّ الذي يمرّ بالحافظة قد تقرأه تطبيقات أخرى، والذي يُلصَق في طرفية ينتهي عادةً في سجلّ الأوامر. وهذه هي الاعتبارات نفسها في أي طريقة أخرى.
الانحياز المختبئ في التنفيذ البديهي
تحويل البايتات العشوائية إلى محارف يبدو سطرًا واحدًا: خذ بايتًا، خذ باقي قسمته على حجم الأبجدية، ثم فهرِس. وهو خطأ خفيّ في كل أبجدية لا يقسم حجمها 256 قسمة تامّة، والمخرج لا يُظهر من ذلك شيئًا.
فمع 62 محرفًا أبجديًّا رقميًّا لا تتمّ القسمة: يدخل 62 في 256 أربع مرات ويفضل 8. وهذه القيم الثماني الفاضلة — من 248 إلى 255 — تنطوي على المحارف الثمانية الأولى من الأبجدية، فينال كل واحد منها خمس فرص من 256 بينما ينال الباقي أربعًا. فتصير المحارف الثمانية الأولى أرجح من غيرها بنحو 25%.
والعلاج هو المعاينة الرافضة: أن تُطرَح السحبة التي تقع في الذيل الفاضل ويُسحَب من جديد، بدل طيّها إلى الداخل. ويكلّف ذلك نحو سحبة زائدة من كل اثنتين وثلاثين في أبجدية من 62 محرفًا، ولا يكلّف شيئًا البتة في hex أو base64url، إذ حجمهما قوة للعدد اثنين فلا ذيل لهما. وهذا الأداة يرفض؛ والأثر لا يُرى في أي رمز مفرد، ولهذا بالذات يستحق أن يُقال.
الإنتروبيا، وهي المقياس الصادق الوحيد
الإنتروبيا بالبتات تقول كم يتّسع فضاء الأسرار المتساوية الاحتمال: n بتًّا تعني 2^n احتمالًا. وهي جمعية يسيرة المقارنة، ولا تقول شيئًا عن سرعة من يمسح ذلك الفضاء — وتلك ميزة، لأن ذلك الشقّ يتعلّق بأمور لا سبيل للمولّد إلى معرفتها.
والحساب بسيط عن قصد. كل محرف يسهم بـ log2(حجم الأبجدية) بتًّا، وكل كلمة تسهم بـ log2(حجم القائمة)، ومن ثمّ:
- محرف hex أربعة بتات، فرمز hex من 32 محرفًا هو 128 بتًّا بالضبط: مفتاح AES-128 مكتوبًا بتمامه.
- محرف base64url ستة بتات، فاثنان وعشرون محرفًا تتجاوز 128 بتًّا، وثلاثة وأربعون تتجاوز 256.
- المحرف الأبجدي الرقمي نحو 5.95 بتًّا؛ واستبعاد المتشابهات يهبط بالأبجدية إلى 58 وبالمحرف إلى 5.86، وهو ما يكلّف نحو محرف واحد من الطول في كل اثني عشر.
- كلمة من قائمة EFF الطويلة 12.925 بتًّا، لأن في القائمة 7776 مدخلًا، وهي 6^5 — خمس رميات نرد.
- وكلمة من القائمة القصيرة 10.34 بتًّا، من 1296 مدخلًا، وهي 6^4.
والهدف المعتاد 128 بتًّا، وهو الموضع الذي تكفّ عنده القوة الغاشمة عن أن تكون استراتيجية بدل أن تكون مجرد مكلفة. وذلك 32 محرف hex، أو 22 محرف base64url، أو 10 كلمات من قائمة EFF الطويلة.
لماذا لا يوجد «زمن كسر» واحد
ليس للسرّ زمن كسر. إنما الزمن للزوج المؤلَّف من السرّ ومما يحرسه، والحارس أثقل وزنًا من السرّ بكثير. فالقيمة نفسها التي يستغرق العثور عليها عبر نموذج دخول أطول من عمر الكون، تسقط في أصيل واحد إن كانت مخزّنة بصيغة SHA-256 بلا ملح وتسرّبت قاعدة البيانات.
ولذلك تُعرَض ثلاثة معدلات لا رقم واحد، وكل معدل مسمّى باسمه:
- عشر محاولات في الثانية، أمام تسجيل دخول يحدّ المعدل. وهذا هو السقف الواقعي لمن عليه أن يمرّ عبر خدمتك.
- عشرة آلاف محاولة في الثانية، أمام بصمة كلمة سر مسرّبة صُنعت لتكون بطيئة — bcrypt بكلفة حديثة، أو argon2. وهذا البطء هو كل غاية تلك الدوال.
- تريليون محاولة في الثانية، أمام بصمة مسرّبة لم تُقصد لكلمات السرّ قط. فـ SHA-256 و‑MD5 مصمَّمان للسرعة، ومنصّة من معالجات الرسوم سريعة سرعة هائلة.
والمعدلات مقرَّبة إلى قوى العشرة عن قصد. فأي دقة أزيد توحي بقياس مهاجم بعينه، بينما المعلومة النافعة هي المراتب الإحدى عشرة بين السطر الأول والأخير. فإن كان السرّ مرتاحًا في الثلاثة جميعًا، فالمسألة محسومة دون حاجة إلى الأرقام المضبوطة.
وكل رقم هو المتوسّط، أي نصف الفضاء لا كلّه — إذ يعثر البحث على الجواب في منتصف الطريق وسطيًّا. وبعد المليون سنة يُعطى الجواب مرتبةً من العِظَم، لأن «أربعمئة تريليون سنة» ليست مدةً يقدر أحد على مقارنتها. وعمر الكون نحو 10^10 سنة، وهو مرساة نافعة للسطور التي تبلغ ذلك المدى.
عبارات المرور، ومن أين تأتي قوّتها حقًّا
عبارة المرور قوية لسبب واحد بعينه: أن عدة كلمات اختيرت عشوائيًّا من قائمة كبيرة. لا لأنها طويلة، ولا لأنها تشبه اللغة. فست كلمات عشوائية من قائمة فيها 7776 كلمة نحو 77 بتًّا؛ أما ست كلمات فكّر فيها إنسان فتساوي أقلّ من ذلك بكثير، لأن الناس لا يختارون اختيارًا منتظمًا، ولأن المهاجم يعرف عن العبارات الشائعة ما تعرفه أنت نفسه.
والقائمتان هنا من EFF وهما قائمتا النرد المنشورتان، نُزّلتا بلا تغيير. وليستا قائمتين جُمعتا هنا، وهذا مهمّ: فقائمة كلمات اخترعها أحدهم حجمها مجهول، وتداخلها مع سواها مجهول، ولا سبيل إلى التحقق من دعوى الإنتروبيا التي تُقال فيها.
- القائمة الطويلة فيها 7776 كلمة، واحدة لكل رمية بخمس نردات، وتعطي 12.925 بتًّا للكلمة.
- والقائمة القصيرة فيها 1296 كلمة، واحدة لكل رمية بأربع نردات، وتعطي 10.34 بتًّا للكلمة. وكلماتها أقصر — ما من كلمة تتجاوز خمسة أحرف — وهو ما يسهّل الكتابة بثمن أن تلزم كلمات أكثر للقوة نفسها.
والكلمة المكرَّرة في عبارة مرور ليست عيبًا ولا تُعاد سحبتها هنا. فكل سحبة مستقلة، فأي زوج بعينه من الكلمات محتمل تمامًا كأي زوج آخر؛ ورفض التكرار كان يضيّق فضاء عبارات المرور الممكنة فيجعلها أضعف قليلًا لا أقوى.
وفي قائمتي EFF كلتيهما حفنة مداخل موصولة بشَرطة: t-shirt و‑yo-yo و‑drop-down و‑felt-tip. فإن كان الفاصل شَرطةً أيضًا، تعذّر ردّ عبارة تحوي إحداها إلى كلماتها ردًّا لا لبس فيه. وتلك مشكلة عرض لا مشكلة أمان، واختيار المسافة أو النقطة فاصلًا يتجنّبها كليًّا.
الخيارات التي تكلّف إنتروبيا، وواحد لا يكلّف
استبعاد المحارف المتشابهة يبادل القوة بالوضوح، والمبادلة ظاهرة. فإزالة 0 و‑O و‑I و‑l تنقل الأبجدية الأبجدية الرقمية من 62 محرفًا إلى 58 — وهي بالضبط أبجدية base58 التي يستعملها بيتكوين، وللسبب نفسه. وينزل كل محرف من 5.95 إلى 5.86 بتًّا، فيحتاج الرمز إلى محرف زائد في كل اثني عشر تقريبًا ليبقى بالقوة ذاتها. وذلك يستحقّ عادةً في كل ما سيقرؤه إنسان من الشاشة ليكتبه في مكان آخر.
والخيار معروض حيث يعني شيئًا فحسب. فـ hex و‑base64url ترميزان لا مجموعتا محارف: أبجديتاهما مثبّتتان في مواصفة، و‑hex بلا 0 لم يعد hex — إذ لن يقدر شيء على فكّ ترميزه.
وجعل أول كل كلمة من عبارة المرور حرفًا كبيرًا لا يضيف شيئًا البتة، ورقم القوة هنا لا يتحرك عن قصد حين تشغّله. فهو التحويل نفسه المطبَّق في كل مرة، فلا يخلق ولو احتمالًا جديدًا واحدًا: والمهاجم الذي يعلم أن العبارة مكتوبة بالحروف الكبيرة واقف حيث بدأ تمامًا. والخيار موجود لأن حقول كلمات السرّ ما تزال تطالب بحرف كبير، لا لأنه يفيد.
اختيار الطول
أما ما تتولاه آلة — مفاتيح API ورموز الجلسات وأسرار الـ webhook وكلمات سرّ قواعد البيانات — فلا داعي فيه للتقتير. فاثنان وثلاثون محرف hex أو اثنان وعشرون محرف base64url تعطي 128 بتًّا، والمضيّ أبعد لا يكلّف إلا بايتات في ملف إعدادات.
وأما ما يكتبه إنسان فالقيد فيه مختلف، وعبارة المرور عادةً هي الصيغة الأفضل. فست كلمات من القائمة الطويلة أقوى من كلمة سرّ عشوائية من اثني عشر محرفًا، وإصابتها من أول محاولة أيسر بكثير، وذلك أهمّ مما يبدو: فالسرّ الذي يخطئ الناس في كتابته سرّ يدوّنه الناس على ورقة.
وأما الرمز الرقمي فالقراءة الصادقة أن أربعة أرقام هي 13 بتًّا، وتُستنفَد في ثوانٍ أمام أي شيء لا يحدّ المعدل. فرمز PIN آمن بفضل سياسة القفل المحيطة به وحدها، لا بقوّته هو أبدًا.
الأسئلة الشائعة
- ما الطول المناسب لمفتاح API؟
- استهدف 128 بتًّا من الإنتروبيا أو أكثر، وهو الموضع الذي تكفّ عنده القوة الغاشمة عن أن تكون استراتيجية. وذلك 32 محرف hex، أو 22 محرف base64url، أو 43 محرف base64url إن أردت 256 بتًّا. وللقيمة التي لا يتولاها إلا البرنامج، لا يكلّف الخيار الأطول شيئًا.
- أهذا أأمن من توليد سرّ بـ Math.random؟
- نعم، والفرق ليس فرق درجة. فـ Math.random مولّد شبه عشوائي ذو حالة صغيرة تتيح المحرّكات الحالية استرجاعها من سلسلة قصيرة من المخرجات، وعندها تُعرف كل قيمة سيُنتجها يومًا. أما هذه الأداة فتستعمل crypto.getRandomValues، وهو CSPRNG المنصّة، وهو المصدر نفسه الذي يستقي منه openssl rand.
- لماذا لا تعرضون «زمن كسر» واحدًا؟
- لأن ذلك الرقم غير موجود. فالسرّ الواحد بعينه بعيد المنال خلف تسجيل دخول يحدّ المعدل، ويسقط سريعًا خلف بصمة سريعة مسرّبة — والمسافة بينهما إحدى عشرة مرتبة. والرقم الواحد مضطر إلى اختيار فرضية ثم إخفائها، والقارئ يحفظ الرقم لا الفرضية. أما ثلاثة معدلات مسمّاة فتضع الفرضية حيث تُرى.
- أعبارات المرور أضعف من السلاسل العشوائية؟
- لا: القوة متعلقة فقط بعدد الكلمات المسحوبة وحجم القائمة، وكلاهما معروض. فست كلمات من قائمة EFF الطويلة نحو 77 بتًّا، وهي أكثر من كلمة سرّ عشوائية من عشرة محارف. والذي يُضعف عبارة المرور هو اختيار كلماتها بنفسك، وهذا ما لا يحدث هنا.
- أأستبعد المحارف المتشابهة؟
- إن كان إنسان سيقرأ السرّ من الشاشة ليكتبه في مكان آخر فنعم: الكلفة صغيرة، والرمز الذي يُقرأ خطأً يصير طلب دعم. وإن لم يتولّه إلا البرنامج فلا داعي. وإزالة 0 و‑O و‑I و‑l تنقل الأبجدية من 62 محرفًا إلى 58، وهي بالضبط أبجدية base58، وتكلّف نحو محرف من الطول في كل اثني عشر.
- أتزيد الحروف الكبيرة عبارة المرور قوة؟
- لا. فهو التغيير نفسه المطبَّق في كل مرة، ولا يضيف احتمالًا واحدًا إلى فضاء العبارات. ورقم الإنتروبيا يبقى ثابتًا حين تشغّله، وذلك هو السلوك الصحيح. والخيار موجود لأن بعض حقول كلمات السرّ تصرّ على حرف كبير.
- أيُرسَل شيء مما أولّده إلى خادم؟
- لا. التوليد يجري كلّه في متصفحك بمولّد العشوائية في المنصّة، ولا شيء يُرفَع أو يُسجَّل، وهو يعمل بلا اتصال بالشبكة. والرموز لا تُحفظ في أي مكان: فإعادة تحميل الصفحة تُنتج رموزًا جديدة، والقديمة تزول.