مقال تقني

HotXLS Delphi Component: XLSX AES-protected output في Delphi

تشرح النسخة المحدثة ما يكتبه SaveAsEncrypted فعلياً، ولماذا لا تعادل حماية الورقة التشفير، وأن مسار الكتابة فقط يرفع EXlsxEncryptionNotImplemented. كما تفرق بين AES-128 Standard وAES-256، وتوضح أن مسار XLS القديم يعيد RC4 وXOR

يعرض Excel شيئين يُسمّى كلاهما "كلمة مرور"، وواحد منهما فقط تشفير. كلمة مرور الفتح تفتح شفرة حقيقية: بدونها لا يمكن قراءة الملف على الإطلاق. أما كلمتا مرور حماية ورقة العمل والمصنف فلا تفعلان شيئًا من هذا القبيل. إنهما تضبطان علامة يوافق محرر متعاون على احترامها، ومصنف لا يحمل سوى تلك العلامة هو أرشيف مضغوط عادي قابل للقراءة والبيانات فيه بنص صريح. اختر الخاطئة منهما وستُسلِّم كشف رواتب يبدو مقفلًا في Excel ويُقرأ في أي محرر نصوص

الإثبات يستغرق عشر ثوانٍ. أعد تسمية ملف .xlsx محمي إلى .zip، وافتحه في أي أداة أرشفة، وانظر إلى xl/worksheets/sheet1.xml. إذا كانت قيم الخلايا موجودة بنص UTF-8 صريح، فالملف غير مشفّر، أيًا كانت مطالبات كلمة المرور التي يثيرها Excel عندما يحاول أحدهم تحرير خلية. هذه الفجوة تنجو لسنوات داخل فرق تفترض أن حماية الورقة سرّية، وعادة ما تظهر يوم تجري مراجعة أمنية بالضبط عملية إعادة التسمية هذه

مخطط يقابل حماية أوراق XLSX في Delphi التي تخزن بصمة ضعيفة وتترك بيانات الخلايا مقروءة في zip عادي بـ SaveAsEncrypted في HotXLS الذي يشتق مفتاح AES-128 ويكتب حاوية تشفير OLE
تحفظ حماية الورقة هاشاً ضعيفاً وتترك الحزمة zip مقروءاً. ويشتق SaveAsEncrypted مفتاح AES-128 ويكتب وعاء OLE لا تستطيع أي أداة أرشيف سردَه

HotXLS مكتبة جداول بيانات أصلية لـDelphi وC++Builder، وهي تبقي الميزتين على طرفي نقيض من ذلك الخط. حماية ورقة العمل والمصنف قيود تحرير مدعومة بتجزئة موروثة ضعيفة عمدًا. SaveAsEncrypted تنتج حزمة مشفّرة بـAES لا يفتحها شيء أقل من كلمة المرور. الأقسام أدناه تغطي ما يكتبه ذلك الاستدعاء، وعدم التماثل الذي يجب التصميم حوله (HotXLS يكتب ملفات مشفّرة لكنه لا يستطيع قراءتها مجددًا)، وكيف يختلف مسار XLS الأقدم

لماذا حماية الورقة ليست تشفيرًا

طرق Protect على الأوراق وProtectWorkbook على المصنف تخزّن تجزئة من 4 أرقام سداسية عشرية لكلمة المرور. هذه هي الخوارزمية الموروثة التي ورثتها OOXML وBIFF كلاهما من Excel تسعينيات القرن الماضي، وتوثيق الصيغة لا يدّعي أبدًا أنها تفعل أكثر من إيقاف التعديلات العرضية. الحزمة تبقى أرشيفًا مضغوطًا عاديًا قابلًا للقراءة: بيانات الخلايا والصيغ والسلاسل المشتركة كلها بصيغة XML نصية صريحة. الافتراضي يجعل الأمر أسوأ، لا أفضل. كل خلية تبدأ بـLocked=True، فاستدعاء Protect دون فتح أقفال نطاق إدخال أولًا يجمّد الورقة بأكملها ضد التحرير مع ترك كل قيمة في مرأى صريح

