Technical Article

Zašto Excel odbija vašu šifrovanu radnu svesku: ECB i RC4

Napišete radnu svesku, šifrujete je lozinkom, predate datoteku kolegi, i kolega je otvori u Excel-u. Excel traži lozinku. Kolega je ukuca, i Excel je prihvati. Za sada šifrovanje izgleda ispravno. Zatim Excel prikaže dijalog koji kaže da je datoteka oštećena i da se ne može otvoriti, ili se otvori sa listom besmislenih ćelija. Lozinka je bila ispravna. Datoteka je ipak pokvarena. Ovo je najzbunjujući način otkazivanja u Office šifrovanju, jer su deo koji vam govori da je lozinka ispravna i deo koji drži vaše podatke zaštićeni dvema različitim operacijama, i ispravnost jedne ne garantuje ispravnost druge.

Oba baga opisana ovde imala su upravo ovaj oblik. U svakom slučaju, verifikator je prošao, a telo dokumenta nije, što vas navodi da tražite bag u lozinki ili izvođenju ključa koji zapravo ne postoji. Stvarna greška je bila nizvodno, u načinu na koji su bajtovi paketa transformisani. Dve greške su nezavisne, jedna na AES putanji i jedna na RC4 putanji, ali dele isti problem dijagnoze, pa vredi videti zašto je polovično tačan rezultat najteža vrsta za analizu.

Zašto prolazak lozinke ne dokazuje ništa o telu dokumenta

Format koji koristi moderni šifrovani XLSX je ECMA-376 Standard Encryption, i on čuva dve šifrovane stvari jednu pored druge. Jedna je EncryptionVerifier: mali blok koji drži nasumičnu vrednost i heš te vrednosti, šifrovan ključem izvedenim iz lozinke. Druga je EncryptedPackage: ceo zip kontejner radne sveske, šifrovan istim ključem. Verifikator postoji kako bi čitač mogao da potvrdi lozinku pre nego što potroši resurse na megabajte tela dokumenta. Dešifrujte verifikator, heširajte nasumičnu vrednost, uporedite je sa sačuvanim hešom, i ako se poklapaju, lozinka je ispravna.

Zamka je u tome što se verifikator i paket šifruju odvojenim pozivima preko odvojenih bafera. Ključ koji je ispravno izveden dešifrovaće verifikator ispravno bez obzira na to šta se posle desi sa paketom. Druga stvar, ako je vaše izvođenje ključa ispravno ali je transformacija paketa pogrešna, Excel potvrđuje lozinku iz verifikatora a zatim pada na telu dokumenta. Simptom se čita kao "ispravna lozinka, oštećena datoteka", što usmerava istragu na putanju lozinke, koja je jedini deo koji nikada nije bio pokvaren. Isti princip razdvajanja važi i za stariji RC4 slučaj: heš verifikatora se proverava prvi, a telo koje skrene sa sinhronizacije i dalje ostavlja tu proveru netaknutom.

Bag jedan: AES u ECB, a ne CBC

[MS-OFFCRYPTO] §2.3.4.15 precizira da Standard Encryption šifruje paket pomoću AES-a u Electronic Codebook režimu. Svaki 16-bajtni blok popunjenog paketa šifruje se nezavisno istim ključem. Nema ulančavanja između blokova i nema inicijalizacijskog vektora. Ovo je neobičan izbor po modernim standardima, gde se ECB obično izbegava, ali kompatibilnost (interop) nije mesto za preispitivanje specifikacije. Excel dešifruje paket kao ECB, tako da ga proizvođač mora šifrovati kao ECB ili se njih dvoje neće složiti.

Bag je bio u tome što je paket bio šifrovan pomoću AES-a u CBC režimu koristeći inicijalizacioni vektor koji se sastojao isključivo od nula. Evo zašto to skoro radi, i zašto je to "skoro" najgore mesto na kom možete da se nađete. U CBC režimu, prvi blok čistog teksta se prolazi kroz XOR sa IV pre šifrovanja. Kada se IV sastoji od nula, taj XOR ne menja ništa, tako da prvi blok CBC-a sa nula-IV daje potpuno isti šifrovani tekst kao i ECB. Od drugog bloka nadalje, CBC ubacuje prethodni blok šifrovanog teksta u sledeći, tako da svaka blok nakon prvog odstupa od ECB-a.

Sada to preklopite na strukturu. Izgled paketa postavlja 8-bajtni little-endian prefiks dužine na sam početak, tako da delovi datoteke koje Excel najranije proverava leže u prvom bloku ili dva. Prvi blok koji se slučajno poklapa znači da najranija validacija prolazi, dok se svaki kasniji blok dešifruje u šum. Popravka je jednostavna kada se imenuje režim: šifrujte svaki 16-bajtni blok pomoću ECB i zaustavite ulančavanje. U endžinu, XlsEncryptStdPackage prolazi kroz popunjeni bafer u koracima od 16 bajtova i poziva AESEncryptECB128Block na svakom od njih, što je ista primitiva koja se već koristi za blokove verifikatora. Izvor nosi komentar u petlji koji jasno navodi pravilo: CBC sa nula IV se poklapa sa ECB samo za prvi blok, pa bi se ostatak paketa dešifrovao u smeće i Excel bi ga odbacio.

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;

Bag dva: RC4 re-key odstupanje izlazi iz koraka

