Odborný článok

Prečo Excel odmieta váš zašifrovaný zošit: ECB a RC4

Zapíšete zošit, zašifrujete ho heslom, odovzdáte súbor kolegovi a ten ho otvorí v Exceli. Excel si vypýta heslo. Kolega ho zadá a Excel ho prijme. Zatiaľ to vyzerá, že šifrovanie funguje správne. Potom však Excel zobrazí dialógové okno, ktoré hovorí, že súbor je poškodený a nedá sa otvoriť, alebo ho otvorí a zobrazí len hárok bezvýznamných buniek. Heslo bolo správne. Súbor je napriek tomu poškodený. Toto je ten najviac mätúci chybový stav pri šifrovaní balíka Office, pretože časť, ktorá vám oznamuje správnosť hesla, a časť, ktorá uchováva vaše dáta, sú chránené dvoma rôznymi operáciami a to, že správne funguje jedna z nich, nezaručuje správnosť tej druhej

Obe tu popísané chyby mali presne tento charakter. V oboch prípadoch verifikátor prešiel, ale telo nie, čo vás núti hľadať chybu v hesle alebo odvodzovaní kľúča, ktorá tam však nie je. Skutočná chyba bola ďalej v linke, v spôsobe transformácie bajtov balíka. Tieto dve chyby sú nezávislé, jedna v ceste AES a druhá v ceste RC4, ale zdieľajú rovnaký problém s diagnostikou, takže stojí za to pozrieť sa na to, prečo je napoly správny výsledok tým najťažšie čitateľným

Prečo prechádzajúce heslo nedokazuje nič o tele dokumentu

Formát, ktorý používa moderný zašifrovaný súbor XLSX, je Standard Encryption ECMA-376 a ukladá dve zašifrované veci vedľa seba. Jednou je EncryptionVerifier: malý blok obsahujúci náhodnú hodnotu a hash tejto hodnoty, zašifrovaný kľúčom odvodeným z hesla. Druhou je EncryptedPackage: celý ZIP kontajner zošita, zašifrovaný rovnakým kľúčom. Verifikátor existuje preto, aby čítačka mohla overiť heslo predtým, ako vynaloží úsilie na spracovanie megabajtov tela. Dešifrujte verifikátor, hashuje náhodnú hodnotu, porovnajte ju s uloženým hashom a ak sa zhodujú, heslo je správne

Pasca spočíva v tom, že verifikátor a balík sú zašifrované samostatnými volaniami nad samostatnými buffermi. Kľúč, ktorý je odvodený správne, dešifruje verifikátor správne bez ohľadu na to, čo sa potom stane s balíkom. Takže ak je odvodenie vášho kľúča správne, ale transformácia balíka nesprávna, Excel potvrdí heslo z verifikátora a potom zlyhá na tele. Príznak sa javí ako „správne heslo, poškodený súbor“, čo zameriava vyšetrovanie na cestu hesla, ktorá je tou jedinou časťou, ktorá nikdy nebola pokazená. Rovnaké oddelenie platí aj pre starší prípad RC4: najprv sa skontroluje hash verifikátora a telo, ktoré sa dostane mimo synchronizácie, stále ponecháva túto kontrolu nedotknutú

Chyba č. 1: AES v režime ECB, nie CBC

Špecifikácia [MS-OFFCRYPTO] §2.3.4.15 stanovuje, že Standard Encryption šifruje balík pomocou AES v režime Electronic Codebook (ECB). Každý 16-bajtový blok doplneného balíka je zašifrovaný nezávisle rovnakým kľúčom. Medzi blokmi neexistuje žiadne zreťazenie a neexistuje ani inicializačný vektor. Podľa moderných štandardov, kde sa ECB zvyčajne nepoužíva, je to nezvyčajná voľba, ale interoperabilita nie je miestom na spochybňovanie špecifikácie. Excel dešifruje balík ako ECB, takže tvorca ho musí zašifrovať ako ECB, inak sa nezhodnú

