شما یک کتاب کار مینویسید، آن را با یک رمز عبور رمزگذاری میکنید، فایل را به همکارتان تحویل میدهید و همکارتان آن را در Excel باز میکند. نرمافزار Excel رمز عبور را میخواهد. همکار آن را تایپ میکند و Excel آن را میپذیرد. تا اینجا رمزگذاری درست به نظر میرسد. سپس Excel کادری را نشان میدهد که میگوید فایل خراب است و نمیتواند باز شود، یا با صفحهای از سلولهای بیمعنی باز میشود. رمز عبور درست بود. با این حال فایل خراب است. این گیجکنندهترین حالت شکست در رمزگذاری Office است، زیرا بخشی که به شما میگوید رمز عبور درست است و بخشی که دادههای شما را نگه میدارد با دو عملیات متفاوت محافظت میشوند و صحیح بودن یکی دلیلی برای تضمین دیگری نیست
هر دو باگ توضیح داده شده در اینجا دقیقاً این شکل را داشتند. در هر مورد، بخش اعتبارسنجی عبور میکرد اما بدنه با شکست مواجه میشد، که شما را به جستجوی یک باگ در رمز عبور یا استخراج کلید میکشاند که اصلاً وجود نداشت. خطای واقعی در پاییندست و در نحوه تبدیل بایتهای بسته بود. این دو خطا مستقل از هم هستند، یکی در مسیر AES و دیگری در مسیر RC4، اما در مشکل تشخیص مشترک هستند، بنابراین ارزش دارد ببینیم چرا نتیجه نیمهصحیح سختترین نوع برای تفسیر است
چرا تایید رمز عبور هیچ چیز را در مورد بدنه ثابت نمیکند
فرمت مورد استفاده در کتاب کار رمزگذاریشده XLSX مدرن، استاندارد رمزگذاری ECMA-376 است و دو بخش رمزگذاریشده را در کنار هم ذخیره میکند. یکی EncryptionVerifier است: بلوک کوچکی حاوی یک مقدار تصادفی و هش آن مقدار، که با کلید مشتق از رمز عبور رمزگذاری شده است. دیگری EncryptedPackage است: کل ظرف فشرده کتاب کار، که با همان کلید رمزگذاری شده است. تاییدکننده به این دلیل وجود دارد که یک خواننده بتواند رمز عبور را قبل از صرف انرژی روی مگابایتها بدنه تایید کند. تاییدکننده را رمزگشایی کنید، مقدار تصادفی را هش کنید، آن را با هش ذخیرهشده مقایسه نمایید و در صورت مطابقت، رمز عبور صحیح است
تله این است که تاییدکننده و بسته با فراخوانیهای جداگانه در بافرهای جداگانه رمزگذاری میشوند. کلیدی که به درستی استخراج شده باشد، تاییدکننده را بدون توجه به اتفاقی که بعداً برای بسته میافتد، به درستی رمزگشایی میکند. بنابراین اگر استخراج کلید شما درست باشد اما تبدیل بسته شما اشتباه باشد، Excel رمز عبور را از روی تاییدکننده تایید کرده و سپس در بدنه شکست میخورد. علامت به صورت "رمز عبور درست، فایل خراب" خوانده میشود که تحقیقات را به مسیر رمز عبور هدایت میکند، یعنی تنها بخشی که هرگز خراب نبوده است. همین جداسازی بر مورد قدیمی RC4 نیز حاکم است: هش تاییدکننده ابتدا بررسی میشود و بدنهای که از همگامسازی خارج شده همچنان آن بررسی را دستنخورده باقی میگذارد
باگ اول: AES در حالت ECB و نه CBC
بخش ۲.۳.۴.۱۵ سند [MS-OFFCRYPTO] مشخص میکند که استاندارد رمزگذاری، بسته را با AES در حالت Electronic Codebook رمزگذاری میکند. هر بلوک ۱۶ بایتی از بسته پدینگشده به طور مستقل با همان کلید رمزگذاری میشود. هیچ زنجیرهای بین بلوکها وجود ندارد و هیچ بردار مقداردهی اولیه (IV) وجود ندارد. این یک انتخاب غیرمعمول بر اساس استانداردهای مدرن است که در آن معمولاً از ECB اجتناب میشود، اما رفت و برگشت اطلاعات جایی برای حدس دوم درباره مشخصات فنی نیست. نرمافزار Excel بسته را به صورت ECB رمزگشایی میکند، بنابراین تولیدکننده نیز باید آن را به صورت ECB رمزگذاری کند تا دو طرف با هم توافق داشته باشند
باگ این بود که بسته با AES در حالت CBC با استفاده از یک بردار مقداردهی اولیه تمام صفر رمزگذاری شده بود. به این دلیل است که این روش تقریباً کار میکرد و چرا تقریباً، بدترین نقطه فرود است. در حالت CBC، اولین بلوک متن ساده قبل از رمزگذاری با IV عمل XOR میشود. وقتی IV تمام صفر است، آن XOR هیچ چیز را تغییر نمیدهد، بنابراین اولین بلوک CBC با IV صفر، دقیقاً همان متن رمزگذاریشده ECB را تولید میکند. از بلوک دوم به بعد، حالت CBC بلوک متن رمزگذاریشده قبلی را به بعدی تغذیه میکند، بنابراین هر بلوکی بعد از اولین بلوک از ECB منحرف میشود
حالا این موضوع را روی ساختار قرار دهید. چیدمان بسته یک پیشوند طول ۸ بایتی little-endian را در همان ابتدای کار قرار میدهد، بنابراین بخشهایی از فایل که Excel زودتر بررسی میکند در اولین بلوک یا دو بلوک اول قرار دارند. اولین بلوکی که تطبیق دارد به این معنی است که اولین اعتبارسنجیها عبور میکنند در حالی که هر بلوک بعدی به عنوان نویز رمزگشایی میشود. راه حل پس از نامگذاری حالت، ظریف نیست: هر بلوک ۱۶ بایتی را با ECB رمزگذاری کنید و زنجیره را متوقف نمایید. در موتور، تابع XlsEncryptStdPackage بافر پدینگشده را در مراحل ۱۶ بایتی پیمایش میکند و AESEncryptECB128Block را روی هر کدام فراخوانی مینماید که همان متد اولیه مورد استفاده برای بلوکهای تاییدکننده است. منبع شامل نظری در حلقه است که قانون را به وضوح بیان میکند: حالت CBC با یک IV صفر فقط برای اولین بلوک با ECB مطابقت دارد، بنابراین بقیه بسته به صورت دادههای بیهوده رمزگشایی میشود و Excel آن را رد میکند
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create(nil);
try
Book.Open('report.xlsx');
// SaveAsEncrypted serializes the workbook, then runs the
// ECMA-376 Standard Encryption pipeline: AES-128 ECB over the
// package per [MS-OFFCRYPTO] 2.3.4.15. Returns 1 on success.
if Book.SaveAsEncrypted('report_secure.xlsx', 'S3cret!') <> 1 then
raise Exception.Create('Encryption failed');
finally
Book.Free;
end;
end;
باگ دوم: تغییر کلید RC4 از مرحله خارج میشود
مجموعه .xls قدیمی از طرح RC4 CryptoAPI استفاده میکند و قانون آن متفاوت است. بخش ۲.۳.۶ سند [MS-OFFCRYPTO] مشخص میکند که رمز در هر مرز بلوک ۱۰۲۴ بایتی مجدداً کلیدگذاری میشود. جریان به بلوکهای ۱۰۲۴ بایتی تقسیم میشود، یک کلید تازه RC4 برای بلوک شماره ۰، ۱، ۲ و غیره مشتق میگردد، و در داخل هر بلوک جریان کلید به طور مداوم از بایت به بایت مصرف میشود. دو ثابت باید با هم حفظ شوند: کلیدگذاری مجدد در هر مرز، و مصرف جریان کلید بدون شکاف در داخل یک بلوک. رمز RC4 یک رمز جریانی است، بنابراین جریان کلید آن یک توالی منظم واحد است؛ بایت nامی که میکشید با تعداد بایتهایی که قبلاً کشیدهاید تعیین میشود. رمزگشایی همان XOR در برابر همان توالی است، به این معنی که تولیدکننده و مصرفکننده باید دقیقاً همان بایتها را در همان موقعیتها بکشند
کل دشواری در همین است. یک رمز جریانی هیچ همگامسازی مجددی ندارد. اگر یک بایت از جریان کلید را هدر دهید، هر بایت بعد از آن با بایت اشتباه از جریان کلید XOR میشود و خطا هرگز خود را اصلاح نمیکند؛ خطا به انتهای بلوک و پس از اشتباه بودن موقعیت در حال اجرا، به هر بلوکی بعد از آن سرایت میکند. باگ در اینجا دقیقاً همین کار را کرد. شمارنده بلوک از یک مقدار نگهبان منفی یک شروع شد و روال پرش فرض کرد که شمارنده از قبل با بلوک فعلی مطابقت دارد. با شروع از آن نگهبان، کلیدگذاری مجدد کرد و یک بلوک کامل ۱۰۲۴ بایتی از جریان کلید را که هرگز نباید مصرف میشد، اجرا کرد و در این فرآیند تعداد باقیمانده را منفی ساخت. از آن نقطه، رمزگشا یک بلوک کامل از فاز خارج شد. تاییدکننده، که قبل از هر یک از اینها بررسی شد، همچنان عبور کرد، بنابراین رمز عبور درست به نظر میرسید در حالی که هر سلول داده به عنوان دادههای بیهوده ظاهر میشد
منطق اصلاحشده در TXLSDecrypterRC4 قرار دارد. هر دو متد Skip و Decrypt در یک حلقه مشترک هستند: کلیدگذاری مجدد فقط زمانی که موقعیت در حال اجرا به یک بلوک جدید وارد میشود، جایی که نمایه بلوک همان موقعیت تقسیم بر REKEY_BLOCK_SIZE (۱۰۲۴) است، سپس تا باقیمانده بلوک فعلی و نه بیشتر مصرف کنید. متد MakeKey با نمایه بلوک فراخوانی میشود، نه با یک نمایه قدیمی یا نگهبان، و موقعیت به تعداد دقیق بایتهای پردازششده پیش میرود تا Skip و Decrypt با تولیدکننده همفاز بمانند. درس در کوچکترین واحد نهفته است: یک بایت هدررفته یک خطای کوچک در یک رمز جریانی نیست، بلکه از دست دادن کامل همه چیز در پاییندست است
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create(nil);
try
// CanReadEncrypted checks the Compound File (OLE2) signature so
// you can branch before attempting a normal Open. OpenEncrypted
// routes plain files to Open and handles the encrypted container.
if Book.CanReadEncrypted('legacy.xls') then
Book.OpenEncrypted('legacy.xls', 'S3cret!')
else
Book.Open('legacy.xls');
// read cells here
finally
Book.Free;
end;
end;
هماهنگی با مشخصات فنی ثابت، مطابقت بایت به بایت است
هر دو باگ به یک اصل ریشهای کاهش مییابند و ارزش دارد که به تنهایی بیان شود زیرا نحوه سنجش گزینههای طراحی را تغییر میدهد. وقتی مصرفکننده خروجی شما یک برنامه خارجی ثابت است که نمیتوانید آن را تغییر دهید، حالت رمزگذاری و آهنگ کلیدگذاری مجدد جزئیات پیادهسازی نیستند که بتوانید آنها را بهینهسازی یا ساده کنید. آنها بخشی از قرارداد سیمکشی هستند. نرمافزار Excel با حالت ECB رمزگشایی خواهد کرد و در مرزهای ۱۰۲۴ بایتی مجدداً کلیدگذاری مینماید، چه این انتخابها شما را خشنود سازد چه نسازد، و تنها وظیفه شما تولید بایتهایی است که تحت آن روش دقیق به نسخه اصلی رمزگشایی شوند. حالتی که مدرنتر است، یک IV که بیضرر به نظر میرسد، شمارندهای که از جایی شروع میشود که طبیعی به نظر میرسد؛ هر یک از اینها لحظهای که از آنچه خواننده انتظار دارد منحرف شود، یک نقص است. هماهنگی در برابر یک مشخصات فنی ثابت تقریبی نیست. بلکه تطابق بایت به بایت است در غیر این صورت خراب است
به همین دلیل است که تاییدکننده به تنهایی آزمایش دود (Smoke test) ضعیفی است. این تاییدکننده به شما میگوید که استخراج کلید کار میکند که لازم است اما به هیچ وجه کافی نیست. آزمایشی که فقط یک فایل رمزگذاریشده را باز میکند و صحت رمز عبور را تایید میکند، در حالی که بدنه غیرقابل خواندن است، موفقیت گزارش خواهد داد. یک آزمایش واقعی بسته را رمزگشایی کرده و بایتهای بازیابیشده را با ورودی اصلی مقایسه میکند، یا یک کتاب کار را در رفت و برگشت رمزگذاری و رمزگشایی کرده و سلولها را دوباره میخواند. تاییدکننده رمز عبور را ثابت میکند؛ فقط بدنه رمزگذاری را ثابت مینماید
روش پشتیبانیشده برای خواندن و نوشتن کتاب کارهای محافظتشده
بخش عمومی کوچک است. برای نوشتن یک کتاب کار مدرن محافظتشده با رمز عبور، یک TXLSXWorkbook را پر یا باز کرده و SaveAsEncrypted را با نام فایل و رمز عبور فراخوانی کنید؛ این متد کتاب کار را سریالسازی کرده و خط لوله رمزگذاری استاندارد ECMA-376 را که اصلاح اول برطرف کرد اجرا میکند و در صورت موفقیت مقدار ۱ را برمیگرداند. برای خواندن، CanReadEncrypted را برای آزمایش اینکه آیا فایل یک ظرف سند مرکب (OLE2) رمزگذاریشده است فراخوانی کرده و سپس شاخه ایجاد کنید: متد OpenEncrypted مسیر رمزگذاریشده را مدیریت میکند و برای فایلهای ساده به Open بازمیگردد، و Open با رمز عبور مستقیماً در دسترس است. مدیریت حالت و حلقه کلیدگذاری مجدد که در بالا توضیح داده شد در زیر این فراخوانیها قرار دارند؛ شما رمز عبور و نام فایل را ارائه میدهید و موتور مشخصات را از طرف شما مطابقت میدهد
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create(nil);
try
Book.Open('quarterly.xlsx');
Book.SaveAsEncrypted('quarterly_locked.xlsx', 'P@ssphrase');
// Reopen on the consumer side
Book.OpenEncrypted('quarterly_locked.xlsx', 'P@ssphrase');
finally
Book.Free;
end;
end;
شکل خروجی محافظتشده، جریان EncryptionInfo، بلوکهای تاییدکننده و طرحبندی بسته در راهنمای ما درباره خروجی XLSX محافظتشده با AES پوشش داده شده است. برای سوال مجزا درباره قفل کردن در سطح برگ و نحوه تعامل محافظت با تنظیم صفحه و چاپ، به مقاله مربوط به محافظت، تنظیم صفحه و چاپ مراجعه نمایید. هر دو بر روی مسیر رمزگذاری توضیح داده شده در اینجا ساخته شدهاند که به عنوان بخشی از کامپوننت صفحه گسترده HotXLS برای Delphi و C++Builder همراه با APIهای خواندن, نوشتن و رندر که در بخشهای دیگر این وبلاگ پوشش داده شدهاند، عرضه میشود