Tekninen artikkeli

Lue Agile-salattuja Excel-tiedostoja Delphissä HotXLS:llä

HotXLS lukee Agile-salattuja Excel-tiedostoja, eli sitä salasanasuojausta, jota Excel 2010 ja kaikki myöhemmät versiot käyttävät oletuksena, yhdellä kutsulla: TXLSXWorkbook.OpenEncrypted. Komponentti jäsentää XML-muotoisen salauskuvaimen, johtaa avaimet salasanasta SHA-512-kierroslukuisella tiivisteketjulla, varmistaa salasanan salattua todentajaa vasten ja purkaa sitten paketin 4096 tavun AES-CBC-segmentteinä. Mukana ei ole Excel-asennusta, COMia eikä ulkoista kryptaus-DLL:ää

Tämä artikkeli käsittelee nimenomaan Agile-salauksen lukupuolta. Kahdella naapuriongelmalla on omat artikkelinsa: vanhojen BIFF .xls -tiedostojen sisäisten RC4- ja XOR-skeemojen kanssa toimimista käsitellään ECB- ja RC4-yhteentoimivuusartikkelissa, ja salasanasuojattujen työkirjojen tuottamista ECMA-376 Standard Encryption -salauksella käsitellään AES-suojatun XLSX-tulosteen artikkelissa. Täällä tiedosto on jo olemassa, joku muu salasi sen, ja sinun tehtäväsi on avata se

Skenaario, joka pakottaa asian esiin, on tuttu jokaiselle dokumenttiputkea pyörittävälle. Palvelinpuolen tuontipalvelu ottaa vastaan työkirjalatauksia; koneella ei ole Exceliä eikä koskaan tule olemaan; ja eräänä aamuna asiakas lataa täysin tavallisen .xlsx-tiedoston, jonka ZIP-lukija hylkää, koska se ei ole ZIP lainkaan. Asiakas tallensi sen salasanalla. Siitä hetkestä lähtien latain joko ymmärtää [MS-OFFCRYPTO]-määrittelyn tai kimmottaa tiedoston takaisin käyttäjälle, joka omasta näkökulmastaan ei tehnyt mitään epätavallista

Mitä Agile-salaus on Excel-tiedostossa?

Agile-salaus on salasanasuojausskeema, joka määritellään kohdissa [MS-OFFCRYPTO] §2.3.4.10 - §2.3.4.15, ja sen Excel 2010 ja uudemmat kirjoittavat aina kun työkirja tallennetaan salasanalla. Salattu tiedosto ei ole enää ZIP-paketti. Se on OLE Compound File Binary (CFB) -säiliö, joka pitää sisällään kaksi virtaa: EncryptionInfo, joka kuvaa miten salaus tehtiin, ja EncryptedPackage, joka on todellinen .xlsx-ZIP salattuna läpinäkymättömänä möykkynä. CFB-allekirjoitus (D0 CF 11 E0 A1 B1 1A E1) on sama taika, jota vanhat BIFF .xls -tiedostot kantavat, minkä vuoksi uudelleennimettyä tai salattua tiedostoa ei voi luokitella pelkän tiedostopäätteen perusteella

Agile-salatun työkirjan anatomia, jonka HotXLS avaa Delphissä: CFB-säiliö, joka pitää sisällään EncryptionInfo-kuvainvirran ja AES-CBC-segmenteistä koostuvan EncryptedPackage-virran
HotXLS lukee Agile-salatun työkirjan CFB-säiliönä, jossa on itseään kuvaava EncryptionInfo-virta ja .xlsx-ZIP salattuna läpinäkymättömänä EncryptedPackage-virtana

