مقاله فنی

خواندن فایل‌های اکسل رمزگذاری‌شده چابک (Agile) در دلفی با HotXLS

کامپوننت HotXLS فایل‌های اکسل رمزگذاری‌شده چابک (Agile) — یعنی محافظت با رمز عبور که اکسل 2010 و هر نسخه بعدی به طور پیش‌فرض اعمال می‌کنند — را از طریق یک فراخوانی واحد می‌خواند: TXLSXWorkbook.OpenEncrypted. این کامپوننت توصیف‌گر رمزگذاری XML را تجزیه می‌کند، کلیدها را از رمز عبور با یک زنجیره هش SHA-512 استخراج می‌نماید، رمز عبور را در برابر تاییدکننده رمزگذاری‌شده بررسی می‌کند و سپس بسته را در بخش‌های 4096 بایتی AES-CBC رمزگشایی می‌نماید. هیچ نیازی به نصب اکسل، COM یا DLL رمزنگاری خارجی نیست

این مقاله به طور خاص بخش خواندن رمزگذاری Agile را پوشش می‌دهد. دو مشکل مشابه مقالات خود را دارند: تعامل با طرح‌های قدیمی RC4 و XOR در فایل‌های قدیمی BIFF .xls در مقاله تعامل ECB و RC4 پوشش داده شده است، و تولید کتاب‌های کاری محافظت‌شده با رمز عبور با رمزگذاری استاندارد ECMA-376 در مقاله خروجی XLSX محافظت‌شده با AES توضیح داده شده است. در اینجا فایل از قبل وجود دارد، شخص دیگری آن را رمزگذاری کرده و کار شما باز کردن آن است

سناریویی که این موضوع را ایجاد می‌کند برای هر کسی که یک خط لوله پردازش اسناد را اجرا می‌کند آشناست. یک سرویس واردکننده سمت سرور، آپلودهای کتاب کار را می‌پذیرد؛ هیچ اکسلی روی دستگاه وجود ندارد و هرگز وجود نخواهد داشت؛ و یک روز صبح مشتری یک فایل کاملاً معمولی .xlsx را آپلود می‌کند که خواننده ZIP آن را رد می‌نماید زیرا اصلاً یک فایل ZIP نیست. مشتری آن را با رمز عبور ذخیره کرده است. از آن لحظه، بارگذار شما یا باید استاندارد [MS-OFFCRYPTO] را بفهمد یا فایل را به کاربری برگرداند که از دیدگاه خودش، هیچ کار غیرمعمولی انجام نداده است

رمزگذاری چابک (Agile) در یک فایل اکسل چیست؟

رمزگذاری Agile طرح محافظت با رمز عبور تعریف شده در استاندارد [MS-OFFCRYPTO] §2.3.4.10 تا §2.3.4.15 است و این همان چیزی است که اکسل 2010 و نسخه‌های بعدی هر زمان که یک کتاب کار با رمز عبور ذخیره شود، می‌نویسند. فایل رمزگذاری‌شده دیگر یک بسته ZIP نیست. بلکه یک کانتینر OLE Compound File Binary (CFB) حاوی دو جریان است: EncryptionInfo که نحوه انجام رمزگذاری را توصیف می‌کند و EncryptedPackage که همان فایل واقعی ZIP .xlsx است که به عنوان یک باینری مبهم رمزگذاری شده است. امضای CFB (D0 CF 11 E0 A1 B1 1A E1) همان جادویی است که فایل‌های قدیمی BIFF .xls حمل می‌کنند، به همین دلیل است که یک فایل تغییر نام یافته یا رمزگذاری‌شده را نمی‌توان تنها با پسوند آن طبقه‌بندی کرد

