تشفير الملفات في JavaScript: دليل خطوة بخطوة
شفّر الملفات في المتصفح باستخدام JavaScript. دليل عملي يغطي تشفير AES واشتقاق المفاتيح والتعامل الآمن مع الملفات.
JavaScript يستطيع تشفير ملف كلياً في المتصفح باستخدام Web Crypto API، دون تدخل الخادم. خط الأنابيب المعياري: اقرأ الملف كـ ArrayBuffer، اشتق مفتاح AES-GCM بـ 256 بت من كلمة مرور عبر PBKDF2-SHA256 (600,000 تكرار)، شفِّر بـ IV عشوائي بـ 12 بايت، وعبِّئ الملح والـ IV والنص المشفَّر في Blob قابل للتنزيل. كل متصفح رئيسي يدعم هذا أصلياً عبر window.crypto.subtle، وللملفات حتى 2-3 جيجابايت تعمل العملية كلها في أقل من عشر ثوانٍ على حاسوب حديث بدون أي مكتبة طرف ثالث.
اختيارات الخوارزمية المهمة
اختَر AES-GCM لا AES-CBC. GCM يُعطيك التشفير المُصادَق في تمريرة واحدة، يكشف العبث بوسم بـ 128 بت، بينما CBC يحتاج خطوة HMAC منفصلة يُخطئ معظم الأدلة فيها. استخدم مفتاحاً بـ 256 بت — فجوة الأداء مقابل 128 بت ضئيلة على أجهزة تدعم AES-NI. اختَر PBKDF2-SHA256 لاشتقاق المفتاح القائم على كلمة المرور ما لم تستطع تضمين Argon2id عبر WebAssembly، وهو أقوى لكن يُضيف 50 KB لحجم التنزيل.
تجنَّب: وضع ECB (معطوب أساسياً)، والحشو اليدوي (عقد كامل من هجمات CBC padding oracle)، وMD5 أو SHA-1 (متصادمان)، وأي شيء من حزمة crypto-js بدون فهم أنها تستخدم CBC مع PKCS7 افتراضياً.
قراءة ملف إلى الذاكرة
واجهة File تُعطيك ثلاث طرق للحصول على البايتات:
const buf = await file.arrayBuffer(); // الملف بالكامل
const stream = file.stream(); // تدفق
const text = await file.text(); // ترميز UTF-8
للملفات فوق 500 ميجابايت، arrayBuffer() كثيراً ما يفشل على Safari للجوال. استخدم التدفق بدلاً من ذلك:
async function* chunks(file, size = 4 * 1024 * 1024) {
for (let off = 0; off < file.size; off += size) {
yield new Uint8Array(await file.slice(off, off + size).arrayBuffer());
}
}
كل شريحة تقرأ بشكل كسول من القرص، لذا تبقى ذروة الذاكرة محدودة.
اشتقاق مفتاح من كلمة مرور المستخدم
لا تمرِّر كلمة مرور خام إلى encrypt. اشتق مفتاحاً أولاً:
async function deriveKey(password, salt) {
const enc = new TextEncoder();
const material = await crypto.subtle.importKey(
'raw', enc.encode(password), { name: 'PBKDF2' }, false, ['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' },
material,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
أنشئ ملحاً جديداً بـ 16 بايت لكل ملف بـ crypto.getRandomValues(new Uint8Array(16)). خزِّن الملح جنباً إلى جنب مع النص المشفَّر — إعادة استخدام ملح عبر الملفات تُبطِل الغرض من PBKDF2. ورقة OWASP لتخزين كلمات المرور (ديسمبر 2026) توصي بـ 600,000 تكرار لـ PBKDF2-SHA256، ما يُترجَم إلى نحو 500 مللي ثانية لاشتقاق المفتاح على هاتف متوسط.
تشفير الملف
بمفتاح في اليد، التشفير هو استدعاء subtle.encrypt واحد لكل مخزن مؤقت:
async function encryptBuffer(key, plaintext) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
key,
plaintext
);
return { iv, ciphertext };
}
GCM يفشل بشكل كارثي إذا أعدت استخدام زوج (iv, key) — تصادم تدفق المفتاح يُسرِّب كلا النصين الواضحين. IV عشوائي بـ 96 بت يُعطي نحو 2^48 تشفيراً آمناً بمفتاح واحد، كافٍ لتشفير الملفات. إذا شفَّرت أجزاء كثيرة بنفس المفتاح، اشتق الـ IV من عداد بالإضافة إلى بادئة عشوائية بـ 32 بت.
تعبئة المخرجات
جهاز فك التشفير يحتاج الملح والـ IV والنص المشفَّر. عبِّئها في blob واحد بترويسة صغيرة:
function pack(salt, iv, ciphertext) {
const magic = new TextEncoder().encode('ENC1');
return new Blob([magic, salt, iv, new Uint8Array(ciphertext)]);
}
أصدِر الترويسة (ENC1، ENC2...) حتى تستطيع ترحيل الخوارزميات لاحقاً دون كسر الملفات القديمة. قدِّم التنزيل عبر:
const url = URL.createObjectURL(packed);
const a = document.createElement('a');
a.href = url; a.download = `${file.name}.enc`; a.click();
URL.revokeObjectURL(url);
فك تشفير الملفات
فك التشفير يعكس العملية ويُلقي OperationError إذا كانت كلمة المرور خاطئة أو الملف مُعبَّث به:
async function decryptFile(blob, password) {
const buf = await blob.arrayBuffer();
const view = new Uint8Array(buf);
const magic = new TextDecoder().decode(view.slice(0, 4));
if (magic !== 'ENC1') throw new Error('Unknown format');
const salt = view.slice(4, 20);
const iv = view.slice(20, 32);
const ct = view.slice(32);
const key = await deriveKey(password, salt);
const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
return new Blob([pt]);
}
أظهِر رسالة خطأ واحدة — "تعذَّر فك التشفير، كلمة مرور خاطئة أو ملف تالف" — بدلاً من التمييز بين فشل الوسم والفشل الهيكلي. هذا يُزيل التسريب المعلوماتي المشابه لـ padding oracle للمهاجمين.
تشفير الملفات الكبيرة بدون نفاد الذاكرة
لأي شيء فوق 500 ميجابايت، لا تستدعِ arrayBuffer() على الملف كله. شفِّر الأجزاء بشكل مستقل بـ IVs فريدة مشتقة من عداد:
function ivForChunk(baseIV, index) {
const iv = new Uint8Array(baseIV);
const view = new DataView(iv.buffer);
view.setUint32(8, index, false);
return iv;
}
مرِّر الملف عبر TransformStream، شفِّر كل جزء 4 ميجابايت، واكتب النتائج إلى WritableStream يُشير إلى القرص عبر File System Access API. ذروة الذاكرة تبقى قرب 10 ميجابايت حتى لملف 20 جيجابايت. الصيغة المجزأة تحتاج تسجيل حجم الجزء وعدده في ترويستها حتى يعرف جهاز فك التشفير كيف يُعيد التجميع.
أخطاء شائعة تصل للإنتاج
ثلاثة أنماط تظهر في مراجعات الكود الفعلية لـ JavaScript crypto:
أولاً، تخزين المفتاح الخام في localStorage للراحة. localStorage متزامن ومحدود بالأصل ويمكن لأي XSS قراءته. استخدم CryptoKey غير قابل للاستخراج في IndexedDB بدلاً من ذلك.
ثانياً، استخدام Math.random() للـ IVs أو الملوح. Math.random() متوقع؛ دائماً استخدم crypto.getRandomValues.
ثالثاً، افتراض أن subtle.encrypt ثابتة الوقت. هي كذلك في تطبيقات المتصفح الأصلية، لكن أي غلاف JavaScript حولها في الغالب ليس كذلك. أبقِ كودك الخاص بعيداً عن المسار الحار.
HexaTransfer يُطبِّق هذا الخط الدقيق بالضبط — PBKDF2 بـ 600k تكرار، وAES-256-GCM، وأجزاء متدفقة — لذا يخزِّن الخادم نصاً مشفَّراً معتماً فحسب.
اختبار التنفيذ
اكتب إطار اختبار يمرِّر blob عشوائية بـ 10 ميجابايت ذهاباً وإياباً، يُحوِّل بايتاً، ويتحقق من رمي فك التشفير لخطأ. أضِف اختبار الضبابية ضد ترويسات مشوهة ونصوص مشفَّرة مُقصَّصة — الخطأ الشائع هو فحوص حدود مفقودة على slice() تُعطِّل بدلاً من الرفض. قِس على iPhone SE وAndroid متوسط وChromebook؛ أي شيء يستغرق أكثر من ثانيتين لاشتقاق المفتاح بطيء جداً لمستخدمي الجوال. راجع بعين ثانية قبل الإنتاج — كود التشفير يبدو بسيطاً ويكسر بطرق خفية تُفوِّتها الاختبارات.
جرّبها على hexatransfer.com — مجاناً، بدون حساب، حتى 10 جيجابايت.
أرسل ملفات كبيرة بأمان مع تشفير من طرف إلى طرف
انقل ملفات حتى 10 جيجابايت مجاناً مع تشفير من طرف إلى طرف. لا حاجة لحساب. يتم تشفير ملفاتك في متصفحك قبل الرفع — لا أحد آخر يستطيع قراءتها.
إرسال ملف