Technical Article

Zakaj Excel zavrne vaš šifrirani delovni zvezek: ECB in RC4

Zapišete delovni zvezek, ga šifrirate z geslom, predate datoteko sodelavcu in sodelavec jo odpre v Excelu. Excel zahteva geslo. Sodelavec ga vpiše in Excel ga sprejme. Do te točke je šifriranje videti pravilno. Nato Excel prikaže pogovorno okno, ki pravi, da je datoteka poškodovana in je ni mogoče odpreti, ali pa se odpre v tabelo z nesmiselnimi celicami. Geslo je bilo pravilno. Datoteka pa je kljub temu pokvarjena. To je najboj zavajajoč način neuspeha pri pisarniškem šifriranju, saj sta del, ki vam pove, da je geslo pravilno, in del, ki vsebuje vaše podatke, zaščitena z dvema različnima operacijama, pravilnost ene pa ne zagotavlja pravilnosti druge.

Obe napaki, opisani tukaj, sta imeli natanko to obliko. V vsakem primeru je validator uspel, telo pa ne, kar vas pošlje iskat napako gesla ali izpeljave ključa, ki je sploh ni. Dejanska napaka je bila v nadaljevanju, v tem, kako so bili bajti paketa preoblikovani. Ti dve napaki sta neodvisni, ena na poti AES in druga na poti RC4, vendar si delita težavo z diagnozo, zato je vredno preučiti, zakaj je napol pravilen rezultat najtežji za branje.

Zakaj uspešno preverjanje gesla ne dokazuje ničesar o telesu

Format, ki ga uporablja sodobno šifriran XLSX, je standardno šifriranje ECMA-376 (Standard Encryption) in shranjuje dve šifrirani stvari eno poleg druge. Ena je EncryptionVerifier: majhen blok z naključno vrednostjo in zgoščeno vrednostjo (hash) te vrednosti, šifrirano s ključem, izpeljanim iz gesla. Druga je EncryptedPackage: celoten vsebnik zip delovnega zvezka, šifriran z istim ključem. Verifikator obstaja zato, da lahko bralnik potrdi geslo, preden porabi trud za megabajte telesa. Dešifrirajte verifikator, zgoščite naključno vrednost, jo primerjajte s shranjeno zgoščeno vrednostjo in če se ujemata, je geslo pravilno.

Past je v tem, da sta verifikator in paket šifrirana z ločenimi klici čez ločene medpomnilnike. Ključ, ki je pravilno izpeljan, bo pravilno dešifriral verifikator ne glede na to, kaj se pozneje zgodi s paketom. Torej, če je vaša izpeljava ključa pravilna, pretvorba paketa pa napačna, Excel potrdi geslo iz verifikatorja in nato spodleti pri telesu. Simptom se bere kot "pravilno geslo, pokvarjena datoteka", kar usmeri preiskavo na pot gesla, to pa je tisti edini del, ki sploh ni bil pokvarjen. Enaka ločitev velja za dedni primer RC4: zgoščena vrednost verifikatorja se preveri najprej, telo, ki se zamakne iz faze, pa še vedno pusti to preverjanje nedotaknjeno.

Napaka ena: AES v načinu ECB, ne CBC

Specifikacija [MS-OFFCRYPTO] §2.3.4.15 določa, da standardno šifriranje šifrira paket z AES v načinu Electronic Codebook (ECB). Vsak 16-bajtni blok oblazinjenega paketa je šifriran neodvisno z istim ključem. Med bloki ni veriženja in ni inicializacijskega vektorja. To je nenavadna izbira po sodobnih standardih, kjer se ECB običajno izogibamo, vendar interop ni mesto za ugibanje specifikacije. Excel dešifrira paket kot ECB, zato ga mora pisec šifrirati kot ECB, sicer se ne bosta ujemala.

