مقاله فنی

رمزگذاری فایل‌های XLSX با AES در دلفی با HotXLS

اکسل دو چیز را افشا می‌کند که هر دو «رمز عبور» نامیده می‌شوند، و فقط یکی از آن‌ها رمزگذاری است. رمز عبور بازکردن، کلید یک رمز واقعی است: بدون آن فایل اصلاً خوانده نمی‌شود. رمزهای عبور محافظت کاربرگ و کارپوشه هیچ‌چیز از این دست انجام نمی‌دهند. آن‌ها پرچمی تنظیم می‌کنند که یک ویرایشگر همکار موافقت می‌کند آن را رعایت کند، و کارپوشه‌ای که چیزی جز آن پرچم ندارد یک zip ساده و خواندنی است که داده‌ها در آن به‌صورت متن آشکار نشسته‌اند. اشتباهی را انتخاب کنید و حقوق و دستمزدی را تحویل می‌دهید که در اکسل قفل به نظر می‌رسد و در هر ویرایشگر متنی خوانده می‌شود

اثبات ده ثانیه طول می‌کشد. یک .xlsx محافظت‌شده را به .zip تغییر نام دهید، آن را در هر ابزار آرشیوی باز کنید، و به xl/worksheets/sheet1.xml نگاه کنید. اگر مقادیر سلول‌ها آنجا به‌صورت UTF-8 ساده حضور دارند، فایل رمزگذاری نشده است، هرچند اکسل هنگام تلاش کسی برای ویرایش یک سلول هر تعداد پنجره رمز عبور نشان دهد. این شکاف سال‌ها درون تیم‌هایی دوام می‌آورد که فرض می‌کنند محافظت برگ همان محرمانگی است، و معمولاً روزی آشکار می‌شود که یک بازبینی امنیتی دقیقاً همین تغییر نام را انجام می‌دهد

HotXLS یک کتابخانه بومی صفحه‌گسترده برای دلفی و C++Builder است، و این دو ویژگی را در دو سوی مخالف آن خط نگه می‌دارد. محافظت کاربرگ و کارپوشه محدودیت‌های ویرایشی هستند که یک هش قدیمی به‌عمد ضعیف پشت آن‌هاست. SaveAsEncrypted بسته‌ای رمزگذاری‌شده با AES تولید می‌کند که هیچ‌چیز جز رمز عبور آن را باز نمی‌کند. بخش‌های زیر پوشش می‌دهند که آن فراخوانی چه می‌نویسد، عدم تقارنی که باید معماری‌تان را حول آن طراحی کنید (HotXLS فایل‌های رمزگذاری‌شده را می‌نویسد ولی نمی‌تواند آن‌ها را بازخوانی کند)، و اینکه مسیر قدیمی‌تر XLS چه تفاوتی دارد

دیاگرام مقایسه محافظت برگ XLSX در دلفی، که یک هش ضعیف ذخیره می‌کند و داده سلول‌ها را در یک zip ساده خواندنی باقی می‌گذارد، با SaveAsEncrypted در HotXLS، که یک کلید AES-128 استخراج می‌کند و یک ظرف رمزگذاری OLE می‌نویسد
محافظت برگ یک هش ضعیف ذخیره می‌کند و بسته را به‌صورت یک zip خواندنی باقی می‌گذارد. SaveAsEncrypted یک کلید AES-128 استخراج می‌کند و ظرف OLE‌ای می‌نویسد که هیچ ابزار آرشیوی نمی‌تواند فهرستش کند

چرا محافظت برگ رمزگذاری نیست

متدهای Protect روی برگ‌ها و ProtectWorkbook روی کارپوشه، یک هش ۴ رقمی هگزادسیمال از رمز عبور ذخیره می‌کنند. این همان الگوریتم قدیمی است که OOXML و BIFF هر دو از اکسل دهه ۱۹۹۰ به ارث برده‌اند، و مستندات فرمت هرگز ادعا نمی‌کند کاری بیش از جلوگیری از ویرایش تصادفی انجام می‌دهد. بسته یک zip معمولی و خواندنی می‌ماند: داده سلول‌ها، فرمول‌ها و رشته‌های مشترک همه در XML آشکار. پیش‌فرض اوضاع را بدتر می‌کند، نه بهتر. هر سلول با Locked=True شروع می‌شود، پس فراخوانی Protect بدون بازکردن قفل یک بازه ورودی از قبل، کل برگ را در برابر ویرایش منجمد می‌کند درحالی‌که هر مقدار را در معرض دید می‌گذارد

