Tehnični članak

Branje šifriranih datotek Excel Agile v Delphiju s HotXLS

HotXLS bere datoteke Excel, zaščitene z Agile šifriranjem, kar je privzeta zaščita z geslom v Excelu 2010 in vseh novejših različicah, z enim samim klicem: TXLSXWorkbook.OpenEncrypted. Komponenta analizira XML opisnik šifriranja, izpelje ključe iz gesla z verigo zgoščevanja SHA-512, preveri geslo v primerjavi s šifriranim preverjalnikom in nato dešifrira paket v 4096-bajtnih segmentih AES-CBC. Pri tem niso vključeni namestitev Excela, COM vmesnik ali zunanje kriptografske knjižnice DLL

Ta članek se osredotoča posebej na branje datotek z Agile šifriranjem. Dve sorodni težavi sta obravnavani v svojih člankih: sodelovanje s starejšimi shemami RC4 in XOR znotraj starih datotek BIFF .xls je opisano v članku o interoperabilnosti ECB in RC4, ustvarjanje geslom zaščitenih delovnih zvezkov s Standardnim šifriranjem ECMA-376 pa je obravnavano v članku o zaščitenem izhodu XLSX z AES. V tem scenariju datoteka že obstaja, nekdo drug jo je šifriral, vaša naloga pa je, da jo odprete

Scenarij, ki sproži to težavo, je dobro znan vsakomur, ki upravlja s cevovodom dokumentov. Storitev uvoza na strani strežnika sprejema prenose delovnih zvezkov; na stroju ni Excela in ga nikoli ne bo; nekega jutra pa stranka prenese povsem običajno datoteko .xlsx, ki jo bralnik ZIP zavrne, ker sploh ni datoteka ZIP. Stranka jo je shranila z geslom. Od tistega trenutka vaš nalagalnik bodisi razume [MS-OFFCRYPTO] ali pa datoteko vrne uporabniku, ki z njegovega stališča ni storil nič nenavadnega

Kaj je Agile šifriranje v datoteki Excel?

Agile šifriranje je shema zaščite z geslom, opredeljena v specifikaciji [MS-OFFCRYPTO] §2.3.4.10 do §2.3.4.15, in je tisto, kar Excel 2010 in novejši zapišejo vsakič, ko se delovni zvezek shrani z geslom. Šifrirana datoteka ni več paket ZIP. Je vsebnik OLE Compound File Binary (CFB), ki hrani dva tokova: EncryptionInfo, ki opisuje, kako je bilo šifriranje izvedeno, in EncryptedPackage, ki je dejanski ZIP .xlsx, šifriran kot neprozoren blok. Podpis CFB (D0 CF 11 E0 A1 B1 1A E1) je enaka magija, kot jo nosijo stare datoteke BIFF .xls, zato preimenovane ali šifrirane datoteke ni mogoče razvrstiti le po končnici

Agile se od svojih predhodnikov razlikuje po tem, da je EncryptionInfo samoopisujoč. Po 8-bajtnem različičnem predponu (z glavno in stransko različico 4) je tok opisnik XML v UTF-8. Element keyData deklarira šifro (AES), način veriženja (ChainingModeCBC), zgoščevanje (SHA512), dolžino ključa v bitih, velikost bloka in sol Base64. Element gesla keyEncryptor nosi lastno sol, spinCount in tri Base64 vsebine: encryptedVerifierHashInput, encryptedVerifierHashValue in encryptedKeyValue. Excel piše AES-256 s spin štetjem 100.000, vendar opisnik lahko deklarira AES-128 ali AES-192, HotXLS pa spoštuje vrednost keyBits in ne predvideva vedno 256

Ena vstopna točka za navadne, Standard in Agile delovne zvezke

Metoda TXLSXWorkbook.OpenEncrypted upravlja z vsemi tremi stanji, na katera lahko klicatelj naleti: navaden ZIP, Standard-šifriran in Agile-šifriran, tako da upravljalnikom prenosov datotek ni treba razvrščati pred nalaganjem. Metoda najprej preveri datoteko: če ni podpisa CFB, se preusmeri na običajno pot Open, geslo pa se preprosto prezre. Če je datoteka vsebnik CFB, najprej poskusi s Standardnim šifriranjem ECMA-376, ko pa je podpis različice EncryptionInfo enak Agile 4.4, jo preda cevovodu Agile. Povratna vrednost je 1 ob uspehu, kar je enako kot pri 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;