لا شيء من هذا يجعل الحماية عديمة الفائدة. توجيه المستخدمين نحو نطاقات قابلة للتحرير وتثبيت تخطيط للطباعة مهام حقيقية، مغطاة في مقالتنا عن حماية ورقة العمل وإعداد الصفحة. لكن تلك مهام قابلية استخدام. في اللحظة التي يكون فيها المطلوب سرّية، فإن الواجهة الوحيدة التي تجيب عليه هي SaveAsEncrypted

ما الذي تكتبه SaveAsEncrypted فعليًا

التنفيذ يتبع تشفير ECMA-376 القياسي، المحدَّد في القسم 2.3.4 من [MS-OFFCRYPTO]. كلمة المرور تمر عبر 50,000 تكرار من SHA-1 لاشتقاق مفتاح AES-128. كتلة تحقق، مشفّرة بـAES-128 في وضع ECB، تتيح للمستهلك تأكيد كلمة المرور قبل فك تشفير أي شيء، ثم تُشفَّر حزمة المصنف بأكملها بـAES-128 في وضع CBC. ما يهبط على القرص ليس أرشيفًا مضغوطًا على الإطلاق. إنه ملف OLE مركّب يحمل تدفقات EncryptionInfo وEncryptedPackage وDataSpaces، دون أي دليل xl/ لتسرده أداة أرشفة، وهذا سبب عدم ظهور أي شيء قابل للقراءة الآن في اختبار إعادة التسمية. يفتحه Excel 2007 وما بعده بكلمة المرور وحدها، ويقرأ LibreOffice الحالي التشفير القياسي أيضًا

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  rc: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Sheet := Book.Sheets.Add('Payroll');
    Sheet.Cells[1, 1].Value := 'Employee';
    Sheet.Cells[1, 2].Value := 'Net pay';
    Sheet.Cells[2, 1].Value := 'A. Garcia';
    Sheet.Cells[2, 2].Value := 4815.16;

    rc := Book.SaveAsEncrypted('payroll-2026-06.xlsx', PasswordFromVault);
    if rc <> 1 then
      raise Exception.CreateFmt('Encrypted save failed (rc=%d)', [rc]);
  finally
    Book.Free;
  end;
end;

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

هناك أيضًا اختبار قبول يمكن للآلة التحقق منه لا يكلف شيئًا يُذكر: استدع CanReadEncrypted على الملف الذي كتبته للتو. إنها تعيد true فقط عندما يكون الخرج فعلًا حاوية تشفير، فتأكيدها بعد كل حفظ مشفّر يلتقط الانحدار الأهم، مسار كود ارتد بصمت إلى SaveAs عادية، في اللحظة التي يحدث فيها بدلًا من أسابيع لاحقًا في صندوق وارد عميل. الكلمة الأخيرة لا تزال تعود إلى فتح يدوي في Excel بكلمة المرور الحقيقية أثناء اختبار الإصدار

للكتابة فقط بالتصميم: التعامل مع EXlsxEncryptionNotImplemented

إليك عدم التماثل الذي ينبغي أن يشكّل معمارية خط الأنابيب لديك: HotXLS يشفّر عند الحفظ لكنه لا يفك التشفير عند الفتح. OpenEncrypted تثير EXlsxEncryptionNotImplemented عند توجيهها إلى حزمة مشفّرة فعلًا؛ وعلى مصنف عادي فهي ببساطة تمر إلى Open عادية. الفاحص المرافق CanReadEncrypted يكتشف حاوية تشفير OLE برخص، فيمكن لكود الاستقبال توجيه مثل تلك الملفات دون إثارة الاستثناء:

