Tehnički članak

Zašto Excel odbija vašu šifriranu radnu knjigu: ECB i RC4

Napišete radnu knjigu, šifrirate je lozinkom, predate datoteku kolegi i kolega je otvori u Excelu. Excel traži lozinku. Kolega je utipka i Excel je prihvati. Za sada šifriranje izgleda točno. Zanim Excel prikazuje dijaloški okvir u kojem piše da je datoteka oštećena i da se ne može otvoriti ili se otvori list s besmislenim ćelijama. Lozinka je bila točna, a datoteka je ipak neupotrebljiva. Ovo je najzbunjujući način kvara u Office šifriranju, jer su dio koji vam govori da je lozinka točna i dio koji drži vaše podatke zaštićeni dvjema različitim operacijama, pa ispravnost jedne ne jamči ispravnost druge

Obje pogreške opisane ovdje imale su točno taj oblik. U svakom slučaju verifikator je prošao, a tijelo nije, što vas šalje u potragu za pogreškom u lozinki ili izvođenju ključa (key-derivation) koje zapravo nema. Stvarna pogreška bila je nizvodno, u načinu na koji su transformirani bajtovi paketa. Dvije su pogreške neovisne, jedna na AES putu i jedna na RC4 putu, ali dijele problem dijagnoze, pa vrijedi vidjeti zašto je napola točan rezultat najteže protumačiti

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

Format koji koristi moderni šifrirani XLSX je ECMA-376 Standardno šifriranje i sprema dvije šifrirane stvari jednu pored druge. Jedna je EncryptionVerifier: mali blok koji drži nasumičnu vrijednost i sažetak te vrijednosti, šifriran ključem izvedenim iz lozinke. Druga je EncryptedPackage: cijeli ZIP spremnik radne knjige, šifriran istim ključem. Verifikator postoji kako bi čitač mogao potvrditi lozinku prije nego što potroši resurse na megabajte tijela datoteke. Dešifrirajte verifikator, sažmite nasumičnu vrijednost, usporedite je s pohranjenim sažetkom i ako se podudaraju, lozinka je točna

Zamka je u tome što se verifikator i paket šifriraju zasebnim pozivima preko zasebnih međuspremnika. Ključ koji je ispravno izveden dešifrirat će verifikator ispravno bez obzira na to što se s paketom dogodi nakon toga. Dakle, ako je vaše izvođenje ključa ispravno, ali je transformacija paketa pogrešna, Excel potvrđuje lozinku iz verifikatora, a zatim zakazuje na tijelu. Simptom se očituje kao "točna lozinka, neupotrebljiva datoteka", što istragu usmjerava na stazu lozinke, koja je zapravo jedini dio koji nikada nije bio pokvaren. Isti princip razdvajanja upravlja i naslijeđenim RC4 slučajem: najprije se provjerava sažetak verifikatora, a tijelo koje ispadne iz sinkronizacije i dalje ostavlja tu provjeru netaknutom

Prva pogreška: AES u ECB-u, a ne u CBC-u

Specifikacija [MS-OFFCRYPTO] §2.3.4.15 određuje da Standardno šifriranje šifrira paket pomoću AES-a u načinu rada Electronic Codebook (ECB). Svaki 16-bajtni blok popunjenog paketa šifrira se neovisno istim ključem. Nema ulančavanja između blokova i nema inicijalizacijskog vektora. To je neobičan izbor prema modernim standardima, gdje se ECB obično izbjegava, ali interoperabilnost nije mjesto za preispitivanje specifikacija. Excel dešifrira paket kao ECB, pa ga proizvođač mora šifrirati kao ECB ili se to dvoje neće slagati

Pogreška je bila u tome što je paket bio šifriran pomoću AES-a u načinu rada CBC koristeći inicijalizacijski vektor koji se sastojao od samih nula. Evo zašto to zamalo radi, i zašto je to "zamalo" najgore mjesto na kojem možete završiti. U CBC-u, prvi blok čistog teksta podvrgava se XOR operaciji s IV-om prije šifriranja. Kada se IV sastoji od samih nula, taj XOR ne mijenja ništa, pa prvi blok CBC-a s nultim IV-om proizvodi točno isti šifrirani tekst kao ECB. Od drugog bloka nadalje, CBC uvodi prethodni šifrirani tekst u sljedeći blok, pa se svaki blok nakon prvog razlikuje od ECB-a

Sada to primijenite na strukturu. Raspored paketa postavlja 8-bajtni little-endian prefiks duljine na sam početak, pa dijelovi datoteke koje Excel najranije provjerava leže u prvom bloku ili dva. Prvi blok koji se podudara znači da najranija provjera prolazi, dok se svaki kasniji blok dešifrira u šum. Rješenje nije komplicirano kada se prepozna način rada: šifrirajte svaki 16-bajtni blok s ECB-om i prekinite ulančavanje. U mehanizmu, XlsEncryptStdPackage walks the padded buffer in 16-byte steps and calls AESEncryptECB128Block on each one, which is the same primitive already used for the verifier blocks. The source carries a comment at the loop that states the rule plainly: CBC with a zero IV only matches ECB for the first block, 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;

Druga pogreška: ponovno ključanje RC4 izlazi iz koraka