Chyba spočívala v tom, že balík bol zašifrovaný pomocou AES v režime CBC s inicializačným vektorom (IV) zloženým zo samých núl. Tu je dôvod, prečo to takmer funguje a prečo je „takmer“ tým najhorším miestom na pristátie. V režime CBC sa prvý blok čistého textu pred šifrovaním zlúči pomocou XOR s IV. Keď je IV nulový, tento XOR nič nezmení, takže prvý blok CBC s nulovým IV vytvorí presne rovnaký šifrovaný text ako ECB. Od druhého bloku ďalej CBC prenáša predchádzajúci blok šifrovaného textu do nasledujúceho, takže každý blok po prvom sa líši od ECB

Teraz to aplikujme na štruktúru súboru. Rozloženie balíka umiestňuje 8-bajtový dĺžkový prefix little-endian na úplný začiatok, takže časti súboru, ktoré Excel kontroluje najskôr, sa nachádzajú v prvom alebo druhom bloku. Zhoda prvého bloku znamená, že najskoršia validácia prebehne úspešne, zatiaľ čo každý ďalší blok sa dešifruje na šum. Riešenie po pomenovaní režimu nie je zložité: zašifrujte každý 16-bajtový blok pomocou ECB a zastavte zreťazenie. V jadre metóda XlsEncryptStdPackage prechádza doplnený buffer po 16-bajtových krokoch a na každom z nich volá AESEncryptECB128Block, čo je rovnaké primitívum, aké sa už používa pre bloky verifikátora. Zdrojový kód obsahuje pri cykle komentár, ktorý toto pravidlo jasne vysvetľuje: CBC s nulovým IV sa zhoduje s ECB iba pre prvý blok, so the rest of the package would decrypt to garbage and Excel would reject it

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;

Chyba č. 2: Zmena kľúča RC4 sa dostáva mimo synchronizácie

Staršia cesta súborov .xls používa schému RC4 CryptoAPI a jej pravidlo má iný charakter. Špecifikácia [MS-OFFCRYPTO] §2.3.6 určuje, že šifra mení kľúč na každej hranici 1024-bajtového bloku. Tok je rozdelený na bloky s veľkosťou 1024 bajtov, pre blok číslo 0, 1, 2 atď. sa odvodí nový kľúč RC4 a vo vnútri každého bloku sa prúd kľúča (keystream) spotrebováva nepretržite od bajtu k bajtu. Musia spoločne platiť dva invarianty: zmeniť kľúč na každej hranici a spotrebovávať prúd kľúča bez medzier vo vnútri bloku. RC4 je prúdová šifra, takže jej keystream je jediná usporiadaná sekvencia; n-tý bajt, ktorý načítate, je určený tým, koľko bajtov ste z neho načítali predtým. Dešifrovanie je rovnaký XOR voči rovnakej sekvencii, čo znamená, že odosielateľ aj príjemca musia načítať presne tie isté bajty na presne rovnakých pozíciách

V tom spočíva celá obtiažnosť. Prúdová šifra nemá žiadnu resynchronizáciu. Ak premrháte čo i len jeden bajt prúdu kľúča, každý ďalší bajt sa zlúči pomocou XOR s nesprávnym bajtom keystreamu a chyba sa už nikdy sama neopraví; šíri sa kaskádovito až do konca bloku a akonáhle je aktuálna pozícia nesprávna, tak aj do každého ďalšieho bloku. Chyba v tomto prípade urobila presne to. Počítadlo blokov začínalo od strážnej hodnoty (sentinel) mínus jedna a vynechávacia (skip) rutina predpokladala, že počítadlo už zodpovedá aktuálnemu bloku. Počnúc týmto sentinelom zmenila kľúč a spustila plný 1024-bajtový blok keystreamu, ktorý sa nikdy nemal spotrebovať, a v procese posunula zostávajúci počet do záporných hodnôt. Od tohto momentu bol dešifrovač o celý blok mimo fázy. Verifikátor kontrolovaný pred týmto všetkým stále prešiel, takže heslo vyzeralo ako správne, zatiaľ čo každá dátová bunka sa vyhodnotila ako odpad