Preusmeritev za nešifriran vhod je pomembnejša, kot je videti na prvi pogled. Paketni uvoznik, ki vedno kliče OpenEncrypted, ne potrebuje vejanja na mestu klica: datoteke, ki nikoli niso bile zaščitene, se naložijo natanko tako kot prej, datoteke, ki prispejo šifrirane, pa se dešifrirajo na mestu in nato predajo običajnemu nalagalniku ZIP kot tok v pomnilniku. Testirati je treba le eno pot kode in ne treh

Kako geslo postane ključ AES?

Agile šifriranje nikoli ne uporablja gesla neposredno. HotXLS najprej izračuna iterirano zgoščeno vrednost: začetni povzetek je SHA-512 nad soljo gesla, povezano z bajti UTF-16LE gesla, nato pa se povzetek znova zgošča spinCount-krat, pri čemer se v vsakem krogu prejšnjemu povzetku doda 32-bitni števec iteracij v zapisu z manj pomembnimi bajti na začetku (little-endian). Pri privzetem Excelovem spin štetju 100.000 to pomeni sto tisoč zaporednih klicev SHA-512 na poskus gesla, kar je tudi celoten namen tega postopka. Spin štetje je zaščita pred grobo silo: legitimnemu klicatelju povzroči strošek nekaj milisekund enkrat, napadalcu s slovarjem pa enake milisekunde za vsak posamezen poskus

// [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;

Zgoščena vrednost po spinanju še vedno ni ključ. Iz nje se izpeljejo trije ločeni ključi z dodatnim enkratnim zgoščevanjem, pri čemer se doda fiksni 8-bajtni ključ bloka, ki je konstanten za vsak namen: FE A7 D2 76 3B 4B 9E 79 za dešifriranje vhoda preverjalnika, D7 AA 0F 6D 30 61 34 4E za zgoščevanje preverjalnika in 14 6E 0B E7 AB AC D0 D6 za odvijanje dejanskega ključa paketa. Vsak rezultat SHA-512 se skrajša na deklarirano dolžino ključa in v skladu z [MS-OFFCRYPTO] dopolni z bajti 0x36 v teoretičnem primeru, ko je zgoščena vrednost krajša od ključa. Enako pravilo za dopolnjevanje 0x36 velja, ko se sol gesla razširi na velikost bloka za uporabo kot inicializacijski vektor CBC

Preverjanje gesla in zanka s skrajšanjem saltSize

HotXLS preveri geslo pred dotikom paketa z uporabo para preverjalnikov iz opisnika. Dešifrira encryptedVerifierHashInput s prvim izpeljanim ključem, zgošči rezultat s SHA-512, dešifrira encryptedVerifierHashValue z drugim izpeljanim ključem in primerja oba povzetka bajt za bajtom. Razlika pomeni, da je geslo napačno, kar se sporoči kot ločen izid in ne kot poškodovan delovni zvezek. Ključno je, da se telo paketa nikoli ne dešifrira z napačnim ključem, zato ni scenarija, kjer bi napačno geslo ustvarilo navidezno pravilne, a poškodovane podatke

Tukaj je podrobnost specifikacije, ki jo je lahko razumeti napačno. [MS-OFFCRYPTO] §2.3.4.13 opredeljuje preverjalnik kot saltSize bajtov naključnih podatkov, kjer je saltSize dolžina soli gesla in ne velikost bloka šifre. Ker je izhod AES-CBC poravnan na bloke, se dešifrirani vhod preverjalnika vrne dopolnjen do večkratnika 16 bajtov in ga je treba pred zgoščevanjem skrajšati nazaj na saltSize. Excel vedno piše saltSize enak blockSize, oba 16, zato implementacija, ki preskoči skrajšanje, uspešno opravi vsak test z realnim izhodom Excela, nato pa ne uspe pri prvi datoteki proizvajalca, ki je izbral drugačno dolžino soli. HotXLS izvaja skrajšanje na dolžino soli, ker specifikacija dejansko tako zahteva, ujemanje obeh vrednosti v praksi pa je naključje in ne pravilo