هیچ‌یک از این‌ها محافظت را بی‌فایده نمی‌کند. هدایت کاربران به بازه‌های قابل ویرایش و تثبیت یک چیدمان برای چاپ کارهای واقعی‌اند، که در مقاله ما درباره محافظت کاربرگ و تنظیم صفحه پوشش داده شده‌اند. ولی آن‌ها کارهای کاربردپذیری‌اند. همان لحظه‌ای که نیازمندی محرمانگی است، تنها API که به آن پاسخ می‌دهد SaveAsEncrypted است

SaveAsEncrypted واقعاً چه می‌نویسد

پیاده‌سازی از رمزگذاری استاندارد ECMA-376 پیروی می‌کند، که در بخش 2.3.4 سند [MS-OFFCRYPTO] مشخص شده است. رمز عبور از ۵۰٬۰۰۰ تکرار SHA-1 می‌گذرد تا یک کلید AES-128 استخراج شود. یک بلوک راستی‌آزما (verifier)، رمزگذاری‌شده با AES-128 در حالت ECB، به مصرف‌کننده اجازه می‌دهد پیش از رمزگشایی هر چیزی رمز عبور را تأیید کند، و سپس کل بسته کارپوشه با AES-128 در حالت CBC رمزگذاری می‌شود. آنچه روی دیسک می‌نشیند اصلاً zip نیست. یک فایل مرکب OLE است که جریان‌های EncryptionInfo، EncryptedPackage و DataSpaces را در خود دارد، بدون هیچ دایرکتوری xl/ که یک ابزار آرشیوی بتواند فهرستش کند، و به همین دلیل آزمون تغییر نام اکنون هیچ چیز خواندنی‌ای پیدا نمی‌کند. اکسل 2007 و بعد از آن، آن را فقط با رمز عبور باز می‌کند، و LibreOffice فعلی هم رمزگذاری استاندارد را می‌خواند

دیاگرام خط لوله یک فراخوانی SaveAsEncrypted در HotXLS برای دلفی: رمز عبور گاوصندوق از ۵۰٬۰۰۰ دور SHA-1 به یک کلید AES-128 می‌رسد، بلوک راستی‌آزمای ECB و رمزگذاری بسته در حالت CBC، و تولید یک فایل مرکب OLE که با CanReadEncrypted بررسی می‌شود
یک فراخوانی SaveAsEncrypted رمز عبور گاوصندوق را به یک کلید AES-128 و یک فایل مرکب OLE تبدیل می‌کند. CanReadEncrypted به ذخیره یک دروازه پذیرش قابل بررسی ماشینی می‌دهد
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;

با متغیر رمز عبور همان‌قدر با احتیاط رفتار کنید که با یک رشته اتصال. آن را در آخرین لحظه از یک گاوصندوق (vault) یا یک سرویس راز تولیدشده بگیرید، هرگز آن را لاگ نکنید، و هرگز آن را درون خود کارپوشه ننویسید. بررسی کد بازگشتی تشریفات اختیاری نیست. یک ذخیره رمزگذاری که در میانه راه شکست بخورد باید تحویل را لغو کند، چون تنها جایگزینی که کد فراخواننده می‌تواند ارائه دهد یک نسخه رمزگذاری‌نشده است، و آن نسخه دقیقاً همان حادثه‌ای است که این ویژگی برای جلوگیری از آن وجود دارد

