Tehnički članak

Čitanje Agile-šifrovanih Excel datoteka u Delphiju pomoću HotXLS-a

HotXLS čita Agile-šifrovane Excel datoteke, vrstu zaštite lozinkom koju Excel 2010 i sve kasnije verzije primenjuju po defaultu, putem jednog poziva: TXLSXWorkbook.OpenEncrypted. Komponenta analizira XML deskriptor šifrovanja, izvodi ključeve iz lozinke sa SHA-512 spin-count lancem sažetaka, proverava lozinku u odnosu na šifrovani verifikator, a zatim dešifruje paket u segmentima AES-CBC od 4096 bajtova. Nije uključena instalacija Excela, nikakav COM niti spoljni kriptografski DLL

Ovaj članak posebno pokriva čitalačku stranu Agile šifrovanja. Dva srodna problema imaju svoje članke: interoperabilnost sa nasleđenim RC4 i XOR shemama unutar starih BIFF .xls datoteka pokrivena je u članku o ECB i RC4 interoperabilnosti, a stvaranje lozinkom zaštićenih radnih svezaka sa ECMA-376 Standardnim šifrovanjem pokriveno je u članku o AES-zaštićenom XLSX izlazu. Ovde datoteka već postoji, neko drugi ju je šifrovao, a vaš je posao da je otvorite

Scenario koji nameće ovaj problem poznat je svima koji upravljaju pipeline-om dokumenata. Usluga uvoza na serverskoj strani prihvata učitavanje radnih svezaka; na mašini nema Excela niti će ga ikada biti; i jednog jutra kupac učita sasvim običan .xlsx koji čitač ZIP datoteka odbija jer to uopšte nije ZIP. Kupac ga je sačuvao sa lozinkom. Od tog trenutka vaš loader ili razume specifikaciju [MS-OFFCRYPTO] ili vraća datoteku korisniku koji, sa svog stanovišta, nije učinio ništa neobično

Šta je Agile šifrovanje u Excel datoteci?

Agile šifrovanje je shema zaštite lozinkom definisana u [MS-OFFCRYPTO] §2.3.4.10 do §2.3.4.15, i to je ono što Excel 2010 i novije verzije zapisuju kad god je radna sveska sačuvana sa lozinkom. Šifrovana datoteka više nije ZIP paket. To je OLE Compound File Binary (CFB) kontejner koji sadrži dva toka: EncryptionInfo, koji opisuje kako je šifrovanje izvedeno, i EncryptedPackage, što je stvarni .xlsx ZIP šifrovan kao neprozirni objekat (blob). CFB potpis (D0 CF 11 E0 A1 B1 1A E1) ista je magija koju nose nasleđene BIFF .xls datoteke, zbog čega se preimenovana ili šifrovana datoteka ne može klasifikovati samo po ekstenziji

Ono što razlikuje Agile od njegovih prethodnika jeste to što je EncryptionInfo samopisujući. Nakon 8-bajtnog prefiksa verzije, pri čemu su i glavna i sporedna verzija 4, tok je UTF-8 XML deskriptor. Element keyData deklarise šifru (AES), način ulančavanja (ChainingModeCBC), sažetak (SHA512), dužinu ključa u bitovima, veličinu bloka i Base64 so (salt). Element lozinke keyEncryptor nosi sopstvenu so, spinCount i tri Base64 korisna tereta: encryptedVerifierHashInput, encryptedVerifierHashValue i encryptedKeyValue. Excel zapisuje AES-256 sa spin countom od 100 000, ali deskriptor sme da deklarise AES-128 ili AES-192, i HotXLS poštuje ono što kaže keyBits umest da pretpostavlja 256

Jedna ulazna tačka za običan tekst, Standard i Agile radne sveske

