انتقل إلى المحتوى
HexaTransfer
العودة إلى المدونة
التشفير والأمان

دليل تنفيذ AES-GCM: التشفير المصدّق بشكل صحيح

نفذ تشفير AES-GCM بشكل صحيح. إدارة النونس والمفاتيح والأخطاء الشائعة في التشفير المصدّق.

AES-GCM (وضع غالوا/العداد) يجمع تشفير AES-CTR مع مصادقة GHASH لإنتاج تشفير مصادَق مع بيانات مرتبطة (AEAD). التنفيذ الصحيح يستخدم مفتاحاً بـ 256 بت، ومُنبَّهاً بـ 96 بت (12 بايت) فريداً لكل مفتاح (لا يُعاد استخدامه قط)، وبطاقة مصادقة بـ 128 بت، وبيانات مرتبطة اختيارية (AAD) تُصادَق لكن لا تُشفَّر. NIST SP 800-38D يحدد البنية الدقيقة. أخطئ في أي من هذا، خاصةً إعادة استخدام المُنبَّه، وينهار أمان GCM: زوج واحد متكرر من (مفتاح، مُنبَّه) يُمكّن المهاجمين من استرداد مفتاح المصادقة وتزوير نصوص مشفَّرة اعتباطية. يغطي هذا الدليل الطريقة الصحيحة لاستخدام AES-GCM في سياقات المتصفح وNode والخادم.

ما تضمنه GCM فعلاً

خاصيتان:

السرية: لا يمكن استرداد النص الصريح بدون المفتاح. طبقة تشفير AES-CTR في GCM توفر هذا.

السلامة والأصالة: أي تعديل على النص المشفَّر أو المُنبَّه أو البيانات المرتبطة يجعل فكّ التشفير يفشل. GHASH ينتج بطاقة بـ 128 بت تُتحقق منها ثابتة الوقت عند فكّ التشفير.

ما لا تضمنه GCM: عدم الإنكار (متماثلة، أي من يملك المفتاح يستطيع إنتاج نصوص مشفَّرة صالحة)، وحماية الإعادة (هذا اهتمام طبقة أعلى)، والترتيب (للتدفقات تحتاج سلسلة بطريقة ما).

الرؤية المحورية: GCM يظل آمناً فقط حين تكون المُنبَّهات فريدة لكل مفتاح. ليس في معظم الأحيان، ليس عادةً، فريدة فعلاً. البرهان الأمني ينهار عند إعادة الاستخدام.

إدارة المُنبَّه: الأمر الأهم من كل شيء

يمكن توليد مُنبَّه بـ 96 بت بطريقتين:

عشوائي: crypto.getRandomValues(new Uint8Array(12)). مع مُنبَّهات عشوائية بـ 96 بت تحت مفتاح واحد، تظهر تصادمات حد عيد الميلاد حول 2^48 تشفيراً. NIST يقترح هامش أمان، فاحدد الاستخدام بـ 2^32 لكل مفتاح.

عداد: زيادة عدد صحيح بـ 96 بت. يضمن التفرد حتى 2^96 رسالة. يحتاج حالة رتيبة موثوقة، صعبة في الأنظمة الموزَّعة.

لنقل الملفات بمفتاح جديد لكل ملف، المُنبَّهات العشوائية آمنة تماماً — لن تصل إلى 2^32 تشفيراً بمفتاح واحد. للتشفير المجزَّأ تحت مفتاح ملف واحد، استخدم عداداً يُضمَّن فهرس الجزء في المُنبَّه:

const nonce = new Uint8Array(12);
new DataView(nonce.buffer).setUint32(0, messageId);
new DataView(nonce.buffer).setBigUint64(4, BigInt(chunkIndex));

الحالة الكارثية: عمليات متعددة تشفّر تحت المفتاح المشترك ذاته بمُنبَّهات عشوائية، موسَّعة إلى ملايين التشفيرات في الثانية. تصادمات عيد الميلاد تصبح محتملة. إذا كان عليك مشاركة المفاتيح عبر العمليات، استخدم عداداً منسَّقاً مع بادئة معرِّف العملية.

لا تستخدم مُنبَّهات بـ 64 بت

AES-GCM يدعم أطوال مُنبَّهات متغيرة، لكن فقط مُنبَّهات 96 بت تستخدم البنية المُحسَّنة المحددة في NIST 800-38D. الأطوال الأخرى (عادةً 64 أو 128 بت) تُثير خطوة معالجة مسبقة GHASH تقلّل الأداء وتزيد التعقيد. Web Crypto API يقبل IVs غير 96-بت لكن المواصفة توصي بـ 96. فقط استخدم 96.