یک آزمون پذیرش قابل بررسی ماشینی هم هست که تقریباً هیچ هزینه‌ای ندارد: CanReadEncrypted را روی فایلی که همین حالا نوشتید فراخوانی کنید. فقط وقتی true برمی‌گرداند که خروجی واقعاً یک ظرف رمزگذاری باشد، پس تأیید آن پس از هر ذخیره رمزگذاری‌شده، مهم‌ترین رگرسیون را می‌گیرد، یعنی یک مسیر کد که بی‌سروصدا به یک SaveAs ساده عقب‌نشینی کرده، همان لحظه که رخ می‌دهد نه هفته‌ها بعد در صندوق ورودی یک مشتری. حرف آخر همچنان با یک بازکردن دستی در اکسل با رمز عبور واقعی هنگام آزمون انتشار است

فقط‌نوشتنی از روی طراحی: مدیریت EXlsxEncryptionNotImplemented

این همان عدم تقارنی است که باید معماری خط لوله شما را شکل دهد: HotXLS هنگام ذخیره رمزگذاری می‌کند ولی هنگام بازکردن رمزگشایی نمی‌کند. OpenEncrypted وقتی به یک بسته واقعاً رمزگذاری‌شده اشاره کند EXlsxEncryptionNotImplemented را برمی‌انگیزد؛ روی یک کارپوشه ساده صرفاً به یک Open معمولی فرو می‌افتد. کاوشگر همراه CanReadEncrypted ظرف رمزگذاری OLE را ارزان تشخیص می‌دهد، پس کد ورودی می‌تواند چنین فایل‌هایی را بدون برانگیختن استثنا هدایت کند:

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 دوباره به کارپوشه نیاز دارد، نسخه اصلی متن آشکار را به آن بدهید، هرگز محصول تحویلی را

دیاگرام معماری برای سرویس‌های دلفی: نسخه اصلی متن آشکار کارپوشه درون یک مرز اعتماد می‌ماند، SaveAsEncrypted در HotXLS به‌عنوان آخرین گام در لبه تحویل اجرا می‌شود، و هشداری در برابر آرشیوکردن فقط نسخه رمزگذاری‌شده
در لبه تحویل رمزگذاری کنید، در آخر، از یک نسخه اصلی متن آشکار که درون مرز اعتماد نگه داشته می‌شود. آرشیوکردن فقط نسخه رمزگذاری‌شده، هر مرحله بعدی را از داده‌های خودش بیرون قفل می‌کند

رمزگذاری استاندارد AES-128 و خط انطباق AES-256

رمزگذاری فایل‌های Office در دو نسل می‌آید. رمزگذاری استاندارد، همانی که HotXLS می‌نویسد، از AES-128 با استخراج کلید SHA-1 استفاده می‌کند. رمزگذاری چابک (Agile) بعدها رسید و به AES-256 با SHA-512 و یک ظرف کلید متفاوت با توصیف XML می‌رود. هر دو به‌صورت شفاف در اکسل باز می‌شوند، و AES-128 هنوز از نظر محاسباتی برای محافظت از یک فایل در حال انتقال به مشتری معتبر است

تفاوت روزی از حالت آکادمیک خارج می‌شود که یک پرسشنامه امنیتی «رمزگذاری AES-256 فایل‌ها در حالت سکون» را بخواهد. رمزگذاری استاندارد آن خط را برآورده نمی‌کند، هرقدر هم رمز عبور قوی باشد، و هیچ پارامتری از SaveAsEncrypted الگوریتمی را که تولید می‌کند تغییر نمی‌دهد. پس نمایه را در مستندات امنیتی خود دقیق بیان کنید: AES-128، رمزگذاری استاندارد ECMA-376، استخراج کلید SHA-1 با ۵۰٬۰۰۰ تکرار. ادعایی که از بازبینی جان سالم به در می‌برد بیش از ادعای خوش‌بینانه‌ای می‌ارزد که زیر یک ممیزی فرو می‌ریزد

مسیر قدیمی XLS: RC4 در خروج، RC4 و XOR در ورود