Nasleđena .xls putanja koristi RC4 CryptoAPI šemu, i njeno pravilo je drugačije vrste. [MS-OFFCRYPTO] §2.3.6 propisuje da se šifra ponovo ključuje (re-keyed) na svakoj granici bloka od 1024 bajta. Tok je podeljen na blokove od 1024 bajta, novi RC4 ključ se izvodi za blok broj 0, 1, 2, i tako dalje, i unutar svakog bloka tok ključa (keystream) se troši neprekidno od bajta do bajta. Dve invarijante moraju da važe zajedno: ponovno ključovanje na svakoj granici, i trošenje toka ključa bez praznina unutar bloka. RC4 je protočna šifra (stream cipher), tako da je njen tok ključa jedan uređeni niz; n-ti bajt koji povučete određen je time koliko ste bajtova povukli pre njega. Dešifrovanje je isti XOR sa istim nizom, što znači da proizvođač i potrošač moraju da povuku tačno iste bajtove na tačno istim pozicijama.

To je čitava poteškoća. Protočna šifra nema resinhronizaciju. Ako protraćite jedan bajt toka ključa, svaki bajt nakon njega prolazi kroz XOR sa pogrešnim bajtom toka ključa, i greška se nikada sama ne ispravlja; ona se kaskadno širi do kraja bloka i, kada je trenutna pozicija pogrešna, na svaki blok nakon njega. Bag ovde je uradio upravo to. Brojač blokova je počeo od sentinel vrednosti minus jedan, a rutina preskakanja je pretpostavila da se brojač već poklapa sa trenutnim blokom. Počevši od tog sentinela, ona je ponovo ključovala i pokrenula ceo blok od 1024 bajta toka ključa koji nikada nije trebao biti potrošen, i u tom procesu je preostali broj odvela u negativan opseg. Od te tačke, dekriptor je bio ceo blok van faze. Verifikator, proveren pre svega ovoga, i dalje je prolavao, pa je lozinka izgledala ispravno dok je svaka ćelija sa podacima ispadala kao smeće.

Ispravljena logika živi u TXLSDecrypterRC4. I Skip i Decrypt dele jednu petlju: ponovno ključovanje vrši se samo kada trenutna pozicija pređe u novi blok, gde je indeks bloka pozicija podeljena sa REKEY_BLOCK_SIZE (1024), a zatim troši do ostatka trenutnog bloka i ne više. MakeKey se poziva sa indeksom bloka, nikada sa zastarelim ili sentinel indeksom, a pozicija napreduje za tačan broj obrađenih bajtova tako da Skip i Decrypt ostaju fazno poravnati sa proizvođačem. Lekcija se nalazi u najmanjoj jedinici: jedan protraćeni bajt nije mala greška u protočnoj šifri, to je potpuni gubitak svega nizvodno.

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;

Kompatibilnost sa zamrznutom specifikacijom je poklapanje u bajt

Oba baga se svode na isti osnovni princip, i to vredi navesti zasebno jer menja način na koji merite dizajnerske izbore. Kada je potrošač vašeg izlaza fiksni eksterni program koji ne možete da promenite, režim šifre i ritam ponovnog ključovanja nisu detalji implementacije koje možete da optimizujete ili pojednostavite. Oni su deo mrežnog ugovora. Excel će dešifrovati pomoću ECB-a i ponovo ključovati na granicama od 1024 bajta bez obzira na to da li vam se ti izbori sviđaju, a vaš jedini posao je da proizvedete bajtove koji se dešifruju u original pod tom tačnom procedurom. Režim koji je moderniji, IV koji izgleda bezopasno, brojač koji počinje tamo gde deluje prirodno; bilo šta od ovoga je defekt onog trenutka kada odstupi od onoga što čitač očekuje. Kompatibilnost sa zamrznutom specifikacijom nije približna. Ona je bajt-tačna ili je pokvarena.

To je takođe razlog zašto je verifikator sam po sebi slab test (smoke test). On vam govori da izvođenje ključa radi, što je neophodno ali daleko od dovoljnog. Test koji samo otvara šifrovanu datoteku i potvrđuje lozinku izvestiće o uspehu dok je telo dokumenta nečitljivo. Pravi test dešifruje paket i poredi oporavljene bajtove sa originalnim ulazom, ili vrši round-trip radne sveske kroz šifrovanje i dešifrovanje i čita ćelije nazad. Verifikator dokazuje lozinku; samo telo dokazuje šifrovanje.

Podržani način za čitanje i pisanje zaštićenih radnih svezaka

Javna površina je mala. Da biste upisali lozinkom zaštićenu modernu radnu svesku, popunite ili otvorite TXLSXWorkbook i pozovite SaveAsEncrypted sa nazivom datoteke i lozinkom; to serijalizuje radnu svesku i pokreće Standard Encryption cevovod koji je prva popravka ispravila, vraćajući 1 u slučaju uspeha. Za čitanje, pozovite CanReadEncrypted da biste testirali da li je datoteka šifrovani kontejner Compound File formata, a zatim se granajte: OpenEncrypted rukuje šifrovanom putanjom i vraća se na Open za obične datoteke, a Open sa lozinkom je dostupan direktno. Rukovanje režimom i petlja ponovnog ključovanja opisani gore nalaze se ispod ovih poziva; vi dajete lozinku i naziv datoteke, a endžin usklađuje specifikaciju u vaše ime.

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;

Oblik zaštićenog izlaza, tok EncryptionInfo, blokovi verifikatora i raspored paketa pokriveni su u našem vodiču kroz XLSX izlaz zaštićen AES-om. Za posebno pitanje zaključavanja na nivou lista i načina na koji zaštita utiče na podešavanje stranice i štampanje, pogledajte članak o zaštiti, podešavanju stranice i štampanju. Oba se grade na putanji šifrovanja opisanoj ovde, koja se isporučuje kao deo HotXLS komponente za tabele za Delphi i C++Builder zajedno sa API-jima za čitanje, pisanje i renderovanje koji su pokriveni na drugim mestima na ovom blogu.