Opravená logika sa nachádza v triede TXLSDecrypterRC4. Metódy Skip aj Decrypt zdieľajú jeden cyklus: zmeniť kľúč iba vtedy, keď aktuálna pozícia prejde do nového bloku, kde index bloku je pozícia vydelená hodnotou REKEY_BLOCK_SIZE (1024), potom spotrebovať prúd do konca aktuálneho bloku a nie viac. Metóda MakeKey sa volá s indexom bloku, nikdy nie so zastaraným alebo sentinel indexom, a pozícia sa posúva o presný počet spracovaných bajtov, takže Skip a Decrypt zostávajú fázovo zosúladené s tvorcom. Ponaučenie sa skrýva v najmenšej jednotke: jediný premrhaný bajt nie je malou chybou v prúdovej šifre, je to úplná strata všetkého, čo nasleduje za ním

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;

Interoperabilita so zmrazenou špecifikáciou vyžaduje presnosť na bajt

Obe chyby sa redukujú na rovnaký základný princíp, ktorý stojí za to uviesť samostatne, pretože mení spôsob, akým posudzujete rozhodnutia pri návrhu. Keď je príjemcom vášho výstupu pevný externý program, ktorý nemôžete zmeniť, režim šifry a frekvencia zmeny kľúča nie sú implementačnými detailmi, ktoré môžete optimalizovať alebo zjednodušovať. Sú súčasťou kontraktu. Excel bude dešifrovať pomocou ECB a meniť kľúč na hraniciach 1024 bajtov bez ohľadu na to, či sa vám tieto voľby páčia alebo nie, a vašou jedinou úlohou je vytvoriť bajty, ktoré sa pri tomto presnom postupe dešifrujú na originál. Režim, ktorý je modernejší, IV, ktorý sa zdá byť neškodný, počítadlo, ktoré začína tam, kde sa to zdá prirodzené — čokoľvek z toho je chybou v okamihu, keď sa odkloní od toho, čo čítačka očakáva. Interoperabilita voči zmrazenej špecifikácii nie je približná. Je presná na bajt, alebo je nefunkčná

Aj preto je samotný verifikátor slabým dymovým testom. Hovorí vám len to, že odvodie kľúča funguje, čo je nevyhnutné, ale zďaleka nie dostatočné. Test, ktorý iba otvorí zašifrovaný súbor a overí správnosť hesla, nahlási úspech, hoci telo je nečitateľné. Skutočný test dešifruje balík a porovná obnovené bajty s pôvodným vstupom, alebo prenesie zošit cez šifrovanie a dešifrovanie a prečíta bunky späť. Verifikátor dokazuje heslo; iba telo dokazuje samotné šifrovanie

Podporovaný spôsob čítania a zápisu chránených zošitov

Verejné rozhranie je malé. Ak chcete zapísať heslo chránený moderný zošit, naplňte alebo otvorte TXLSXWorkbook a zavolajte SaveAsEncrypted s názvom súboru a heslom; zoserializuje zošit a spustí linku Standard Encryption, ktorú opravila prvá oprava, a pri úspechu vráti 1. Pri čítaní zavolajte CanReadEncrypted na testovanie, či je súbor zašifrovaným kontajnerom Compound File, a potom sa rozvetvite: OpenEncrypted spracováva šifrovanú cestu a vracia sa k bežnej Open pre obyčajné súbory, a metóda Open s heslom je k dispozícii priamo. Spracovanie režimu a slučka výmeny kľúča popísané vyššie sa nachádzajú pod týmito volaniami; vy dodáte heslo a názov súboru a jadro splní špecifikáciu za vás

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;

Podoba chráneného výstupu, tok EncryptionInfo, bloky verifikátora a rozloženie balíka sú popísané v našom sprievodcovi výstupom XLSX chráneným AES. Pre samostatnú otázku zamykania na úrovni hárkov a vplyvu ochrany na nastavenie stránky a tlač si pozrite článok o ochrane, nastavení stránky a tlači. Obe riešenia stavajú na tu opísanej ceste šifrovania, ktorá sa dodáva ako súčasť tabuľkového komponentu HotXLS pre Delphi a C++Builder spolu s API pre čítanie, zápis a vykresľovanie popísanými inde na tomto blogu