Tekninen artikkeli

PDF:n avaaminen raakalla salausavaimella

PDF Library for Delphi voi avata salatun PDF:n raakaa tiedoston salausavainta käyttäen salasanan sijaan. DAOpenFileWithEncryptionKey ottaa avaimen heksadesimaalitekstinä, tarkistaa sen salausdictionaryyn jo tallennettua todentinta vasten ja palauttaa read-only Direct Access -kahvan; DAOpenFromStreamWithEncryptionKey tekee saman kutsujan omistamalle TStream-oliolle. Molemmat tulivat mukaan versiossa v3.496.0

Käyttötapaus on kapea mutta todellinen. Forensiikkatutkimuksessa saat muistivedoksesta palautetun avaimen ilman salasanaa. Laaja arkistointiajo käsittelee kymmentätuhatta dokumenttia, joiden tiedostoavaimet ovat escrow-tietokannassa, koska alkuperäinen DRM-järjestelmä lopetti salasanojen myöntämisen vuosia sitten. Vanhasta oikeuksienhallintatuotteesta siirtyessä sinulla on avainmateriaali mutta ei mitään muuta. Kaikissa näissä tapauksissa hallussasi oleva tunniste on avainjohdannan tulos eikä syöte, eikä mikään API:n salasanaparametri voi vastaanottaa sitä

Miksi tiedoston salausavain ei ole salasana?

Salasana ja tiedoston salausavain ovat PDF-standardin suojauskäsittelijän (ISO 32000-1 §7.6.3) avainjohdannan vastakkaisilla puolilla. Käsittelijä ottaa salasanan, sekoittaa siihen arvot /O, /P ja tiedoston ID:n sekä revisiokohtaisen tiivisteen ja tuottaa tiedostoavaimen. Jos syötät tiedostoavaimen salasanakenttään, saat yhden hölynpölyn tiivistettynä toiseksi hölynpölyksi, minkä vuoksi tarvitaan oma entry point eikä DAOpenFile-metodin lippu

Kohta johon raaka avain voidaan syöttää määräytyy revision mukaan. Revisiot 2–4 johtavat edelleen erillisen objektikohtaisen avaimen tiedostoavaimesta, objektin numerosta, generointinumerosta ja AESV2:n tapauksessa AES-suolasta, joten tiedostoavain ei auta ohittamaan objektitason johdantaa lainkaan. Revisiot 5–7 käyttävät 32-tavuista tiedostoavainta suoraan AES-256:een ilman objektikohtaista vaihetta. Molempia yhdistävä taso on itse tiedostoavain, joten juuri siinä kohdassa PDF Library for Delphi hyväksyy ulkoa annetun avaimen. Jos salasana on yhä saatavilla, käytä tavallista polkua ja anna väärän ensimmäisen yrityksen käsittelevän salasanan uudelleenyrityksen elinkaaren hoitaa asia, koska raaka-avaimen entry point luopuu tarkoituksella useista mukavuuksista jotka salasanapolku säilyttää

Millaista syötettä DAOpenFileWithEncryptionKey hyväksyy?

Vain etuliitteetöntä, tyhjeetöntä ja parillisella pituudella annettua ASCII-heksaa, jonka dekoodattujen tavujen määrän täytyy vastata salaustäsmennettä tarkasti. Revisioilla 2–4 odotettu pituus saadaan salausdictionaryn arvosta /Length: 8 bitin monikerta joka osuu 5 ja 16 tavun väliin, oletuksena 40 bittiä kun /Length puuttuu. Revisioilla 5–7 pituus on täsmälleen 32 tavua, eikä siitä neuvotella. 0x-etuliite, pariton numeromäärä, yli 64 heksamerkkiä, tuntematon bitti Options-arvossa tai salaamaton dokumentti tuottavat kaikki saman lopputuloksen: kahvan 0 ja LastErrorCode-arvon PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, joka on 425. Tiukkuus on tarkoituksellista. Säännöt joustavasti toteuttava jäsentäjä, joka trimmaa tyhjeet ja täyttää lyhyen syötteen nollilla, voi hyväksyä katkenneen leikepöytäkopion tunnisteeksi ja epäonnistua sitten paljon epäselvemmässä kohdassa

Var
  Lib: TPDFlib;
  FileHandle, PageRef: Integer;