طول البطاقة: لا تقصّرها

بطاقة GCM حتى 128 بت. بعض المواصفات تسمح بالاقتصار على 96 أو 64 أو حتى 32 بت. لا تفعل. البطاقات المقصورة تُيسّر هجمات التزوير، والتوفير (4-12 بايت لكل رسالة) لا أهمية له لنقل الملفات. AES-GCM في Web Crypto يتخذ افتراضي 128 بت عبر معامل tagLength (افتراضي 128). اتركه كما هو.

البيانات المرتبطة (AAD)

AAD هي بيانات تُصادَق لكن لا تُشفَّر. استخدمها للبيانات الوصفية التي تريد ربطها بالنص المشفَّر: اسم الملف ونوع المحتوى وتوقيت انتهاء الصلاحية ومعرِّف الرافع.

await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv: nonce,
    additionalData: new TextEncoder().encode(JSON.stringify({
      filename: "report.pdf",
      contentType: "application/pdf",
      expires: 1712345678,
    })),
  },
  key,
  plaintext
);

إذا عدَّل مهاجم AAD، يفشل فكّ التشفير. هذا يمنع هجمات المقايضة حيث يستبدل شخص ما اسم الملف على نص مشفَّر محفوظ دون كشف. يجب أن يعرف المستقبِل AAD الدقيق ليُفكّ التشفير، فخزّنه مع النص المشفَّر.

توليد المفاتيح واشتقاقها

للمفاتيح لكل ملف:

const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

256 بت هو الافتراضي عام 2026. AES بـ 128 بت لا يزال آمناً لكن له هامش ما بعد كم أقل (خوارزمية Grover تُنصّف القوة الفعلية).

للمفاتيح المشتقة من كلمة المرور:

const aesKey = await crypto.subtle.deriveKey(
  {
    name: "PBKDF2",
    salt: crypto.getRandomValues(new Uint8Array(16)),
    iterations: 600000,
    hash: "SHA-256",
  },
  passwordKey,
  { name: "AES-GCM", length: 256 },
  false,
  ["encrypt", "decrypt"]
);

خزّن الملح مع النص المشفَّر. ليس سراً؛ يجب أن يكون فريداً لكل كلمة مرور.

مسار الكود الحرج

دالة تشفير دنيا:

async function encrypt(key, plaintext, aad = new Uint8Array()) {
  const nonce = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = new Uint8Array(
    await crypto.subtle.encrypt(
      { name: "AES-GCM", iv: nonce, additionalData: aad },
      key,
      plaintext
    )
  );
  return { nonce, ciphertext, aad };
}

فكّ التشفير مع معالجة الأخطاء المناسبة:

async function decrypt(key, { nonce, ciphertext, aad }) {
  try {
    return await crypto.subtle.decrypt(
      { name: "AES-GCM", iv: nonce, additionalData: aad },
      key,
      ciphertext
    );
  } catch (e) {
    // فشل المصادقة
    throw new Error("فشل فكّ التشفير: نص مشفَّر متلاعَب به أو مفتاح خاطئ");
  }
}

استدعاء decrypt يرمي OperationError عند عدم تطابق البطاقة أو نص مشفَّر قصير أو مفتاح خاطئ. عامِل أي استثناء كفشل سلامة؛ لا تحاول التمييز.

الملفات الكبيرة المجزَّأة

للملفات فوق بضع مئات من الميجابايتات، جزّئها لتجنب ضغط الذاكرة:

async function encryptChunks(key, file, chunkSize = 1024 * 1024) {
  const chunks = [];
  let chunkIndex = 0;
  for (let offset = 0; offset < file.size; offset += chunkSize) {
    const chunk = await file.slice(offset, offset + chunkSize).arrayBuffer();
    const nonce = new Uint8Array(12);
    new DataView(nonce.buffer).setBigUint64(4, BigInt(chunkIndex++));
    const ct = await crypto.subtle.encrypt(
      { name: "AES-GCM", iv: nonce }, key, chunk
    );
    chunks.push(new Uint8Array(ct));
  }
  return chunks;
}

تحذير: AES-GCM المجزَّأ لا يكتشف الاقتطاع. يستطيع مهاجم حذف أجزاء لاحقة وكل جزء باقٍ يُفكَّك تشفيره بصورة صحيحة. للدفاع، ضمّن العدد الكلي للأجزاء في AAD لكل جزء، أو استخدم crypto_secretstream من libsodium الذي يعالج هذا.