مخطط مسار لاستدعاء SaveAsEncrypted في HotXLS داخل Delphi: كلمة مرور الخزنة تُدار عبر 50000 دورة SHA-1 إلى مفتاح AES-128، وكتلة مدقق ECB وتشفير حزمة CBC، منتِجة ملف OLE مركب يفحص بـ CanReadEncrypted
يحوّل استدعاء SaveAsEncrypted واحد كلمة مرور خزنة إلى مفتاح AES-128 وملف OLE مركب. ويمنح CanReadEncrypted الحفظ بوابة قبول قابلة للفحص آلياً
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    if Book.CanReadEncrypted(FileName) then
    begin
      // حاوية مشفّرة: HotXLS لا يستطيع فك تشفيرها.
      Writeln(FileName + ': needs manual decryption in Excel first');
      Exit;
    end;
    try
      Book.OpenEncrypted(FileName, '');   // الملفات العادية تمر إلى Open
      Writeln(FileName + ': opened, ' + IntToStr(Book.Sheets.Count) + ' sheet(s)');
    except
      on EXlsxEncryptionNotImplemented do
        Writeln(FileName + ': encrypted - routed to manual queue');
    end;
  finally
    Book.Free;
  end;
end;

ذلك التماثل غير المتكافئ له قراءة معمارية واحدة واضحة: شفّر عند حافة التسليم، أخيرًا. أبقِ النسخة الرئيسية بنص صريح داخل حدود ثقتك، في قاعدة بيانات، أو مخزن مستندات، أو مشاركة مضبوطة الوصول، وأنتج النسخة المشفّرة كالخطوة الأخيرة قبل مغادرة الملف للنظام. خط أنابيب يؤرشف الخرج المشفّر فقط قد أقفل نفسه خارج بياناته، لأنه لا توجد مرحلة لاحقة من النظام نفسه تستطيع إعادة فتح تلك الملفات. عندما تحتاج عملية HotXLS لاحقة المصنف مجددًا، سلّمها النسخة الرئيسية بنص صريح، لا قطعة التسليم أبدًا

مخطط معماري لخدمات Delphi: تبقى النسخة الأصلية النص الصريح للمصنف داخل حد ثقة، ويعمل SaveAsEncrypted في HotXLS كخطوة أخيرة عند حافة التسليم، مع تحذير من أرشفة النسخة المشفرة وحدها
شفّر عند حافة التسليم، أخيراً، من سيد صريح يُحفظ داخل حدود الثقة. والأرشفة للنسخة المشفرة وحدها تقفل كل مرحلة لاحقة خارج بياناتها هي

تشفير AES-128 القياسي وخط امتثال AES-256

يأتي تشفير ملفات Office في جيلين. التشفير القياسي، الذي يكتبه HotXLS، يستخدم AES-128 مع اشتقاق مفتاح SHA-1. التشفير الرشيق (Agile) وصل لاحقًا وينتقل إلى AES-256 مع SHA-512 وحاوية مفاتيح موصوفة بـXML مختلفة. كلاهما يُفتح بشفافية في Excel، وAES-128 لا يزال سليمًا حسابيًا لحماية ملف أثناء انتقاله إلى عميل

الفرق يتوقف عن كونه أكاديميًا في اليوم الذي يطلب فيه استبيان أمني "تشفير AES-256 للملفات في حالة السكون". التشفير القياسي لا يفي بذلك الخط، مهما كانت كلمة المرور قوية، ولا أي معامل من معاملات SaveAsEncrypted يغيّر الخوارزمية التي تصدرها. لذا اذكر الملف الشخصي بدقة في توثيقك الأمني: AES-128، تشفير ECMA-376 القياسي، اشتقاق مفتاح SHA-1 عند 50,000 تكرار. ادعاء ينجو من المراجعة يستحق أكثر من ادعاء متفائل ينهار تحت تدقيق

مسار XLS القديم: RC4 يخرج، RC4 وXOR يعودان