Hrošč je bil v tem, da je bil paket šifriran z AES v načinu CBC z uporabo povsem ničelnega inicializacijskega vektorja. Tukaj je razlog, zakaj to skoraj deluje in zakaj je "skoraj" najslabše mesto za pristanek. V načinu CBC se prvi blok nešifriranega besedila XOR-ira z IV pred šifriranjem. Ko je IV povsem ničeln, ta XOR ne spremeni ničesar, zato prvi blok CBC z ničelnim IV ustvari natanko enako šifrirano besedilo kot ECB. Od drugega bloka naprej pa CBC prenaša prejšnji šifrirani blok v naslednjega, zato se vsak blok po prvem razlikuje od ECB.

Zdaj pa to postavite čez strukturo. Postavitev paketa postavi 8-bajtno predpono dolžine v obliki little-endian na sam začetek, zato se deli datoteke, ki jih Excel preveri najprej, nahajajo v prvem ali drugem bloku. Prvi blok, ki se po naključju ujema, pomeni, da prva potrditev uspe, medtem ko se vsak kasnejši blok dešifrira v šum. Popravek ni subtilen, ko enkrat poimenujemo način: šifrirajte vsak 16-bajtni blok z ECB in prekinite veriženje. V pogonu XlsEncryptStdPackage prehodi oblazinjen medpomnilnik v 16-bajtnih korakih in pokliče AESEncryptECB128Block na vsakem od njih, kar je ista primitiva, ki se že uporablja za bloke verifikatorja. Vir vsebuje opombo pri zanki, ki jasno določa pravilo: CBC z ničelnim IV se ujema z ECB le za prvi blok, preostanek paketa pa bi se dešifriral v smeti in Excel bi ga zavrnil.

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;

Napaka dve: ponovni ključ RC4 se zamakne iz faze

Dedna pot .xls uporablja shemo RC4 CryptoAPI, njeno pravilo pa je drugačne vrste. Specifikacija [MS-OFFCRYPTO] §2.3.6 določa, da se šifra ponovno ključi na vsaki meji 1024-bajtnega bloka. Tok je razdeljen na bloke po 1024 bajtov, nov ključ RC4 se izpelje za bloke številka 0, 1, 2 in tako naprej, znotraj vsakega bloka pa se tok ključev (keystream) porablja neprekinjeno od bajta do bajta. Dve invarianti se morata ujemati skupaj: ponovni ključ na vsaki meji in poraba toka ključev brez vrzeli znotraj bloka. RC4 je tokovna šifra, zato je njen tok ključev eno urejeno zaporedje; n-ti bajt, ki ga vzamete, določa, koliko bajtov ste vzeli pred njim. Dešifriranje je isti XOR z istim zaporedjem, kar pomeni, da morata pisec in bralnik vzeti natanko enake bajte na natanko enakih položajih.

V tem je celotna težava. Tokovna šifra nima ponovne sinhronizacije. Če izgubite en bajt toka ključev, se vsak bajt za njim XOR-ira z napačnim bajtom toka ključev in napaka se nikoli ne popravi sama; razširi se do konca bloka in, ko je trenutni položaj napačen, na vsak blok za njim. Hrošč tukaj je naredil natanko to. Števec blokov se je začel pri varovalni vrednosti minus ena, rutina preskoka pa je predvidevala, da se števec že ujema s trenutnim blokom. Od te varovalne vrednosti je ponovno ključil in zagnal celoten 1024-bajtni blok toka ključev, ki ne bi smel biti nikoli porabljen, s tem pa je preostali števec potisnil v negativne številke. Od te točke je bil dešifrirnik celoten blok izven faze. Verifikator, preverjen pred vsem tem, je še vedno uspel, zato je geslo delovalo pravilno, medtem ko je vsaka podatkovna celica postala šum.