Naslijeđena .xls staza koristi shemu RC4 CryptoAPI, a njezino je pravilo po prirodi drugačije. Specifikacija [MS-OFFCRYPTO] §2.3.6 određuje da se šifra ponovno ključa na svakoj granici bloka od 1024 bajta. Tok je podijeljen na blokove od 1024 bajta, novi RC4 ključ se izvodi za blokove broj 0, 1, 2 i tako dalje, a unutar svakog bloka tok ključa (keystream) troši se neprekidno od bajta do bajta. Dvije invarijante moraju se održati zajedno: ponovno ključanje na svakoj granici i trošenje toka ključa bez praznina unutar bloka. RC4 je protočna šifra (stream cipher), pa je njezin tok ključa jedan uređeni niz; n-ti bajt koji povučete određen je time koliko ste bajtova povukli prije njega. Dešifriranje je ista XOR operacija protiv istog niza, što znači da proizvođač i potrošač moraju povući točno iste bajtove na točno istim pozicijama

U tome je cijela poteškoća. Protočna šifra nema resinkronizaciju. Ako izgubite ijedan bajt toka ključa, svaki bajt nakon njega podvrgava se XOR operaciji s pogrešnim bajtom toka ključa, i ta se pogreška nikada sama ne ispravlja; ona se kaskadno prenosi do kraja bloka i, jednom kada je trenutna pozicija pogrešna, na svaki blok nakon njega. Pogreška ovdje učinila je upravo to. Brojač blokova započeo je od sentinel vrijednosti minus jedan, a rutina preskakanja pretpostavila je da se brojač već podudara s trenutnim blokom. Počevši od tog sentinela, ponovno je ključao i pokrenuo puni blok toka ključa od 1024 bajta koji se nikada nije trebao potrošiti, a u tom procesu preostali broj je otišao u negativnu vrijednost. Od tog trenutka dešifrant je bio puni blok izvan faze. Verifikator, provjeren prije svega ovoga, i dalje je prolazio, pa je lozinka izgledala ispravno, dok su sve ćelije s podacima ispadale kao smeće

Ispravljena logika živi u TXLSDecrypterRC4. I Skip i Decrypt dijele jednu petlju: ponovno ključanje se odvija samo kada trenutna pozicija prijeđe u novi blok, gdje je indeks bloka pozicija podijeljena s REKEY_BLOCK_SIZE (1024), a zatim se troši samo do ostatka trenutnog bloka i ne više. MakeKey se poziva s indeksom bloka, nikada sa zastarjelim ili sentinel indeksom, a pozicija napreduje za točan broj obrađenih bajtova kako bi Skip i Decrypt ostali fazno usklađeni s proizvođačem. Pouka leži u najmanjoj jedinici: jedan izgubljeni bajt nije mala pogreš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;

Interoperabilnost sa zamrznutom specifikacijom je podudaranje u bajt

Obje se pogreške svode na isto temeljno načelo i vrijedi ga navesti samo za sebe jer mijenja način na koji procjenjujete odluke o dizajnu. Kada je potrošač vašeg izlaza fiksni vanjski program koji ne možete mijenjati, način rada šifre i ritam ponovnog ključanja nisu detalji implementacije koje možete optimizirati ili pojednostaviti. Oni su dio ugovora o prijenosu. Excel će dešifrirati pomoću ECB-a i ponovno ključati na granicama od 1024 bajta sviđali se vama ti izbori ili ne, a vaš je jedini posao proizvesti bajtove koji se pod tim točnim postupkom dešifriraju u izvornik. Moderniji način rada, IV koji izgleda bezopasan, brojač koji počinje tamo gdje se čini prirodnim; bilo što od toga je nedostatak onog trenutka kada odstupi od onoga što čitač očekuje. Interoperabilnost sa zamrznutom specifikacijom nije približna. Ona je točna u bajt ili je neupotrebljiva

To je također razlog zašto je verifikator sam po sebi loš brzinski test (smoke test). On vam govori da izvođenje ključa radi, što je nužno, ali daleko od dovoljnog. Test koji samo otvara šifriranu datoteku i potvrđuje da lozinka prolazi izvijestit će o uspjehu iako je tijelo nečitljivo. Pravi test dešifrira paket i uspoređuje vraćene bajtove s izvornim unosom ili provodi radnu knjigu kroz puni krug šifriranja i dešifriranja te ponovno čita ćelije. Verifikator dokazuje lozinku; samo tijelo dokazuje šifriranje

Podržani način čitanja i pisanja zaštićenih radnih knjiga

Javno sučelje je malo. Za pisanje lozinkom zaštićene moderne radne knjige, popunite ili otvorite TXLSXWorkbook i pozovite SaveAsEncrypted s nazivom datoteke i lozinkom; to serijalizira radnu knjigu i pokreće cjevovod Standardnog šifriranja koji je prvi ispravak popravio, vraćajući 1 u slučaju uspjeha. Za čitanje pozovite CanReadEncrypted da biste provjerili je li datoteka šifrirani Compound File spremnik, a zatim se granajte: OpenEncrypted rukuje šifriranom stazom i vraća se na Open za obične datoteke, a Open s lozinkom dostupan je izravno. Rukovanje načinom rada i petlja ponovnog ključanja opisani iznad nalaze se ispod tih poziva; vi dajete lozinku i naziv datoteke, a mehanizam 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 izgled paketa pokriveni su u našem pregledu AES-zaštićenog XLSX izlaza. Za zasebno pitanje zaključavanja na razini lista i načina na koji zaštita stupa u interakciju s pripremom stranice i ispisom, pogledajte članak o zaštiti, pripremi stranice i ispisu. Obje se značajke grade na stazi šifriranja opisanoj ovdje, koja dolazi kao dio komponente proračunskih tablica HotXLS spreadsheet component za Delphi i C++Builder, zajedno s API-jima za čitanje, pisanje i renderiranje koji su pokriveni drugdje na ovom blogu