واجهة BIFF لها شكل معاكس. تشفيرها أقدم وأضعف، لكن الرحلة ذهابًا وإيابًا كاملة: ما تكتبه، تستطيع أيضًا قراءته مجددًا. ضبط EncryptionPassword قبل SaveAs ينتج ملف .xls مشفّرًا بـRC4 عبر آلية FilePass الخاصة بـBIFF، وOpen مع معامل كلمة مرور تقرأ المخططات القديمة الثلاثة جميعًا، RC4، وRC4 CryptoAPI، وتعمية XOR القديمة:

var
  Writer, Reader: IXLSWorkbook;   // مراجع واجهة: لا تحرير يدوي
begin
  Writer := TXLSWorkbook.Create;
  Writer.Sheets.Add.Cells.Item[1, 1].Value := 'Confidential';
  Writer.EncryptionPassword := 'S3cret!';
  Writer.SaveAs('confidential.xls');

  Reader := TXLSWorkbook.Create;
  if Reader.Open('confidential.xls', 'S3cret!') > 0 then
    Writeln(Reader.Sheets[1].Cells.Item[1, 1].Value);  // الفهرسة تبدأ من 1
end;

RC4 تشفير عفا عليه الزمن ولا ينبغي أبدًا أن يحمي بيانات مهمة اليوم؛ قيمته المتبقية الوحيدة هي التشغيل البيني مع أنظمة لا تزال تتبادل ملفات .xls. جهة القراءة، مع ذلك، تكسب مكانتها في أعمال الترحيل. ملف قديم محمي بكلمة مرور يُفتح بـOpen(FileName, Password)، ويُبنى جسر له في نموذج OOXML، ويُعاد تأمينه عبر مسار AES، ترقية باتجاه واحد تعمل دون Excel في أي مكان من الحلقة. للتسليمات المشفّرة عالية الحجم، ملاحظات إنتاجية جهة الحفظ في مقالتنا عن الكتابة المتدفقة للمهام الدفعية على الخادم تنطبق على مرحلة بناء المحتوى التي تحدث قبل التشفير

التشفير والحماية ليسا خصمين

نقطة أخيرة تستحق التوضيح، لأنها تُثار في اللحظة التي يقرأ فيها أحدهم التحذير في أعلى هذه الصفحة على أنه "الحماية عديمة القيمة". هذا غير صحيح. التشفير والحماية يجيبان عن سؤالين مختلفين، ويتراكبان بنظافة. التشفير يقرر من يستطيع فتح الملف؛ والحماية تقرر ما الذي يستطيع تغييره قارئ موجود بالفعل داخله. تسليم رواتب يمكنه معقولًا فعل الاثنين معًا: شفّر الحزمة حتى لا يراها إلا حامل كلمة المرور، ثم اقفل خلايا الصيغ حتى يستطيع المستلم التصفية والترتيب لكن دون إعادة كتابة الحسابات بهدوء. الخطأ ليس أبدًا في إضافة الحماية. الخطأ هو ترك وجودها يحل محل التشفير عندما كان المطلوب سرّية

جهة الحفظ الآمن ليس لها شبكة أمان، وهذا بالتصميم. اشتقاق المفتاح بـ50,000 تكرار موجود ليجعل التخمين مكلفًا، ولا شيء داخل الملف يودع السر لدى طرف ثالث. كلمة مرور مفقودة تعني بيانات مفقودة. ولّد وسلّم وخزّن كلمات المرور هذه بنفس الانضباط الذي تطبّقه على بيانات اعتماد قاعدة البيانات، وسيصمد التشفير بجانبه

تشفير الملفات الحقيقي استدعاء واحد في HotXLS. الانضباط يعيش في كل ما يحيط بالاستدعاء: حفظ كلمة المرور، والحد الأحادي الاتجاه الذي يمنع HotXLS من إعادة فتح خرجه الخاص، وادعاء خوارزمية يمكنك الدفاع عنه في تدقيق. SaveAsEncrypted والرحلة القديمة ذهابًا وإيابًا تشحنان مع HotXLS Delphi Component، تعملان بشكل أصلي في عمليات Delphi وC++Builder دون أي أتمتة لـExcel في أي مكان من المسار