نمای BIFF شکل معکوس دارد. رمزگذاری‌اش قدیمی‌تر و ضعیف‌تر است، ولی رفت‌وبرگشت کامل است: آنچه می‌نویسد را می‌تواند بازخوانی هم بکند. تنظیم EncryptionPassword پیش از SaveAs یک .xls رمزگذاری‌شده با RC4 از طریق سازوکار FilePass در BIFF تولید می‌کند، و Open با یک پارامتر رمز عبور هر سه طرح قدیمی را می‌خواند: RC4، RC4 CryptoAPI و مبهم‌سازی باستانی XOR:

var
  Writer, Reader: IXLSWorkbook;   // ارجاع‌های واسط: بدون Free دستی
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);  // ورودی‌ها ۱-مبنا هستند
end;

RC4 رمزنگاری منسوخ است و هرگز نباید از داده‌ای که امروز اهمیت دارد محافظت کند؛ تنها ارزش باقی‌مانده‌اش تعامل‌پذیری با سیستم‌هایی است که هنوز .xls رد و بدل می‌کنند. با این حال، سمت خواندن در کار مهاجرت ارزش خود را ثابت می‌کند. یک فایل قدیمی محافظت‌شده با رمز عبور با Open(FileName, Password) باز می‌شود، به مدل OOXML پل می‌زند، و از طریق مسیر AES دوباره ایمن می‌شود؛ یک ارتقای یک‌طرفه که بدون اکسل در هیچ جای حلقه اجرا می‌شود. برای تحویل‌های رمزگذاری‌شده پرحجم، نکات توان عملیاتی سمت ذخیره در مقاله ما درباره نوشتن جریانی برای کارهای دسته‌ای سرور بر مرحله ساخت محتوا که پیش از رمزگذاری رخ می‌دهد اعمال می‌شوند

رمزگذاری و محافظت رقیب نیستند

یک نکته دیگر ارزش تعیین‌تکلیف دارد، چون همان لحظه‌ای مطرح می‌شود که کسی هشدار بالای این صفحه را به‌صورت «محافظت بی‌ارزش است» بخواند. چنین نیست. رمزگذاری و محافظت به پرسش‌های متفاوتی پاسخ می‌دهند، و به‌خوبی روی هم سوار می‌شوند. رمزگذاری تعیین می‌کند چه کسی می‌تواند فایل را باز کند؛ محافظت تعیین می‌کند خواننده‌ای که از قبل داخل است چه چیزی را می‌تواند تغییر دهد. یک تحویل حقوق و دستمزد به‌طور معقول می‌تواند هر دو را انجام دهد: بسته را رمزگذاری کنید تا فقط دارنده رمز عبور آن را ببیند، سپس سلول‌های فرمول را قفل کنید تا گیرنده بتواند فیلتر و مرتب کند ولی بی‌سروصدا محاسبات را بازنویسی نکند. خطا هرگز افزودن محافظت نیست. خطا این است که اجازه دهید حضورش جای رمزگذاری را بگیرد وقتی نیازمندی محرمانگی بوده است

سمت نگهداری هیچ تور ایمنی ندارد، و این از روی طراحی است. استخراج کلید با ۵۰٬۰۰۰ تکرار وجود دارد تا حدس‌زدن را گران کند، و هیچ‌چیز درون فایل راز را به امانت نمی‌سپارد. رمز عبور گم‌شده یعنی داده گم‌شده. این رمزهای عبور را با همان انضباطی که برای اعتبارنامه‌های پایگاه داده به کار می‌برید تولید، تحویل و ذخیره کنید، و رمزگذاری سهم خود را ادا می‌کند

رمزگذاری واقعی فایل در HotXLS یک فراخوانی است. انضباط در همه‌چیز پیرامون آن فراخوانی زندگی می‌کند: نگهداری رمز عبور، مرز فقط‌نوشتنی که HotXLS را از بازکردن دوباره خروجی خودش بازمی‌دارد، و ادعای الگوریتمی که بتوانید در یک ممیزی از آن دفاع کنید. SaveAsEncrypted و رفت‌وبرگشت قدیمی همراه با HotXLS Delphi Component عرضه می‌شوند، که به‌صورت بومی در فرایندهای دلفی و C++Builder و بدون اتوماسیون اکسل در هیچ جای مسیر اجرا می‌شود