Agilen erottaa edeltäjistään se, että EncryptionInfo kuvaa itse itsensä. Kahdeksan tavun versioetuliitteen jälkeen, jossa sekä pää- että aliversio ovat 4, virta on UTF-8-muotoinen XML-kuvain. keyData-elementti ilmoittaa salakirjoitusalgoritmin (AES), ketjutustilan (ChainingModeCBC), tiivisteen (SHA512), avaimen pituuden bitteinä, lohkokoon ja Base64-suolan. Salasanan keyEncryptor-elementti kantaa omaa suolaansa, spinCount-arvoa ja kolmea Base64-hyötykuormaa: encryptedVerifierHashInput, encryptedVerifierHashValue ja encryptedKeyValue. Excel kirjoittaa AES-256:n kierrosluvulla 100 000, mutta kuvain saa ilmoittaa myös AES-128:n tai AES-192:n, ja HotXLS kunnioittaa sitä mitä keyBits sanoo sen sijaan, että olettaisi 256:n

Yksi sisäänkäynti tavallisille, Standard- ja Agile-työkirjoille

TXLSXWorkbook.OpenEncrypted käsittelee kaikki kolme tilaa, joihin kutsuja voi törmätä — tavallisen ZIPin, Standard-salatun ja Agile-salatun — joten latausten käsittelijöiden ei tarvitse luokitella tiedostoja ennen niiden lataamista. Metodi nuuskii ensin tiedoston: jos CFB-allekirjoitusta ei ole, se väistää tavalliselle Open-polulle ja salasana yksinkertaisesti ohitetaan. Jos tiedosto on CFB-säiliö, se kokeilee ensin ECMA-376 Standard Encryption -salausta, ja kun EncryptionInfo-version allekirjoitus on Agilen 4.4, se ohjaa kutsun Agile-liukuhihnalle. Paluuarvo on 1 onnistuessa, sama sopimus kuin Open-metodilla

var
  Wb: TXLSXWorkbook;
begin
  Wb := TXLSXWorkbook.Create;
  try
    // Toimii yhtä lailla tavalliselle .xlsx-, Standard-salatulle ja
    // Agile-salatulle tiedostolle
    if Wb.OpenEncrypted('upload.xlsx', 'customer-password') = 1 then
      Writeln(VarToWideStr(Wb.Sheets[1].Cells[1, 1].Value));
  finally
    Wb.Free;
  end;
end;

Salaamattoman syötteen varareitti merkitsee enemmän kuin miltä näyttää. Eräajotuoja, joka kutsuu aina OpenEncrypted-metodia, ei tarvitse haarautumista kutsukohdassa: tiedostot, joita ei koskaan suojattu, latautuvat täsmälleen kuten ennenkin, ja salattuina saapuvat tiedostot puretaan paikallaan ja syötetään sitten tavalliselle ZIP-lataimelle muistinsisäisenä virtana. Testattavia koodipolkuja on yksi, ei kolme

Miten salasanasta tulee AES-avain?

Agile-salaus ei koskaan käytä salasanaa suoraan. HotXLS laskee ensin iteroidun tiivisteen: alkuperäinen tiiviste on SHA-512 salasanan suolasta yhdistettynä salasanan UTF-16LE-tavuihin, ja sitten tiiviste tiivistetään uudelleen spinCount kertaa niin, että jokainen kierros liittää 32-bittisen little-endian-iteraatiolaskurin edellisen tiivisteen eteen. Excelin oletuskierrosluvulla 100 000 se on satatuhatta peräkkäistä SHA-512-kutsua salasanayritystä kohti, ja juuri se on koko pointti. Kierrosluku on raa-an voiman kuristin: se maksaa lailliselle kutsujalle muutaman millisekunnin kerran, ja sanakirjahyökkääjälle samat muutamat millisekunnit jokaisesta yksittäisestä arvauksesta

Miten HotXLS johtaa Agile-salauksen avaimet Delphissä: salasana pyöritetään 100 000 SHA-512-kierroksen läpi, minkä jälkeen kolme kiinteää 8 tavun lohkoavainta tuottavat todentaja- ja pakettiavaimet
Salasana ei koskaan pura mitään suoraan: SHA-512-kierroslukuketju syöttää kolme lohkoavaintiivistettä, jotka tuottavat todentaja-avaimet ja avaavat satunnaisen pakettiavaimen
// [MS-OFFCRYPTO] iteroitu salasanatiiviste:
//   H(0) = SHA-512(salt + UTF-16LE(password))
//   H(n) = SHA-512(LE32(n - 1) + H(n - 1)), toistetaan spinCount kertaa
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);            // iteraatiolaskuri, little-endian
    Move(Result[0], buf[4], 64);   // edellinen tiiviste
    Result := XlsSHA512(buf);
  end;