Popravljena logika živi v TXLSDecrypterRC4. Obe metodi Skip in Decrypt si delita eno zanko: ponovni ključ le takrat, ko trenutni položaj prečka nov blok, kjer je indeks bloka položaj, deljen z REKEY_BLOCK_SIZE (1024), nato pa porabi do preostanka trenutnega bloka in nič več. Metoda MakeKey se pokliče z indeksom bloka, nikoli z zastarelim ali varovalnim indeksom, položaj pa napreduje za natančno število obdelanih bajtov, tako da Skip in Decrypt ostaneta fazno poravnana s piscem. Lekcija živi v najmanjši enoti: en izgubljen bajt v tokovni šifri ni majhna napaka, temveč popolna izguba vsega v nadaljevanju.

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;

Interop z zamrznjeno specifikacijo pomeni ujemanje do bajta natančno

Obe napaki se zmanjšata na isto temeljno načelo, ki ga je vredno navesti posebej, saj spreminja tehtanje odločitev pri načrtovanju. Ko je porabnik vašega izhoda fiksni zunanji program, ki ga ne morete spremeniti, način šifre in kadenca ponovnega ključa nista podrobnosti implementacije, ki bi jih lahko optimizirali ali poenostavili. Sta del pogodbe o prenosu. Excel bo dešifriral z ECB in ponovno ključil na 1024-bajtnih mejah, ne glede na to, ali vam te izbire ugajajo ali ne, vaša edina naloga pa je ustvariti bajte, ki se pod tem natančnim postopkom dešifrirajo v izvirnik. Način, ki je sodobnejši, IV, ki se zdi neškodljiv, števec, ki se začne tam, kjer se zdi naravno; vse to postane napaka v trenutku, ko se razlikuje od tistega, kar bralnik pričakuje. Interop proti zamrznjeni specifikaciji ni približen. Je bajtno natančen ali pa je pokvarjen.

Zato je verifikator slab test sam po sebi. Pove vam le, da izpeljava ključa deluje, kar je nujno, a še daleč ne dovolj. Test, ki le odpre šifrirano datoteko in potrdi, da geslo deluje, bo poročal o uspehu, medtem ko bo telo nečitljivo. Resničen test dešifrira paket in primerja obnovljene bajte z izvirnim vhodom ali pa krožno preveri delovni zvezek skozi šifriranje in dešifriranje ter prebere celice nazaj. Verifikator dokazuje geslo; le telo dokazuje šifriranje.

Podprt način za branje in pisanje zaščitenih delovnih zvezkov

Javna površina je majhna. Če želite zapisati zaščiten sodoben delovni zvezek, napolnite ali odprite TXLSXWorkbook in pokličite SaveAsEncrypted z imenom datoteke in geslom; ta serializira delovni zvezek in zažene standardni šifrirni cevovod ECMA-376, ki ga je prvi popravek popravil, ter vrne 1 ob uspehu. Za branje pokličite CanReadEncrypted, da preizkusite, ali je datoteka šifriran vsebnik Compound File, nato pa se usmerite: OpenEncrypted obravnava šifrirano pot in se vrne k Open za navadne datoteke, Open z geslom pa je na voljo neposredno. Upravljanje načina in zanka ponovnega ključa, opisana zgoraj, se nahajata pod temi klici; vi priskrbite geslo in ime datoteke, pogon pa se uskladi s specifikacijo v vašem imenu.

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;

Oblika zaščitenega izhoda, tok EncryptionInfo, bloki verifikatorja in postavitev paketa so obravnavani v našem vodniku o izhodu XLSX, zaščitenem z AES. Za ločeno vprašanje zaklepanja na ravni lista in kako zaščita vpliva na postavitev strani in tiskanje, si oglejte članek o zaščiti, nastavitvi strani in tiskanju. Oboje gradi na poti šifriranja, opisani tukaj, ki se dostavlja kot del komponente HotXLS spreadsheet component za Delphi in C++Builder, skupaj z vmesniki API za branje, pisanje in upodabljanje, ki so obravnavani drugje na tem blogu.