Web Crypto API: دليل كامل لتشفير الملفات
أتقن Web Crypto API لتشفير الملفات عبر المتصفح. AES-GCM و RSA-OAEP وإدارة المفاتيح في تطبيقات JavaScript.
Web Crypto API يُتيح لك تشفير الملفات مباشرةً في المتصفح باستخدام طرق window.crypto.subtle الأصلية، دون مكتبات خارجية. لتشفير الملفات، عادةً تشتق مفتاحاً من كلمة مرور عبر PBKDF2 (210,000 تكرار، SHA-256)، ثم تُشفِّر بايتات الملف بـ AES-GCM مع IV بـ 96 بت ووسم مصادقة بـ 128 بت. سير العمل بالمفتاح العام يستخدم RSA-OAEP بمفاتيح 4096 بت لتغليف المفتاح المتماثل. الواجهة متاحة عبر HTTPS في كل متصفح حديث وتعمل داخل خلفية التشفير الأصلية لا في JavaScript.
لماذا SubtleCrypto أفضل من مكتبات JS الخالصة
window.crypto.subtle تستدعي خلفية التشفير الأصلية المدققة للمتصفح، عادةً BoringSSL في Chromium أو CommonCrypto على Safari. مقارنةً بخيارات JS الخالصة مثل CryptoJS أو sjcl، يعمل SubtleCrypto أسرع بـ 30-80 مرة لـ AES-GCM، ويتجنب قنوات التوقيت الجانبية في مفسِّرات JavaScript، ولا يُرسِل بايتاً للمستخدمين. المقايضة هي واجهة قائمة على Promise تعمل فقط على كائنات ArrayBuffer وCryptoKey، لذا تقضي وقتاً طويلاً في التنقل بين Uint8Array وBlob وReadableStream. للملفات فوق 100 ميجابايت، هذا السباكة أهم من سرعة التشفير الخام.
اشتقاق مفتاح من كلمة مرور بـ PBKDF2
لا تستخدم كلمة مرور مباشرةً كمفتاح AES. بدلاً من ذلك، استورد كلمة المرور كمادة خام، ثم اشتق مفتاحاً بـ 256 بت:
async function deriveKey(password, salt) {
const enc = new TextEncoder();
const material = await crypto.subtle.importKey(
'raw', enc.encode(password), 'PBKDF2', false, ['deriveKey']
);
return crypto.subtle.deriveKey(
{ name: 'PBKDF2', salt, iterations: 210000, hash: 'SHA-256' },
material,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
توجيهات OWASP لعام 2026 تشترط 600,000 تكرار على الأقل مع SHA-256، رغم أن 210,000 لا يزال مقبولاً للسياقات منخفضة المخاطر. أنشئ ملحاً (salt) جديداً بـ 16 بايت لكل ملف بـ crypto.getRandomValues وخزِّنه جنباً إلى جنب مع النص المشفَّر. Argon2id أقوى لكنه غير متاح بعد عبر SubtleCrypto.
تشفير ملف بـ AES-GCM
AES-GCM يمنحك السرية والمصادقة في تمريرة واحدة. القاعدة الحرجة هي عدم إعادة استخدام زوج (key, IV) أبداً:
async function encryptFile(file, key) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const plaintext = await file.arrayBuffer();
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv, tagLength: 128 },
key,
plaintext
);
return { iv, ciphertext };
}
لملف بحجم 2 جيجابايت، file.arrayBuffer() ستُخصِّص المخزن المؤقت بالكامل مما يُعطِّل Safari على الجوال كثيراً. قسِّم الملف إلى أجزاء 4 ميجابايت، شفِّر كل جزء بـ IV فريد مشتق من عداد مُلحَق ببادئة عشوائية، وأضِف بايت نسخة وملحاً في البداية حتى يعرف جهاز فك التشفير ما يتعامل معه.
تدفق الملفات الكبيرة عبر TransformStream
لتجنب انفجار الذاكرة، لفِّ التشفير في TransformStream ومرِّر الملف عبره:
const chunkSize = 4 * 1024 * 1024;
const encryptor = new TransformStream({
async transform(chunk, controller) {
const iv = nextIV(counter++);
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, chunk);
controller.enqueue(new Uint8Array([...iv, ...new Uint8Array(ct)]));
}
});
await file.stream()
.pipeThrough(sliceByChunks(chunkSize))
.pipeThrough(encryptor)
.pipeTo(uploadSink);
file.stream() تُعيد ReadableStream<Uint8Array> يقرأ من القرص بشكل كسول. المقسِّم يُنتِج أجزاء بحجم ثابت حتى تتوافق وسوم GCM بشكل متوقع. ذروة الذاكرة تبقى أقل من 20 ميجابايت حتى لرفع 10 جيجابايت.
تغليف المفتاح المتماثل بـ RSA-OAEP
حين تحتاج مشاركة ملف مع مستلم محدد، أنشئ زوج مفاتيح RSA مرة واحدة وانشر المفتاح العام:
const keypair = await crypto.subtle.generateKey(
{ name: 'RSA-OAEP', modulusLength: 4096,
publicExponent: new Uint8Array([1,0,1]), hash: 'SHA-256' },
true, ['wrapKey', 'unwrapKey']
);
أنشئ مفتاح AES-GCM للملف، ثم غلِّفه:
const wrapped = await crypto.subtle.wrapKey(
'raw', fileKey, keypair.publicKey,
{ name: 'RSA-OAEP' }
);
مفاتيح RSA بـ 4096 بت تُعطي أمناً يعادل 150 بت حتى 2030 وفق NIST SP 800-57. إذا احتجت السرية المستقبلية أو مقاومة الحوسبة الكمومية، اقترن RSA-OAEP بـ ECDH على P-384 أو انتقل إلى ML-KEM (Kyber) حين تُنزِّله مجموعة عمل WebCrypto.
تخزين المفاتيح بأمان في IndexedDB
كائنات CryptoKey غير قابلة للاستخراج افتراضياً، مما يعني إمكانية استمرارها في IndexedDB دون كشف البايتات الخام لـ JavaScript:
const db = await openDB('keystore', 1);
await db.put('keys', keypair.privateKey, 'user-signing-key');
المتصفحات تُسلسِل المفتاح باستخدام خوارزمية الاستنساخ المنظَّم وتحتفظ بالبايتات الفعلية في خلفية التشفير. سكريبت مُخترَق يستطيع استدعاء encrypt أو decrypt بالمفتاح المخزَّن لكن لا يستطيع قراءة مادته. هذا تحصين ذو معنى مقارنةً بحشو المفاتيح بـ base64 في localStorage.
معالجة الأخطاء التي تُلقيها الواجهة
SubtleCrypto تُلقي OperationError لفشل فك التشفير بالمصادقة، مما يعني عادةً العبث بالنص المشفَّر أو خطأ في IV أو كتابة المستخدم لكلمة مرور خاطئة. تُلقي DataError حين يكون للمخزن المؤقت للإدخال طول خاطئ، وNotSupportedError حين الخوارزمية غير مُنفَّذة، وInvalidAccessError حين لم يُستورَد المفتاح بأعلام الاستخدام الصحيحة. دائماً لفَّ فك التشفير في try/catch، أظهِر رسالة محايدة "تعذَّر فك تشفير الملف"، وتجنَّب تسريب ما إذا كان الوسم أو البنية قد فشلت.
مشاكل واقعية شائعة
Firefox على Android يحدُّ تكرارات deriveKey عند حوالي مليون قبل تجمُّد خيط UI لعدة ثوانٍ، لذا شغِّل اشتقاق المفتاح داخل Worker مخصص. Safari أقل من 16.4 لا يدعم crypto.subtle.verify مع حشو PSS. Chrome يُقيِّد استدعاءات getRandomValues فوق 64 KB لكل استدعاء، لذا أجرِ حلقة إذا احتجت المزيد من العشوائية. ونقل ArrayBuffer عبر postMessage هو نسخ بصفر (zero-copy) لكن يفصل الأصل، مما يُفاجئ الناس.
HexaTransfer يستخدم هذا الخط الدقيق من AES-GCM مع PBKDF2 لكل رفع، مع اشتقاق المفاتيح في Worker والنص المشفَّر يتدفق للتخزين دون أن يرى الخادم النص الواضح.
تجميع كل شيء معاً
سير رفع مشفَّر بسيط: أنشئ ملحاً وIV بـ getRandomValues، اشتق مفتاح AES-GCM من كلمة مرور المستخدم عبر PBKDF2، دفِّق الملف عبر TransformStream يُشفِّر كل جزء 4 ميجابايت، أضِف ترويسة صغيرة تحتوي النسخة والملح وعدد الأجزاء، وأرسِل النتيجة إلى خادمك. عند التنزيل، عكِس العملية جزءاً جزءاً، ملتقطاً OperationError كإشارة لكلمة مرور خاطئة أو تلف. Web Crypto API يمنحك كل ما تحتاج، والتنفيذ الأصلي للمتصفح سيتفوق على أي بديل JavaScript بمقدار رتبة.
جرّبها على hexatransfer.com — مجاناً، بدون حساب، حتى 10 جيجابايت.
أرسل ملفات كبيرة بأمان مع تشفير من طرف إلى طرف
انقل ملفات حتى 10 جيجابايت مجاناً مع تشفير من طرف إلى طرف. لا حاجة لحساب. يتم تشفير ملفاتك في متصفحك قبل الرفع — لا أحد آخر يستطيع قراءتها.
إرسال ملف