آنچه Agile را از پیشینیان خود متمایز می‌کند این است که جریان EncryptionInfo خودتوصیف‌گر است. پس از یک پیشوند نسخه 8 بایتی، با نسخه اصلی و فرعی هر دو 4، این جریان یک توصیف‌گر XML با رمزگذاری UTF-8 است. یک عنصر keyData نوع سایفر (AES)، حالت زنجیره‌ای (ChainingModeCBC)، هش (SHA512),طول کلید به بیت، اندازه بلوک و یک نمک (salt) با فرمت Base64 را اعلام می‌کند. یک عنصر رمز عبور keyEncryptor نمک مخصوص خود، تعداد تکرار spinCount و سه بارگذاری Base64 را حمل می‌کند: encryptedVerifierHashInput، encryptedVerifierHashValue و encryptedKeyValue. اکسل AES-256 را با تعداد تکرار 100,000 می‌نویسد، اما توصیف‌گر مجاز است AES-128 یا AES-192 را اعلام کند، و HotXLS به هر آنچه keyBits می‌گوید احترام می‌گذارد تا اینکه 256 را فرض کند

یک نقطه ورود برای کتاب‌های کاری متنی ساده، استاندارد و چابک (Agile)

متد TXLSXWorkbook.OpenEncrypted هر سه حالتی را که یک فراخوان ممکن است با آن مواجه شود (ZIP ساده، رمزگذاری‌شده استاندارد و رمزگذاری‌شده چابک) مدیریت می‌کند، بنابراین کنترل‌کننده‌های آپلود نیازی به طبقه‌بندی فایل‌ها قبل از بارگذاری ندارند. این متد ابتدا فایل را بو می‌کشد: اگر امضای CFB وجود نداشته باشد، کار را به مسیر عادی Open واگذار می‌کند و رمز عبور به سادگی نادیده گرفته می‌شود. اگر فایل یک کانتینر CFB باشد، ابتدا رمزگذاری استاندارد ECMA-376 را امتحان می‌کند و زمانی که امضای نسخه EncryptionInfo نسخه 4.4 Agile باشد، کار را به خط لوله Agile می‌فرستد. مقدار بازگشتی در صورت موفقیت 1 است، که همان پیمان متد Open است

var
  Wb: TXLSXWorkbook;
begin
  Wb := TXLSXWorkbook.Create;
  try
    // برای فایل‌های .xlsx ساده، رمزگذاری‌شده استاندارد
    // و رمزگذاری‌شده Agile به طور مشابه کار می‌کند
    if Wb.OpenEncrypted('upload.xlsx', 'customer-password') = 1 then
      Writeln(VarToWideStr(Wb.Sheets[1].Cells[1, 1].Value));
  finally
    Wb.Free;
  end;
end;

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

چگونه یک رمز عبور به یک کلید AES تبدیل می‌شود؟

رمزگذاری Agile هرگز مستقیماً از رمز عبور استفاده نمی‌کند. HotXLS ابتدا یک هش تکرارشونده را محاسبه می‌کند: خلاصه اولیه SHA-512 روی نمک رمز عبور است که با بایت‌های UTF-16LE رمز عبور متصل شده است، و سپس خلاصه spinCount بار دوباره هش می‌شود که در هر دور شمارنده تکرار 32 بیتی لیتل‌اندین به خلاصه قبلی متصل می‌گردد. با تعداد تکرار پیش‌فرض اکسل یعنی 100,000، این به معنای صدهزار فراخوانی متوالی SHA-512 برای هر تلاش رمز عبور است و این کل موضوع است. تعداد تکرار یک گلوگاه در برابر حمله نیروی ضربتی (brute-force) است: برای یک فراخوان مجاز یک بار چند میلی‌ثانیه هزینه دارد، اما برای یک مهاجم با دیکشنری، همین چند میلی‌ثانیه را برای هر حدس هزینه می‌کند

var
  buf: TBytes;
  i: Integer;
begin
  Result := XlsSHA512(Concat(Salt, Utf16LEBytes(Password)));
  SetLength(buf, 4 + 64);
  for i := 0 to SpinCount - 1 do
  begin
    PutLE32(buf, 0, i);            // شمارنده تکرار، لیتل‌اندین
    Move(Result[0], buf[4], 64);   // خلاصه قبلی
    Result := XlsSHA512(buf);
  end;
end;