Begin
  Lib:= TPDFlib.Create;
  Try
    // KeyHex on 32 heksamerkkiä AES-128 R4:lle ja 64 AES-256 R6:lle
    FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
    If FileHandle= 0 Then
      Raise Exception.CreateFmt('raw key refused (error %d)', [Lib.LastErrorCode]);
    Try
      PageRef:= Lib.DAFindPage(FileHandle, 1);
      WriteLn(Lib.DAExtractPageText(FileHandle, PageRef, 0));
    Finally
      Lib.DACloseFile(FileHandle);
    End;
  Finally
    Lib.Free;
  End;
End;

Muut virhekoodit pysyvät erillään, jotta eräajo voi erottaa käyttäjän virheen aineisto-ongelmasta: 411 kun tiedostoa ei ole, 401 kun sitä ei voi avata lukemista varten ja 409 kun cross-reference-rakenne on rikki. Kaikki avaimeen liittyvät ongelmat tiivistyvät tarkoituksella arvoon 425, sillä raaka-avaimen entry point joka kertoisi mikä avaimen osa oli väärin toimisi oracleina

Mitä tarkistus todellisuudessa todistaa?

PDF Library for Delphi todistaa, että annettu avain kuuluu tähän dokumenttiin, käyttämällä salausdictionaryn mukana olevaa todentinta, ja tarkistus eroaa revision mukaan. Revisio 2 laskee standardin 32-tavuisen padding-merkkijonon RC4-salauksen uudelleen ja vertaa kaikkia 32 tavua arvoon /U. Revisiot 3 ja 4 tiivistävät paddingin yhdessä tiedosto-ID:n kanssa, ajavat RC4-vaiheen ja 19 XOR-johdettua kierrosta sekä vertaavat arvon /U ensimmäisiä 16 tavua. Revisiot 5–7 purkavat 16-tavuisen /Perms-merkkijonon salauksen nolla-IV:llä ja tarkistavat samanaikaisesti neljä toisistaan riippumatonta asiaa: little-endian-muotoisen oikeussanan suhteessa arvoon /P, neljä FF-tavua kohdissa 5–8, metatietojen salaamisen lipun arvona T tai F sekä adb-merkin kohdissa 10–12

Kun todentin on olemassa mutta ei täsmää, avaaminen hylätään ehdoitta. Tämä kannattaa sanoa suoraan, koska koko ominaisuus perustuu tähän takuuseen. Huomaa myös mitä tarkistus ei todista: se kertoo, että avain purkaa tämän tiedoston, ei että kenelläkään olisi lupa käyttää sitä. /Perms-arvosta palautettu oikeussana on avainta koskevaa näyttöä eikä käyttöoikeus, ja jos haluat tietää mitä dokumentti todella väittää sallivansa, se on erillinen tehtävä salaus- ja käyttöoikeustarkistuksessa. Salasanapuolen normalisointiongelmia, kuten ei-ASCII-muotoisten AES-256-salasanojen SASLprep-käsittelyä, ei tässä synny, koska mikään merkkijono ei päädy tiivisteeseen

Milloin PDF_RAW_KEY_ALLOW_UNVERIFIED pätee?

PDF_RAW_KEY_ALLOW_UNVERIFIED kattaa täsmälleen yhden tilanteen: dokumentissa ei ole käyttökelpoista todentinta, koska /Perms puuttuu tai ei ole 16-tavuinen, tai /U on liian lyhyt vertailuun. Se ei voi ohittaa ristiriitaista näyttöä. Muuta yksi heksanumero arvossa /Perms ja anna oikea avain option kanssa, niin PDF Library for Delphi palauttaa silti arvon 0 ja virheen 425. Anna ehjää todentinta vasten 32 nollatavun avain option kanssa, niin tulos on sama. Optio höllentää todisteen puuttumista, ei koskaan ristiriidan olemassaoloa. Koska palautusavaus ja varmennettu avaus ovat tiedollisesti eri tiloja, ne raportoidaan erikseen sen sijaan että ne sulautettaisiin paluuarvoon, ja DAGetEncryptionKeyValidation ottaa avauskahvan ja vastaa yhdellä kolmesta vakiosta:

  • PDF_RAW_KEY_VALIDATION_VERIFIED (1) tarkoittaa, että todentin oli olemassa ja täsmäsi
  • PDF_RAW_KEY_VALIDATION_UNVERIFIED (2) tarkoittaa, että avain hyväksyttiin vain koska todentinta ei voitu arvioida ja kutsuja pyysi tätä käytäntöä nimenomaisesti
  • PDF_RAW_KEY_VALIDATION_NONE (0) on arvo jonka tavallisella salasanalla avattu kahva ilmoittaa
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
   (Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
  // tässä tiedostossa ei ole todentinta? yritä uudelleen eksplisiittisellä palautuskäytännöllä
  FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex,
    PDF_RAW_KEY_ALLOW_UNVERIFIED);