TXLSXWorkbook.OpenEncrypted rukuje sa sva tri stanja sa kojima se pozivalac može susresti: običan ZIP, Standard-šifrovani i Agile-šifrovani, tako da rukovaoci učitavanjem ne moraju da klasifikuju datoteke pre nego što ih učitaju. Metoda najpre ispituje datoteku: ako nema CFB potpisa, prepušta učitavanje uobičajenoj stazi Open, a lozinka se jednostavno ignoriše. Ako je datoteka CFB kontejner, najpre pokušava sa ECMA-376 Standardnim šifrovanjem, a kada je potpis verzije EncryptionInfo Agile 4.4, šalje je u Agile pipeline. Povratna vrednost je 1 u slučaju uspeha, što je isti ugovor kao i za Open

var
  Wb: TXLSXWorkbook;
begin
  Wb := TXLSXWorkbook.Create;
  try
    // Works for plain .xlsx, Standard-encrypted and
    // Agile-encrypted files alike
    if Wb.OpenEncrypted('upload.xlsx', 'customer-password') = 1 then
      Writeln(VarToWideStr(Wb.Sheets[1].Cells[1, 1].Value));
  finally
    Wb.Free;
  end;
end;

Rezervna opcija (fallback) za nešifrovani unos važnija je nego što izgleda. Serijski uvoznik koji uvek poziva OpenEncrypted ne treba grananje na mestu poziva: datoteke koje nikada nisu bile zaštićene učitavaju se tačno kao i pre, a datoteke koje stignu šifrovane dešifruju se na licu mesta i zatim šalju običnom ZIP loaderu kao tok u memoriji. Postoji samo jedna staza koda za testiranje, a ne tri

Kako lozinka postaje AES ključ?

Agile šifrovanje nikada ne koristi lozinku direktno. HotXLS najpre izračunava iterirani sažetak: početni sažetak je SHA-512 preko soli lozinke spojene sa UTF-16LE bajtovima lozinke, a zatim se sažetak ponovo sažima spinCount puta, pri čemu se u svakom krugu ispred prethodnog sažetka dodaje 32-bitni little-endian brojač iteracija. Uz Excelov zadani spin count od 100 000, to je stotinu hiljada serijskih poziva SHA-512 po pokušaju lozinke, i u tome je cela poenta. Spin count je prigušivač grube sile (brute-force throttle): legitimnog pozivaoa košta nekoliko milisekundi jednokratno, a napadača rečnikom košta tih istih nekoliko milisekundi za svaki pojedini pokušaj pogađanja

// [MS-OFFCRYPTO] iterated password hash:
//   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);            // iteration counter, little-endian
    Move(Result[0], buf[4], 64);   // previous digest
    Result := XlsSHA512(buf);
  end;
end;

Spun hash je i dalje nije ključ. Tri različita ključa izvode se iz njega ponovnim sažimanjem sa pridodanom fiksnom 8-bajtnom blok ključ konstantom, jednom po svrsi: FE A7 D2 76 3B 4B 9E 79 za dešifrovanje unosa verifikatora, D7 AA 0F 6D 30 61 34 4E za sažetak verifikatora, i 14 6E 0B E7 AB AC D0 D6 za otvaranje samog ključa paketa. Svaki SHA-512 rezultat skraćuje se na deklarisanu dužinu ključa, i, prema [MS-OFFCRYPTO], popunjava se bajtovima 0x36 u teoretskom slučaju kada je sažetak kraći od ključa. Isto pravilo popunjavanja sa 0x36 primenjuje se kada se so lozinke proširi na veličinu bloka za upotrebu kao CBC inicijalizacioni vektor

Provera lozinke i zamka skraćivanja saltSize

HotXLS proverava lozinku pre nego što dotakne paket, koristeći verifikacioni par iz deskriptora. Dešifruje encryptedVerifierHashInput sa prvim izvedenim ključem, sažima rezultat pomoću SHA-512, dešifruje encryptedVerifierHashValue sa drugim izvedenim ključem i upoređuje dva sažetka bajt po bajt. Nepodudaranje znači da je lozinka pogrešna, što se prijavljuje kao jasan ishod, a ne kao zbrkana radna sveska. Ono što je kritično, to znači da se telo paketa nikada ne dešifruje sa lošim ključem, pa ne postoji scenario u kojem pogrešna lozinka proizvodi naizgled ispravne, a zapravo oštećene podatke

