Техническа статия

Четене на гъвкаво шифровани (Agile-Encrypted) Excel файлове в Delphi с HotXLS

HotXLS чете Agile шифровани Excel файлове — защитата с парола, която Excel 2010 и всички следващи версии прилагат по подразбиране — чрез едно единствено извикване: TXLSXWorkbook.OpenEncrypted; Компонентът анализира XML дескриптора за шифроване, извежда ключове от паролата с SHA-512 хеш верига, проверява паролата спрямо шифрования валидатор и след това дешифрира пакета в 4096-байтови AES-CBC сегменти; В процеса не участват инсталация на Excel, COM или външни криптографски DLL файлове

Тази статия разглежда конкретно страната за четене на Agile шифроването; Два съседни проблема имат свои собствени статии: съвместимостта със старите схеми RC4 и XOR в стари BIFF .xls файлове е описана в статията за съвместимост с ECB и RC4, а генерирането на защитени с парола работни книги с ECMA-376 Standard Encryption е описано в статията за защитен с AES XLSX изход; Тук файлът вече съществува, някой друг го е шифровал и вашата задача е да го отворите

Сценарият, който налага това, е познат на всеки, който управлява конвейер за обработка на документи; Сървърна услуга за импортиране приема качвания на работни книги; на машината няма инсталиран Excel и никога няма да има; и една сутрин потребител качва напълно обикновен .xlsx, който ZIP четецът отхвърля, защото изобщо не е ZIP файл; Клиентът го е запазил с парола; От този момент вашето зареждащо устройство или разбира [MS-OFFCRYPTO], или връща файла обратно на потребител, който от своя страна не е направил нищо необичайно

Какво представлява Agile шифроването в Excel файл?

Agile шифроването е схемата за защита с парола, дефинирана в [MS-OFFCRYPTO] §2.3.4.10 до §2.3.4.15, и е това, което Excel 2010 и следващите версии записват винаги, когато работна книга се записва с парола; Шифрованият файл вече не е ZIP пакет; Той е OLE Compound File Binary (CFB) контейнер, съдържащ два потока: EncryptionInfo, който описва как е извършено шифроването, и EncryptedPackage, който представлява реалния .xlsx ZIP, шифрован като непрозрачен двоичен обект (blob); Сигнатурата на CFB (D0 CF 11 E0 A1 B1 1A E1) е същата магия, която носят и старите BIFF .xls файлове, поради което преименуван или шифран файл не може да бъде класифициран само по разширението му

Това, което отличава Agile от неговите предшественици, е че EncryptionInfo се самоописва; След 8-байтов префикс за версия, с основна и подверсия 4, потокът представлява UTF-8 XML дескриптор; Елемент keyData декларира шифъра (AES), режима на веригата (ChainingModeCBC), хеша (SHA512), дължината на ключа в битове, размера на блока и Base64 сол (salt); Елемент keyEncryptor за парола носи своя собствена сол, spinCount и три Base64 полезни товара: encryptedVerifierHashInput, encryptedVerifierHashValue и encryptedKeyValue; Excel записва AES-256 с брой завъртания (spin count) 100 000, но дескрипторът позволява деклариране на AES-128 или AES-192, а HotXLS зачита стойността на keyBits, вместо да предполага 256

Една входна точка за документи в чист текст, Standard и Agile защитени работни книги

TXLSXWorkbook.OpenEncrypted обработва и трите състояния, които извикващият може да срещне: чист ZIP, Standard шифрован и Agile шифрован, така че модулите за качване не трябва да класифицират файловете, преди да ги заредят; Методът първо проверява файла: ако няма CFB сигнатура, той преминава към нормалния път Open и паролата просто се игнорира; Ако файлът е CFB контейнер, по опитва ECMA-376 Standard Encryption и когато сигнатурата на версията в EncryptionInfo е Agile 4.4, преминава към Agile обработка; Върнатата стойност е 1 при успех — същият договор като при Open

Резервният вариант за нешифрован вход има по-голямо значение, отколкото изглежда; Пакетен импортер, който винаги извиква OpenEncrypted, не се нуждае от разклонения на кода при извикването: файловете, които никога не са били защитени, се зареждат точно както преди, а файловете, които пристигат шифровани, се дешифрират на място и след това се подават на обикновения ZIP четец като поток в паметта; Има един път на кода за тестване, а не три

Как паролата се превръща в AES ключ?

Agile шифроването никога не използва паролата директно; HotXLS първо изчислява итериран хеш: първоначалният дайджест е SHA-512 върху солта за паролата, съединена с UTF-16LE байтовете на паролата, а след това дайджестът се хешира повторно spinCount пъти, като при всеки кръг се предхожда от 32-битовия малко-ендиан брояч на итерациите към предишния дайджест; При подразбиращия се за Excel брой завъртания 100 000 това прави сто хиляди последователни SHA-512 извиквания за всеки опит с парола, и това е цялата идея; Броят завъртания е спирачка срещу груба сила (brute-force): той струва на легитимен извикващ няколко милисекунди веднъж, а на атакуващ — същите няколко милисекунди за всяко едно предположение