end;

Pyöritetty tiiviste ei ole vieläkään avain. Siitä johdetaan kolme erillistä avainta tiivistämällä se vielä kerran kiinteä 8 tavun lohkoavain perässään, yksi vakio kutakin käyttötarkoitusta kohti: FE A7 D2 76 3B 4B 9E 79 todentajan syötteen purkuun, D7 AA 0F 6D 30 61 34 4E todentajan tiivisteelle ja 14 6E 0B E7 AB AC D0 D6 varsinaisen pakettiavaimen avaamiseen. Kukin SHA-512-tulos katkaistaan ilmoitettuun avaimen pituuteen, ja [MS-OFFCRYPTO]:n mukaisesti se täytetään 0x36-tavuilla siinä teoreettisessa tapauksessa, jossa tiiviste on avainta lyhyempi. Sama 0x36-täyttösääntö pätee, kun salasanan suola laajennetaan lohkokokoon CBC:n alustusvektoriksi

Salasanan todennus ja saltSize-katkaisuansa

HotXLS varmistaa salasanan ennen kuin se koskee pakettiin, käyttäen kuvaimen todentajaparia. Se purkaa encryptedVerifierHashInput-arvon ensimmäisellä johdetulla avaimella, tiivistää tuloksen SHA-512:lla, purkaa encryptedVerifierHashValue-arvon toisella johdetulla avaimella ja vertaa kahta tiivistettä tavu tavulta. Epäsuhta tarkoittaa, että salasana on väärä, mistä raportoidaan omana lopputuloksenaan eikä sekavana työkirjana, ja ratkaisevasti se tarkoittaa, ettei paketin runkoa koskaan pureta väärällä avaimella, joten ei ole skenaariota, jossa väärä salasana tuottaisi uskottavan näköistä turmeltunutta dataa

Tässä on spesifikaation yksityiskohta, jonka on helppo mennä väärin. [MS-OFFCRYPTO] §2.3.4.13 määrittelee todentajan saltSize-tavuiseksi satunnaisdataksi, jossa saltSize on avainsalaajan suolan pituus, ei salakirjoituksen lohkokoko. Koska AES-CBC-salateksti on lohkolinjattua, purettu todentajan syöte palaa täytettynä 16 tavun monikertaan, ja se on katkaistava takaisin saltSize-mittaan ennen tiivistämistä. Excel kirjoittaa saltSize-arvon aina yhtä suureksi kuin blockSize, molemmat 16, joten toteutus, joka ohittaa katkaisun, läpäisee kaikki testit todellista Excel-tulostetta vasten ja kaatuu sitten ensimmäiseen tiedostoon sellaiselta tuottajalta, joka valitsi toisen suolapituuden. HotXLS katkaisee suolan pituuteen, koska niin spesifikaatio oikeasti sanoo, ja se että nuo kaksi arvoa käytännössä täsmäävät on sattuma, ei sopimus

Miten EncryptedPackage puretaan?

EncryptedPackage-virta alkaa 8 tavun little-endian-selväkielikoolla, jota seuraa salateksti 4096 tavun segmentteinä, ja HotXLS purkaa sen segmentti kerrallaan tuoreella alustusvektorilla kutakin segmenttiä kohti. Pakettiavain itse ei ole salasanasta johdettu: se on satunnainen välitason avain, jonka kirjoittaja salasi encryptedKeyValue-kenttään, ja HotXLS avaa sen kolmannella johdetulla avaimella katkaisten sen keyData-elementin ilmoittamaan avaimen pituuteen. Kunkin segmentin alustusvektori on SHA-512 keyData-suolasta yhdistettynä 32-bittiseen little-endian-segmentti-indeksiin, katkaistuna lohkokokoon. Tuo rakenne tarkoittaa, että mikä tahansa 4096 tavun segmentti voidaan purkaa itsenäisesti, mikä tekee muodosta periaatteessa myös suorasaantiystävällisen, vaikka HotXLS purkaa koko paketin muistiin ja luovuttaa syntyvät ZIP-tavut tavalliselle XLSX-lataimelleen