فكّ التشفير من جانب الخادم (Node.js)

وحدة crypto في Node تستطيع فكّ تشفير البيانات المشفَّرة في المتصفح:

const { createDecipheriv } = require('crypto');

function decrypt(key, nonce, ciphertextWithTag) {
  const tag = ciphertextWithTag.slice(-16);
  const ct = ciphertextWithTag.slice(0, -16);
  const decipher = createDecipheriv('aes-256-gcm', key, nonce);
  decipher.setAuthTag(tag);
  return Buffer.concat([decipher.update(ct), decipher.final()]);
}

Web Crypto يُلحق بطاقة 128 بت بالنص المشفَّر؛ API في Node يتوقع البطاقة والنص المشفَّر منفصلَين. افصلهما وفق ذلك.

أرقام الأداء

على العتاد النموذجي 2024-2026 مع AES-NI:

  • أصلي (OpenSSL مع AES-NI): 3-5 جيجابايت/ثانية لكل نواة.
  • Web Crypto (متصفح مع تسريع الأجهزة): 1-2 جيجابايت/ثانية.
  • libsodium.js WASM AES-GCM: 400-800 ميجابايت/ثانية.
  • JavaScript خالص (@noble/ciphers): 50-150 ميجابايت/ثانية.

لملف بـ 1 جيجابايت، تشفير Web Crypto يعمل في 0.5-1 ثانية. JavaScript الخالص يأخذ 7-20 ثانية. اختر التنفيذات بناءً على هذا الواقع؛ لتجربة المستخدم في نقل الملفات الكبيرة، Web Crypto هو الخيار العملي.

ملخص المزالق الشائعة

  • إعادة استخدام المُنبَّه: كارثي. أكبر وضع فشل واحد.
  • استخدام Math.random() بدلاً من crypto.getRandomValues().
  • نسيان مصادقة البيانات الوصفية المرتبطة مع AAD.
  • استخدام وضع CBC "لأننا معتادون عليه." CBC يستلزم MAC منفصلاً لمطابقة سلامة GCM؛ بنية HMAC-CBC صحيحة لكن معقدة للتنفيذ الصحيح، GCM تتجنب الفخ.
  • التقاط أخطاء فكّ التشفير بصمت وإعادة هراء. افشل دائماً بصوت عالٍ.
  • بناء تنفيذ GCM الخاص. استخدم Web Crypto أو libsodium أو node:crypto. تنفيذ GHASH يحتوي نقاط ضعف قنوات جانبية استغرق خبراء سنوات لتجاوزها.

HexaTransfer يستخدم Web Crypto's AES-256-GCM مع مُنبَّهات عشوائية بـ 96 بت وبطاقات بـ 128 بت ودون AAD لأن المفتاح مخصص لكل ملف واسم الملف مخزَّن في بيانات وصفية محمية بـ AEAD بصورة منفصلة. بسيط وصحيح وسريع.

متى تختار بديلاً

AES-GCM مثالي لنقل الملفات، لكن فكّر في بدائل في حالات محددة:

  • XChaCha20-Poly1305: مُنبَّهات بـ 192 بت تجعل سلامة المُنبَّه العشوائي مضمونة على أي مقياس. أبطأ قليلاً على الأجهزة ذات AES-NI، أسرع على ARM القديمة بدون AES-NI. libsodium يوفره.
  • AES-GCM-SIV: مقاوم لسوء الاستخدام؛ إعادة استخدام المُنبَّه لا تكشف المفتاح، بل تكشف فقط ما إذا كانت النصوص الصريحة متساوية. مفيد حين لا تستطيع ضمان تفرد المُنبَّه.

لمعظم أعباء عمل نقل الملفات في مكدسة الويب المعيارية، AES-256-GCM بمفتاح جديد لكل ملف ومُنبَّهات عشوائية بـ 96 بت هو الخيار الصحيح والأبسط للتنفيذ الصحيح.

جرّبها على hexatransfer.com — مجاني، بدون حساب، حتى 10 جيجابايت.

أرسل ملفات كبيرة بأمان مع تشفير من طرف إلى طرف

انقل ملفات حتى 10 جيجابايت مجاناً مع تشفير من طرف إلى طرف. لا حاجة لحساب. يتم تشفير ملفاتك في متصفحك قبل الرفع — لا أحد آخر يستطيع قراءتها.

إرسال ملف