Kako se dešifrira EncryptedPackage?

Tok EncryptedPackage se začne z 8-bajtnim predpono velikosti čistega besedila v zapisu little-endian, sledi pa šifrirano besedilo v 4096-bajtnih segmentih. HotXLS ga dešifrira segment za segmentom s svežim IV za vsak segment. Sam ključ paketa ni izpeljan iz gesla: je naključni vmesni ključ, ki ga je zapisovalnik šifriral v encryptedKeyValue, HotXLS pa ga odvije s tretjim izpeljanim ključem in skrajša na dolžino ključa, deklarirano v keyData. IV vsakega segmenta je SHA-512 nad soljo keyData, povezano z 32-bitnim števcem segmentov little-endian, skrajšan na velikost bloka. Ta konstrukcija pomeni, da je mogoče vsak 4096-bajtni segment dešifrirati neodvisno, kar načeloma omogoča naključni dostop, čeprav HotXLS dešifrira celoten paket v pomnilnik in nastale bajte ZIP preda svojemu običajnemu nalagalniku XLSX

Deklarirana velikost čistega besedila opravi končni del dela. Izhod AES-CBC je poravnan na bloke, zato zadnji segment vsebuje do 15 bajtov dopolnitve, ki niso del dokumenta; dešifrirani odložišče se skrajša na predpono velikosti, rezultat pa je natanko tisti ZIP .xlsx, ki ga je Excel šifriral. HotXLS pred dešifriranjem potrdi predpono glede na dejansko dolžino toka, tako da skrajšan prenos ali spremenjeno polje velikosti odpove čistokrvno namesto s prekoračitvijo

Poročanje o napakah in poštene meje

Načini neuspeha so namerno ločeni. Napačno geslo sproži izjemo z eksplicitnim sporočilom o napačnem geslu, kar vodi razlika preverjalnika, tako da lahko uporabniški vmesnik uporabnika pozove k ponovnemu poskusu. Vsebnik CFB, katerega opisnik deklarira algoritme izven podprtega nabora (kar koli drugega kot AES z veriženjem CBC in zgoščevanjem SHA-512 v opisniku Agile) ali vsebnik, ki ni ne Standard ne Agile, sproži drugačno izjemo, ki shemo opredeli kot nepodprto. Obeh primerov ne smemo mešati: ponovni poskus gesla pri nepodprti shemi pomeni izgubo časa za uporabnika, poročanje o napačnem geslu kot o napaki formata pa pošlje vašo podporno ekipo na napačno pot

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;

Meje si zaslužijo jasen zapis. HotXLS bere opisnike Agile, ki deklarirajo AES v načinu CBC s SHA-512, kar pokriva to, kar Excel 2010 do Excel 365 dejansko zapišejo v vseh treh velikostih ključev. Opisniki, ki deklarirajo druge šifre ali algoritme zgoščevanja, so zavrnjeni in ne ugibani, ključni šifratorji na osnovi potrdil pa se ne preverjajo, temveč le tisti na osnovi gesla. Na strani pisanja HotXLS trenutno ustvari Standardno šifriranje in ne Agile, kar je razlika, ki šteje, če orodja v nadaljnjem poteku preverjajo shemo; podrobnosti so v članku o pisanju zaščitenega izhoda XLSX z AES

Geslom zaščiteni prenosi prenehajo biti poseben primer, ko nalagalnik obravnava šifriranje kot del formata datoteke in ne kot izjemo. Vstopna točka OpenEncrypted, izpeljava spin-štetja SHA-512 in segmentirani cevovod AES-CBC, opisani tukaj, so del komponente HotXLS Delphi Excel Component, skupaj z ostalim delom njenega izvornega mehanizma za branje in pisanje XLS in XLSX za Delphi in C++Builder