کلید به دست آمده از هش همچنان یک کلید نهایی نیست. سه کلید مجزا با هش کردن مجدد آن با اتصال یک کلید بلوک 8 بایتی ثابت استخراج می‌شوند: FE A7 D2 76 3B 4B 9E 79 برای رمزگشایی ورودی تاییدکننده، D7 AA 0F 6D 30 61 34 4E برای هش تاییدکننده و 14 6E 0B E7 AB AC D0 D6 برای باز کردن کلید واقعی بسته. هر نتیجه SHA-512 به طول کلید اعلام‌شده کوتاه می‌شود و طبق استاندارد [MS-OFFCRYPTO]، با بایت‌های 0x36 در حالت تئوریک که هش کوتاه‌تر از کلید است پر می‌شود. همین قانون پر کردن با 0x36 در مواقعی که نمک رمز عبور برای استفاده به عنوان بردار مقداردهی اولیه CBC تا اندازه بلوک گسترش می‌یابد نیز اعمال می‌شود

تایید رمز عبور و تله کوتاه‌سازی saltSize

کامپوننت HotXLS قبل از دست زدن به بسته، رمز عبور را با استفاده از جفت تاییدکننده حاصل از توصیف‌گر تایید می‌کند. متد encryptedVerifierHashInput را با اولین کلید استخراج‌شده رمزگشایی می‌کند، نتیجه را با SHA-512 هش می‌نماید، encryptedVerifierHashValue را با کلید دوم رمزگشایی کرده و دو خلاصه را بایت به بایت مقایسه می‌کند. عدم تطابق به این معنی است که رمز عبور اشتباه است، که به عنوان یک نتیجه مشخص گزارش می‌شود و نه یک کتاب کار خراب، و به طور حیاتی به این معنی است که بدنه بسته هرگز با یک کلید بد رمزگشایی نمی‌شود، بنابراین سناریویی وجود ندارد که در آن رمز عبور اشتباه داده‌های خراب اما ظاهراً معتبر تولید کند

در اینجا یک جزئیات مشخصات وجود دارد که اشتباه گرفتن آن آسان است. استاندارد [MS-OFFCRYPTO] §2.3.4.13 تاییدکننده را به عنوان saltSize بایت داده تصادفی تعریف می‌کند، جایی که saltSize طول نمک رمزکننده کلید است و نه اندازه بلوک سایفر. از آنجا که متن رمزگذاری‌شده AES-CBC با بلوک تراز شده است، ورودی تاییدکننده رمزگشایی شده به صورت پر شده به مضربی از 16 بایت بازمی‌گردد و باید قبل از هش کردن به saltSize کوتاه شود. اکسل همیشه saltSize را برابر با blockSize، هر دو 16 می‌نویسد، بنابراین پیاده‌سازی که از کوتاه‌سازی چشم‌پوشی می‌کند، تمام آزمایش‌ها را در برابر خروجی واقعی اکسل با موفقیت پشت سر می‌گذارد و سپس در اولین فایل از تولیدکننده‌ای که طول نمک متفاوتی را انتخاب کرده با شکست مواجه می‌شود. HotXLS به طول نمک کوتاه می‌کند زیرا این چیزی است که مشخصات در واقع می‌گوید، و توافق این دو مقدار در عمل یک تصادف است و نه یک پیمان

چگونه EncryptedPackage رمزگشایی می‌شود؟

جریان EncryptedPackage با اندازه متن ساده 8 بایتی لیتل‌اندین شروع می‌شود و به دنبال آن متن رمزگذاری‌شده در بخش‌های 4096 بایتی قرار دارد، و HotXLS آن را بخش به بخش با یک IV جدید برای هر بخش رمزگشایی می‌کند. خود کلید بسته از رمز عبور مشتق نشده است: این یک کلید میانی تصادفی است که نویسنده آن را در encryptedKeyValue رمزگذاری کرده است و HotXLS آن را با کلید سوم باز می‌کند و به طول کلید اعلام‌شده توسط keyData کوتاه می‌نماید. IV هر بخش برابر با SHA-512 روی نمک keyData متصل‌شده با اندیس بخش 32 بیتی لیتل‌اندین است که به اندازه بلوک کوتاه شده است. این ساختار به این معنی است که هر بخش 4096 بایتی را می‌توان به طور مستقل رمزگشایی کرد، که این موضوع در اصل فرمت را با دسترسی تصادفی سازگار می‌کند، اگرچه HotXLS کل بسته را در حافظه رمزگشایی می‌کند و بایت‌های ZIP حاصل را به بارگذار معمولی XLSX خود می‌دهد