Ovde postoji detalj specifikacije koji je lako pogrešno shvatiti. [MS-OFFCRYPTO] §2.3.4.13 definiše verifikator kao saltSize bajtova nasumičnih podataka, pri čemu je saltSize dužina soli (salta) šifranta ključa, a ne veličina bloka šifre. Budući da je AES-CBC šifrovani tekst poravnat po blokovima, dešifrovani unos verifikatora vraća se popunjen do umnoška od 16 bajtova i mora se skratiti nazad na saltSize pre sažimanja. Excel uvek zapisuje saltSize jednak blockSize-u, oboje 16, pa implementacija koja preskače skraćivanje prolazi svaki test protiv stvarnog Excel izlaza, a zatim zakazuje na prvoj datoteci proizvođača koji je odabrao drugu dužinu soli. HotXLS skraćuje na dužinu soli jer je to ono što specifikacija zapravo kaže, a to što se te dve vrednosti u praksi slažu je koincidencija, a ne ugovor

Kako se dešifruje EncryptedPackage?

Tok EncryptedPackage započinje sa 8-bajtnom little-endian veličinom čistog teksta, nakon čega sledi šifrovani tekst u segmentima od 4096 bajtova, a HotXLS ga dešifruje segment po segment sa svežim IV-om po segmentu. Sam ključ paketa nije izveden iz lozinke: to je nasumični srednji ključ koji je pisac šifrovao u encryptedKeyValue, a HotXLS ga otvara sa trećim izvedenim ključem, skraćujući ga na dužinu ključa koju deklarise keyData. IV svakog segmenta je SHA-512 preko soli keyData spojene sa 32-bitnim little-endian indeksom segmenta, skraćen na veličinu bloka. Takva konstrukcija znači da se bilo koji segment od 4096 bajtova može dešifrovati nezavisno, što u načelu čini format prikladnim za nasumični pristup, iako HotXLS dešifruje ceo paket u memoriju i predaje rezultujuće ZIP bajtove svom uobičajenom XLSX loaderu

Deklarisana veličina čistog teksta obavlja poslednji deo posla. AES-CBC izlaz je poravnat po blokovima, pa poslednji segment nosi do 15 bytes ispune (padding) koji nisu deo dokumenta; dešifrovani međuspremnik se skraćuje na prefiks veličine, a rezultat je tačno .xlsx ZIP koji je Excel šifrovao. HotXLS proverava valjanost prefiksa u odnosu na stvarnu dužinu toka pre dešifrovanja, tako da skraćeni učitani podaci ili izmenjeno polje veličine zakazuju čisto umesto da uzrokuju prekoračenje

Prijavljivanje grešaka i poštene granice

Granice vredi jasno reći. HotXLS čita Agile deskriptore koji deklarišu AES u CBC načinu rada sa SHA-512, što pokriva ono što Excel 2010 do Excel 365 stvarno zapisuju, u sve tri veličine ključa. Deskriptori koji deklarišu druge šifre ili algoritme sažetka odbijaju se umesto da se pogađaju, a šifranti kljeva temeljeni na sertifikatu se ne konsultuju, već samo šifrant ključa lozinke. Na strani pisanja HotXLS trenutno proizvodi Standardno šifrovanje umesto Agilea, što je razlika koja je važna ako nizvodni alati ispituju shemu; detalji su u članku o pisanju AES-zaštićenog XLSX izlaza

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
      // Raised for both a wrong password and an unsupported
      // scheme; E.Message states which, so log it verbatim and
      // only offer a password retry for the wrong-password case
      RejectUpload(FileName, E.Message);
  end;
end;

Učitavanja zaštićena lozinkom prestaju biti poseban slučaj kada loader tretira šifrovanje kao deo formata datoteke, a ne kao izuzetak od njega. Uzlazna tačka OpenEncrypted, izvođenje SHA-512 sa spin-countom i segmentirani pipeline AES-CBC koji su ovde opisani dolaze kao deo komponente HotXLS Delphi Excel Component, zajedno sa ostatkom njenog izvornog mehanizma za čitanje i pisanje XLS i XLSX datoteka za Delphi i C++Builder