If FileHandle<> 0 Then
Begin
  Case Lib.DAGetEncryptionKeyValidation(FileHandle) Of
    PDF_RAW_KEY_VALIDATION_VERIFIED:
      Chain.Note('key verified against the encryption dictionary');
    PDF_RAW_KEY_VALIDATION_UNVERIFIED:
      Chain.Note('no verifier available: extraction is unattested');
  End;
End;

Read-only rakenteena ja streamin omistaja

Raaka-avaimella avattava tiedosto avaa lähteen aina lipuilla fmOpenRead or fmShareDenyWrite ja merkitsee koko Direct Access -ketjun read-onlyksi, joten DAAppendFile kieltäytyy paikalla kirjoittamisesta ja palauttaa arvon 2 sen sijaan että yrittäisi inkrementaalista päivitystä. Kahvaa ei voi taivuttaa pois tästä käytännöstä; se asetetaan konstruktorissa ennen kuin tiedostoa edes jäsennetään. Aineistotyössä haluat varmistaa, että lähdetavut ovat kahvan sulkemisen jälkeen tavutasolla identtiset, ja regressiotestit varmistavat tämän sekä AES-128-revision 4 että AES-256-revision 6 fixturellä. Stream-entry point toimii kirjoitusten osalta samalla tavalla ja lisää yhden säännön: DAOpenFromStreamWithEncryptionKey ei koskaan ota streamia omistukseensa, joten DACloseFile jättää TStream-oliosi eloon ja vapautat sen itse

Source:= TMemoryStream.Create;
Try
  Source.LoadFromFile(ArchivePath);
  Source.Position:= 0;
  FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
  If FileHandle<> 0 Then
  Try
    // 2 = read-only-kahva: vie tulos muualle, älä koskaan lisää aineistoon
    Assert(Lib.DAAppendFile(FileHandle)= 2);
    Harvest(Lib, FileHandle);
  Finally
    Lib.DACloseFile(FileHandle);
  End;
Finally
  Source.Free;   // kahva ei koskaan omistanut tätä streamia
End;

Avaimen käsittely ja DLL:n eksportit

Ketjun sulkeminen ylikirjoittaa tiedostoavaimen, salasanacachen ja johdetut objektiavaimet, ja dekoodattu avain pyyhitään entry pointin omassa Finally-lohkossa riippumatta siitä onnistuiko avaaminen. Tämän takana on yksityiskohta, jonka selvittämiseen kului oikeasti aikaa: jokainen säilytettävä kopio kloonataan eksplisiittisesti SetLength- ja Move-kutsuilla sen sijaan että se sijoitettaisiin. Kun yksi AnsiString annetaan toiselle Delphissä, molemmat nimet jakavat copy-on-write -mallilla saman puskurin, joten kutsujan puolen pyyhkiminen nollaisi avaimen jota crypt handler vielä käyttää ja dokumentin purku muuttuisi roskaksi ilman selitystä antavaa stack tracea. Vain tiedostopohjaiset entry pointit ylittävät DLL-rajan leveissä ja ANSI-muodoissa yhdessä validointitilan accessorin kanssa; TStream-versio pysyy Delphi-kohtaisena, koska se riippuu Delphin objektien elinkaaresta ja viitesemantiikasta joille ei ole rehellistä esitystä litteässä C-ABI:ssa. Jos palautustyökalusi on DLL-asiakas, varaudu stagingiin väliaikaiseen tiedostoon ja sen poistamiseen samoilla kontrolleilla joita käytät avaimelle

Käsittele heksa-avainta tunnistemateriaalina samoilla säännöillä kuin dokumentin salasanaa ja säilytä validointitila siinä lokissa jonka säilytysketjusi tuottaa, jotta myöhempi lukija erottaa varmennetun purun todentamattomasta. Jos arvioit Delphi PDF -komponenttia forensiikkaan, laajaan arkistointiin tai DRM-siirtoon, raaka-avaimen entry pointit, read-only-takuu ja salaustarkastuksen pinta kuuluvat kaikki samaan kirjastoon, ja täydellinen ominaisuuslista löytyy PDF Library for Delphi -tuotesivulta