اندازه متن ساده اعلام‌شده بخش نهایی کار را انجام می‌دهد. خروجی AES-CBC با بلوک تراز شده است، بنابراین آخرین بخش تا 15 بایت پدینگ (padding) را حمل می‌کند که بخشی از سند نیست؛ بافر رمزگشایی شده به پیشوند اندازه کوتاه می‌شود و نتیجه دقیقاً همان ZIP .xlsx است که اکسل رمزگذاری کرده است. HotXLS قبل از رمزگشایی، پیشوند را در برابر طول واقعی جریان تایید می‌کند، بنابراین آپلود ناقص یا فیلد اندازه دستکاری‌شده به جای سرریز، به طور تمیز با شکست مواجه می‌شود

گزارش خطا و مرزهای واقعی

حالت‌های شکست عمداً جدا نگه داشته می‌شوند. رمز عبور اشتباه یک استثنا (exception) با پیام صریح رمز عبور اشتباه که ناشی از عدم تطابق تاییدکننده است ایجاد می‌کند، بنابراین یک رابط کاربری می‌تواند از کاربر بخواهد دوباره تلاش کند. یک کانتینر CFB که توصیف‌گر آن الگوریتم‌هایی خارج از مجموعه پشتیبانی‌شده اعلام می‌کند (هر چیزی غیر از AES با زنجیره CBC و هش SHA-512 در یک توصیف‌گر Agile) یا کانتینری که نه استاندارد است و نه چابک، استثنای متفاوتی را ایجاد می‌کند که طرح را به عنوان پشتیبانی‌نشده شناسایی می‌نماید. این دوگزینه هرگز نباید با هم ادغام شوند: تلاش مجدد برای رمز عبور در برابر یک طرح پشتیبانی‌نشده وقت کاربر را تلف می‌کند و گزارش یک رمز عبور اشتباه به عنوان یک خطای فرمت، تیم پشتیبانی شما را به مسیر اشتباه می‌فرستد

function LoadUploadedWorkbook(const FileName: WideString;
  const Password: WideString; Wb: TXLSXWorkbook): Boolean;
begin
  Result := False;
  try
    Result := Wb.OpenEncrypted(FileName, Password) = 1;
  except
    on E: EXlsxEncryptionNotImplemented do
      // برای رمز عبور اشتباه و طرح پشتیبانی‌نشده ایجاد می‌شود؛
      // پیام E.Message مشخص می‌کند کدام است، پس آن را دقیقاً ثبت کنید
      // و فقط برای رمز عبور اشتباه پیشنهاد تلاش مجدد بدهید
      RejectUpload(FileName, E.Message);
  end;
end;

شایسته است مرزها به وضوح بیان شوند. HotXLS توصیف‌گرهای Agile را که AES در حالت CBC با SHA-512 را اعلام می‌کنند می‌خواند، که شامل آنچه اکسل 2010 تا اکسل 365 در واقع می‌نویسند، در هر سه اندازه کلید می‌شود. توصیف‌گرهایی که سایر سایفرها یا الگوریتم‌های هش را اعلام می‌کنند به جای حدس زدن رد می‌شوند، و رمزکننده‌های کلید مبتنی بر گواهی مشورت نمی‌شوند، فقط رمزکننده کلید رمز عبور بررسی می‌شود. در سمت نوشتن، HotXLS در حال حاضر رمزگذاری استاندارد را به جای Agile تولید می‌کند، تمایزی که در صورت بررسی طرح توسط ابزارهای پایین‌دستی اهمیت دارد؛ جزئیات در مقاله نوشتن خروجی XLSX محافظت‌شده با AES آمده است

زمانی که بارگذار با رمزگذاری به عنوان بخشی از فرمت فایل رفتار کند تا یک استثنا برای آن، آپلودهای محافظت‌شده با رمز عبور دیگر یک حالت خاص نخواهند بود. نقطه ورود OpenEncrypted، استخراج با تعداد تکرار SHA-512 و خط لوله بخش‌بندی‌شده AES-CBC که در اینجا توضیح داده شد، به عنوان بخشی از کامپوننت اکسل دلفی HotXLS در کنار بقیه موتورهای بومی خواندن و نوشتن XLS و XLSX برای دلفی و C++Builder ارائه می‌شوند