توليد أرقام عشوائية آمنة: أساس التشفير القوي
التوليد العشوائي الآمن حاسم للتشفير. تعلم كيف يعمل crypto.getRandomValues ولماذا العشوائية الضعيفة تكسر نموذج أمانك.
توليد الأرقام العشوائية الآمنة هو الأساس الذي يرتكز عليه كل مكوّن تشفيري آخر. في JavaScript، تملأ crypto.getRandomValues(buffer) مصفوفة مُكتَّبة (TypedArray) ببايتات عشوائية آمنة تشفيرياً مصدرها مولّد الأرقام العشوائية الآمن (CSPRNG) الخاص بنظام التشغيل — /dev/urandom على Linux/macOS، وBCryptGenRandom على Windows، وSecRandomCopyBytes على iOS/macOS. لا تستخدم Math.random() في أي شيء يمس الأمان؛ فهو مولّد أرقام شبه عشوائية (PRNG) من طراز Mulberry32 أو xorshift، مصمَّم للسرعة لا لعدم القدرة على التنبؤ، ويمكن التنبؤ بمخرجاته بعد رصد عينات قليلة. إن المولّد الضعيف يكسر مفاتيح AES، ومصافحات TLS، وتفرّد nonces في وضع GCM، وعدم قابلية الرموز للتخمين، وكل أداة أمانية أخرى تعتمد على بتّات غير قابلة للتنبؤ.
الفرق بين العشوائي والعشوائي التشفيري
يُنتج PRNG تيّاراً حتمياً من بذرة (seed). وبمعرفة البذرة والخوارزمية، يمكن إعادة إنتاج كل مخرجاتها — أمر مناسب للألعاب والمحاكاة وطرق مونت كارلو، لكنه كارثي في التشفير.
أما CSPRNG فيُستهَل من مصدر إنتروبيا حقيقية (ضجيج حراري، توقيت المقاطعات، تعليمات مولّد الأرقام العشوائية للأجهزة مثل RDSEED من Intel)، ويضمن تصميمه أن مخرجاته لا يمكن تمييزها حسابياً من العشوائية الحقيقية، وأن رصد المخرجات السابقة لا يُعين على التنبؤ بالمخرجات المستقبلية.
تتيح JavaScript كليهما: Math.random() هو PRNG، أما crypto.getRandomValues() فهو غلاف حول CSPRNG الخاص بنظام التشغيل. فرق سطر واحد في الكود، وفارق أمني هائل.
الاستخدام الصحيح الأمثل
// توليد بايتات عشوائية بحجم مفتاح AES ذي 256 بت
const keyBytes = crypto.getRandomValues(new Uint8Array(32));
// توليد nonce ذي 96 بتاً لوضع GCM
const nonce = crypto.getRandomValues(new Uint8Array(12));
// توليد salt ذي 128 بتاً
const salt = crypto.getRandomValues(new Uint8Array(16));
// توليد رمز عشوائي آمن للـ URL
const tokenBytes = crypto.getRandomValues(new Uint8Array(32));
const token = btoa(String.fromCharCode(...tokenBytes))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
crypto.getRandomValues() متزامنة، تملأ المخزن المؤقت في مكانه وتُعيده. الحد الأقصى للطلب الواحد 65,536 بايت (حصة تفرضها المواصفة لتجنب الحظر). للحصول على مواد عشوائية أكبر، استدعِها بشكل متكرر.
مكافئات Node.js
const { randomBytes, randomFillSync, webcrypto } = require('crypto');
const keyBytes = randomBytes(32); // تُعيد Buffer
// أو متوافق مع Web Crypto
const nonce = webcrypto.getRandomValues(new Uint8Array(12));
تسحب randomBytes في Node من CSPRNG الأساسي ذاته الذي تستخدمه Web Crypto. استخدم أيّ أسلوب API يناسب كودك. للكود المشترك الذي يعمل في البيئتين، تطابق webcrypto.getRandomValues المتصفح تماماً.
لماذا تفشل Math.random
تُطبّق كلٌّ من V8 (Chrome/Node) وSpiderMonkey (Firefox) وJavaScriptCore (Safari) دالةَ Math.random() بوصفها PRNG سريعاً دون أي ضمانات تشفيرية. يستخدم V8 متغيراً من xorshift128+. وقد أثبت الباحثون أنه بعد رصد خمسة مخرجات تقريباً، يستطيع المهاجم استرداد الحالة الداخلية والتنبؤ بجميع المخرجات المستقبلية. عام 2015، نجح مايك باوند وزملاؤه في عكس حالة Math.random في V8 خلال برامج مكافأة الثغرات الواقعية.
إذا استخدمت Math.random() لتوليد رموز الجلسات، أو روابط إعادة تعيين كلمة المرور، أو nonces التشفير، أو معرّفات المشاركة، فإن المهاجمين الذين يرصدون بعضها يستطيعون التنبؤ بالبقية. هذا ليس نظرياً — بل هو صنف شائع من الأخطاء في عمليات المراجعة.
الاستخدامات الخاطئة الشائعة
استخدام Math.random() كبذرة لمكتبة PRNG:
// خطأ
const seed = Math.floor(Math.random() * 2**32);
لا يمكن لأي شيء لاحق أن يكون أكثر عشوائية من البذرة. استخدم crypto.getRandomValues(new Uint32Array(1))[0] بدلاً من ذلك.
استخدام Date.now() كإنتروبيا: الوقت قابل للتخمين ضمن نوافذ ضيقة. حتى مع إضافة عامل عشوائي ضئيل، تُسرّب الطوابع الزمنية بتّات كافية للمهاجمين.
لفّ المصادر يدوياً عبر XOR: لا تفعل ذلك. تخلط CSPRNGs لأنظمة التشغيل بالفعل كل مصادر الإنتروبيا المفيدة. إضافة لفّ خاص بك تُقلّل الإنتروبيا عادةً بدلاً من زيادتها.
تحيّز المودولو عند توليد نطاقات: randomBytes[0] % 10 لا تتوزع بشكل منتظم على 0-9 لأن 256 ليست مضاعفاً لـ 10. للحصول على أعداد صحيحة عشوائية في نطاق، استخدم أسلوب الرفض:
function randomInt(max) {
const range = new Uint32Array(1);
const threshold = 2**32 - (2**32 % max);
do {
crypto.getRandomValues(range);
} while (range[0] >= threshold);
return range[0] % max;
}
مصادر الإنتروبيا ومخاوف وقت التشغيل
على Linux، /dev/urandom آمن دائماً بعد بدء التشغيل المبكر. خلال الثواني الأولى على الأنظمة التي تفتقر إلى مولّد أرقام عشوائية للأجهزة، قد يكون مجمع النواة ناقص التلقيم. استُغلّ هذا في خلل Debian OpenSSL عام 2008، حين أزالت تعديلات مبرمج لفّة الإنتروبيا وأبقت على معرّف العملية فقط كبذرة. المفاتيح المُولَّدة في تلك النافذة لم تكن لها سوى 2^15 قيمة ممكنة — تُعدّد في ثوانٍ.
تُلقِّم الأنظمة الحديثة CSPRNG للنواة من: RDSEED على x86-64 (عند توفّره)، وتعليمات RNG لـ ARMv8.5-A، والضجيج الحراري من أجهزة طرفية متنوعة، وتوقيت المقاطعات، ولوحة المفاتيح/الماوس عند التفاعل. على الخوادم التي تستخدم أجهزة كـ Intel Ice Lake أو AMD Zen 3+، يُلقَّم CSPRNG في غضون ميكروثوانٍ من بدء التشغيل.
بالنسبة لحاويات Docker: يُمرَّر /dev/urandom الخاص بالمضيف افتراضياً — لا يلزم اتخاذ أي إجراء. أما البيئات اللاخادمية (AWS Lambda، Cloudflare Workers)، فتتولى بيئة التشغيل تلقيم الإنتروبيا لكل استدعاء.
رموز الجلسات ومعرّفات المشاركة
في خدمة نقل الملفات، تُولِّد معرّفات عشوائية لـ:
- معرّفات الملفات في URLs (يجب ألا يستطيع المهاجمون تخمين معرّفات صالحة)
- رموز المشاركة للروابط المحمية بكلمة مرور
- رموز CSRF
- مفاتيح التشفير (مفاتيح AES لكل ملف)
- nonces لوضع GCM
الحد الأدنى للطول: 128 بتاً (16 بايت) لمقاومة التصادم وعدم القابلية للتخمين، و256 بتاً (32 بايت) للمفاتيح. ترميز base64url يضيف حوالي 33% من الطول؛ أما hex فيضيف 100%.
رمز base64url بحجم 32 بايت يبلغ 43 حرفاً وهو خالٍ من التصادمات عملياً عند 2^256.
اختبار المولّدات الضعيفة
علامات على أن مولّد الأرقام العشوائية لديك معطوب أو ضعيف:
- رموز متطابقة تُولَّد بطلبات مختلفة (تصادم في مساحة يجب أن تكون ضخمة)
- المخرجات تجتاز الاختبارات البصرية لكنها ترسب بطاريات
dieharderأوPractRandالإحصائية - إعادة استخدام البذرة بعد إعادة تشغيل العملية — كل نشر يستخدم الحالة الأولية ذاتها
- المفاتيح المُولَّدة تقع في أنماط (مثلاً: أول 4 بايتات تتغيير لكن آخر 28 متطابقة)
في بيئة الإنتاج، لن ترى هذه المؤشرات إلا إذا حدث خطأ كارثي. نمط الفشل عادةً صامت: تصبح الهجمات ممكنة عملياً على ما يُفترض أن يكون فضاءً من 2^256 احتمالاً.
مراجعة: كل استدعاء لـ Math.random() في قاعدة الكود يجب مراجعته. grep لـ Math.random على شجرة المصدر هو فحص نظافة أسبوعي جيد. تحويل أي موقع استدعاء يتعلق بالأمان إلى crypto.getRandomValues يستغرق دقائق ويمنع ثغرات حقيقية.
السلاسل العشوائية والـ UUIDs
للمعرّفات التي تظهر للمستخدم، تُعيد crypto.randomUUID() UUID من الإصدار 4 (122 بتاً من العشوائية) بتنسيق قياسي:
const id = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"
مدعوم في Chrome 92+، وFirefox 95+، وSafari 15.4+، وNode 14.17+. مناسب للمفاتيح الأساسية في قواعد البيانات، ومعرّفات طلبات API، والمعرّفات الفريدة غير الحرجة أمنياً. استخدم getRandomValues صراحةً لأي شيء يتطلب تنسيقات مخصصة أو إنتروبيا أعلى.
على الخوادم: تجنّب مجمعات RNG المخصصة
تُقدّم بعض أطر الخوادم مجمعات عشوائية خاصة بها تدّعي "لفّ" CSPRNG الخاص بالنظام مع إنتروبيا على مستوى التطبيق. تعامل مع هذا بتشكيك. نادراً ما يُحسّن اللفّ المخصص مخرجات النواة، وقد يُقلّل الإنتروبيا بصمت إذا كان مُعطِّباً.
إذا كنت على Node أو بيئة تشغيل رئيسية، فإن crypto.randomBytes المدمج صحيح وسريع. لا تستبدله بخلاطات من جهات خارجية.
الخلاصة
كل مكوّن تشفيري في تطبيق نقل ملفات يعتمد على بايتات عشوائية غير قابلة للتنبؤ. مفاتيح AES، و nonces لوضع GCM، وأملاح PBKDF2، ورموز المشاركة، ورموز CSRF، ومعرّفات الجلسات — كلها تحتاج إلى الأداة ذاتها: crypto.getRandomValues() في المتصفحات، وcrypto.randomBytes() أو webcrypto.getRandomValues في Node. استخدم هذه. لا تستخدم Math.random() أبداً. ولا الطوابع الزمنية. ولا خلاطك الخاص.
يشتق HexaTransfer كل مفتاح AES لكل ملف، و nonce، ومعرّف URL من crypto.getRandomValues() على جانب العميل. معرّفات المشاركة من جانب الخادم تأتي من crypto.randomBytes. واجهة برمجية واحدة، سلوك متسق، ولا طريقة لتسريب بتّات قابلة للتنبؤ عرضاً إلى النظام.
الأداة مملّة تحديداً لأنها تحتاج أن تكون كذلك. مملّة وصحيحة ومتاحة في كل مكان — هذا بالضبط ما يجب أن تكون عليه أسس التشفير.
جرّبها على hexatransfer.com — مجاني، بدون حساب، حتى 10 جيجابايت.
أرسل ملفات كبيرة بأمان مع تشفير من طرف إلى طرف
انقل ملفات حتى 10 جيجابايت مجاناً مع تشفير من طرف إلى طرف. لا حاجة لحساب. يتم تشفير ملفاتك في متصفحك قبل الرفع — لا أحد آخر يستطيع قراءتها.
إرسال ملف