HotXLS purkaa EncryptedPackage-virran Delphissä segmentti kerrallaan johtaen kunkin 4096 tavun AES-CBC-segmentin alustusvektorin keyData-suolasta ja segmentti-indeksistä
Jokainen 4096 tavun AES-CBC-segmentti purkautuu itsenäisesti oman suola- ja indeksipohjaisen alustusvektorinsa alla, ja tulos katkaistaan ilmoitettuun selväkielikokoon

Ilmoitettu selväkielikoko tekee viimeisen työn. AES-CBC-tuloste on lohkolinjattua, joten viimeinen segmentti kantaa jopa 15 tavua täytettä, joka ei kuulu dokumenttiin; purettu puskuri katkaistaan kokoetuliitteen mittaan, ja tulos on täsmälleen se .xlsx-ZIP, jonka Excel salasi. HotXLS validoi etuliitteen todellista virran pituutta vasten ennen purkua, joten katkennut lataus tai peukaloitu kokokenttä epäonnistuu siististi eikä ylivuoda

Virheraportointi ja rehelliset rajat

Vikatilat pidetään tarkoituksella erillään. Väärä salasana nostaa poikkeuksen, jonka viesti kertoo eksplisiittisesti väärästä salasanasta ja jonka laukaisee todentajan epäsuhta, joten käyttöliittymä voi kehottaa käyttäjää yrittämään uudelleen. CFB-säiliö, jonka kuvain ilmoittaa tuetun joukon ulkopuolisia algoritmeja — Agile-kuvaimessa mitä tahansa muuta kuin AES CBC-ketjutuksella ja SHA-512-tiivistyksellä — tai säiliö, joka ei ole Standard eikä Agile, nostaa eri poikkeuksen, joka nimeää skeeman tukemattomaksi. Näitä kahta ei saa koskaan sekoittaa: salasanan uudelleenyrittäminen tukematonta skeemaa vastaan tuhlaa käyttäjän aikaa, ja väärän salasanan raportoiminen muotovirheenä lähettää tukitiimisi väärälle tielle

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
      // Nostetaan sekä väärästä salasanasta että tukemattomasta
      // skeemasta; E.Message kertoo kummasta, joten kirjaa se
      // sanatarkasti ja tarjoa salasanan uusintaa vain väärälle salasanalle
      RejectUpload(FileName, E.Message);
  end;
end;

Rajat kannattaa todeta suoraan. HotXLS lukee Agile-kuvaimia, jotka ilmoittavat AES:n CBC-tilassa SHA-512:n kanssa, mikä kattaa sen mitä Excel 2010 - Excel 365 todella kirjoittavat, kaikissa kolmessa avainkoossa. Kuvaimet, jotka ilmoittavat muita salakirjoituksia tai tiivistealgoritmeja, hylätään sen sijaan että niitä arvailtaisiin, eikä varmennepohjaisia avainsalaajia kuunnella, vain salasanan avainsalaajaa. Kirjoituspuolella HotXLS tuottaa nykyisellään Standard Encryption -salausta Agilen sijaan, mikä on merkityksellinen ero, jos myöhemmät työkalut tarkastavat skeeman; yksityiskohdat ovat artikkelissa AES-suojatun XLSX-tulosteen kirjoittamisesta

Salasanasuojatut lataukset lakkaavat olemasta erikoistapaus heti kun latain kohtelee salausta osana tiedostomuotoa eikä poikkeuksena siihen. OpenEncrypted-sisäänkäynti, SHA-512-kierroslukujohdanto ja tässä kuvattu segmentoitu AES-CBC-liukuhihna toimitetaan osana HotXLS Delphi Excel Component -komponenttia, sen natiivin XLS- ja XLSX-luku- ja kirjoitusmoottorin muun osan rinnalla Delphille ja C++Builderille