// [MS-OFFCRYPTO] итериран хеш на парола:
//   H(0) = SHA-512(salt + UTF-16LE(password))
//   H(n) = SHA-512(LE32(n - 1) + H(n - 1)), repeated spinCount times
function AgilePasswordHash(const Password: WideString;
  const Salt: TBytes; SpinCount: Integer): TBytes;
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 инициализиращ вектор (IV)

Проверка на паролата и капанът със съкращаването на saltSize

HotXLS проверява паролата преди да докосне пакета, като използва двойката валидатори от дескриптора; Той дешифрира encryptedVerifierHashInput с първия изведен ключ, хешира резултата с SHA-512, дешифрира encryptedVerifierHashValue с втория изведен ключ и сравнява двата дайджеста байт по байт; Несъответствието означава, че паролата е грешна, което се докладва като отделен резултат, а не като повредена работна книга, и което е критично — тялото на пакета никога не се дешифрира с лош ключ, така че няма сценарий, при който грешна парола да произвежда изглеждащи вярно, но повредени данни

Тук има подробност от спецификацията, която е лесно да се сгреши; [MS-OFFCRYPTO] §2.3.4.13 дефинира валидатора като saltSize байта произволни данни, където saltSize е дължината на солта на шифроващия ключ, а не размерът на блока на шифъра; Тъй като изходът на AES-CBC е подравнен по блокове, дешифрираният вход на валидатора се връща допълнен до кратно на 16 байта и трябва да бъде съкратен обратно до saltSize преди хеширане; Excel винаги записва saltSize равен на size на блока, и двата 16, така че имплементация, която пропуска съкращаването, преминава успешно всеки тест срещу реален изход от Excel и след това се проваля при първия файл от създател, избрал различна дължина на солта; HotXLS съкращава до дължината на солта, защото това е, което спецификацията действително изисква, а съвпадението на двете стойности на практика е съвпадение, а не договор

Как се дешифрира EncryptedPackage?

Потокът EncryptedPackage започва с 8-байтов малко-ендиан размер в чист текст, последван от шифрования текст в 4096-байтови сегменти, и HotXLS го дешифрира сегмент по сегмент с нов IV за всеки сегмент; Самият ключ на пакета не е изведен от паролата: той е произволен междинен ключ, който създателят е шифровал в encryptedKeyValue, и HotXLS го дешифрира с третия изведен ключ, като го съкращава до дължината на ключа, декларирана от keyData; Инициализиращият вектор (IV) за всеки сегмент е SHA-512 върху солта на keyData, съединена с 32-битовия малко-ендиан индекс на сегмента, съкратена до размера на блока; Тази конструкция означава, че всеки 4096-байтов сегмент може да се дешифрира независимо, което също така прави формата удобен за произволен достъп по принцип, въпреки че HotXLS дешифрира целия пакет в паметта и предава получените ZIP байтове на своя нормален XLSX четец

Декларираният размер на чистия текст върши последната част от работата; AES-CBC изходът е подравнен по блокове, така че последният сегмент носи до 15 байта допълване (padding), които не са част от документа; дешифрираният буфер се съкращава до префикса за размер и резултатът е точно този .xlsx ZIP, който Excel е шифровал; HotXLS проверява префикса спрямо действителната дължина на потока преди дешифриране, така че съкратено качване или променено поле за размер се проваля чисто, вместо да препълни буфера

Докладване на грешки и честни граници

Режимите на неуспех са умишлено разделени; Грешна парола предизвиква изключение с изрично съобщение за грешна парола, задвижвано от несъответствието във валидатора, така че потребителският интерфейс може да подкани потребителя да опита отново; CFB контейнер, чийто дескриптор декларира алгоритми извън поддържания набор (всичко различно от AES с CBC верига и SHA-512 хеширане в Agile дескриптор), или контейнер, който не е нито Standard, нито 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, което обхваща това, което Excel 2010 до Excel 365 действително записват, и в трите размера на ключа; Дескриптори, деклариращи други шифри или хеш алгоритми, се отхвърлят, а базираните на сертификати шифратори на ключове не се консултират, а само този за парола; От страна на записването HotXLS в момента генерира Standard Encryption, а не Agile — разлика, която има значение, ако следващите инструменти проверяват схемата; подробностите са в статията за писане на защитен с AES XLSX изход

Защитените с парола качвания престават да бъдат специален случай, след като четецът третира шифроването като част от файловия формат, а не като изключение от него; Входната точка OpenEncrypted, SHA-512 извеждането на ключове с броя завъртания и сегментираният AES-CBC конвейер, описани тук, се доставят като част от HotXLS Delphi Excel Component, заедно с останалата част от неговия собствен двигател за четене и писане на XLS и XLSX за Delphi и C++Builder