Tehnički članak

Otvaranje šifriranih PDF-ova sirovim ključem u Delphiju

PDF Library for Delphi može otvoriti šifrirani PDF iz njegova sirovog ključa šifriranja datoteke umjesto lozinke. DAOpenFileWithEncryptionKey prihvaća ključ kao heksadecimalni tekst, provjerava ga prema verifikatoru koji je već pohranjen u rječniku šifriranja i vraća Direct Access ručku samo za čitanje; DAOpenFromStreamWithEncryptionKey isto radi za TStream u vlasništvu pozivatelja. Obje su mogućnosti uvedene u v3.496.0

Scenarij je uzak, ali stvaran. Forenzička istraga preda vam ključ oporavljen iz memorijske slike i nijednu lozinku. Masovna arhivska obrada ima deset tisuća dokumenata čiji ključevi datoteka sjede u escrow bazi jer je izvorni DRM sustav prije mnogo godina prestao izdavati lozinke. Migracija sa starog sustava za upravljanje pravima ima ključni materijal i ništa drugo. U svakom od tih slučajeva vjerodajnica koju držite izlaz je derivacije ključa, a ne ulaz, i nijedan parametar lozinke u API-ju neće je prihvatiti

Zašto ključ šifriranja datoteke nije lozinka?

Lozinka i ključ šifriranja datoteke nalaze se na suprotnim stranama derivacije ključa u standardnom sigurnosnom rukovatelju PDF-a (ISO 32000-1 §7.6.3). Rukovatelj uzima lozinku, miješa je s /O, /P, ID-jem datoteke i hashom specifičnim za reviziju te proizvodi ključ datoteke. Ako ključ datoteke ubacite u utor za lozinku, dobit ćete besmislicu hashiranu u drugu besmislicu, zbog čega ovo treba vlastitu ulaznu točku, a ne zastavicu na DAOpenFile

Mjesto na koje se sirovi ključ može ubrizgati određeno je revizijom. Revizije od 2 do 4 i dalje izvode poseban ključ po objektu iz ključa datoteke, broja objekta, generacijskog broja i, za AESV2, AES soli, pa posjedovanje ključa datoteke uopće ne omogućuje preskakanje derivacije na razini objekta. Revizije od 5 do 7 izravno koriste 32-bajtni ključ datoteke za AES-256, bez koraka po objektu. Jedini sloj zajednički objema skupinama jest sam ključ datoteke, pa je to jedino mjesto na kojem PDF Library for Delphi prihvaća izvana predani ključ. Ako još imate lozinku, ostanite na običnom putu i prepustite životnom ciklusu ponovnog pokušaja lozinke obradu prvog pogrešnog pokušaja, jer sirovi ključ namjerno odustaje od nekoliko pogodnosti koje put lozinke zadržava

Koji ulaz prihvaća DAOpenFileWithEncryptionKey?

Samo ASCII heksadecimalni tekst bez prefiksa i razmaka, parnog broja znakova, a broj dekodiranih bajtova mora točno odgovarati reviziji šifriranja. Za revizije od 2 do 4 očekivana duljina dolazi iz /Length u rječniku šifriranja: višekratnik od 8 bitova koji daje između 5 i 16 bajtova, uz zadanu vrijednost od 40 bitova kada /Length nedostaje. Za revizije od 5 do 7 duljina je točno 32 bajta, bez pregovaranja. Prefiks 0x, neparan broj znamenki, više od 64 heksadecimalna znaka, nepoznati bit u Options ili dokument koji uopće nije šifriran daju isti ishod: ručku 0 i LastErrorCode postavljen na PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, što je 425. Strogoća je ovdje namjerna. Blagi parser koji ukloni razmake i dopuni kratak ulaz nulama rado će pretvoriti nepotpuno lijepljenje iz međuspremnika u vjerodajnicu, a zatim zakazati na mjestu koje je mnogo teže protumačiti

Var
  Lib: TPDFlib;
  FileHandle, PageRef: Integer;
Begin
  Lib:= TPDFlib.Create;
  Try
    // KeyHex je 32 heksadecimalna znaka za AES-128 R4, a 64 za AES-256 R6
    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;

Ostali kodovi neuspjeha ostaju različiti kako bi paketni posao mogao razlikovati operatorovu pogrešku od problema s dokaznim materijalom: 411 kada datoteka ne postoji, 401 kada je nije moguće otvoriti za čitanje, 409 kada je struktura unakrsnih referenci oštećena. Sve što se odnosi na ključ svodi se na 425, namjerno, jer bi ulazna točka za sirovi ključ koja prijavljuje koji je dio ključa pogrešan bila oracle

Što provjera zapravo dokazuje?

PDF Library for Delphi dokazuje da predani ključ pripada tom dokumentu, koristeći verifikator koji rječnik šifriranja već nosi, a provjera se razlikuje po reviziji. Revizija 2 ponovno izračunava RC4 šifriranje 32-bajtnog standardnog niza za dopunu i uspoređuje svih 32 bajta s /U. Revizije 3 i 4 hashiraju dopunu zajedno s ID-jem datoteke, pokreću RC4 prolaz i 19 rundi izvedenih XOR-om te uspoređuju prvih 16 bajtova /U. Revizije od 5 do 7 dešifriraju 16-bajtni niz /Perms s nultim IV-om i istodobno provjeravaju četiri neovisne stvari: riječ dozvola little-endian redoslijedom prema /P, četiri bajta FF na položajima od 5 do 8, zastavicu šifriranja metapodataka kao T ili F te oznaku adb na položajima od 10 do 12

Kada verifikator postoji, ali se ne podudara, otvaranje se bezuvjetno odbija. To vrijedi reći izravno jer je upravo to jamstvo na kojem počiva cijela značajka. Primijetite i što provjera nije: ona govori da ključ dešifrira ovu datoteku, a ne da vas je netko ovlastio za njezinu upotrebu. Riječ dozvola oporavljena iz /Perms dokaz je o ključu, a ne odobrenje, i ako želite znati što dokument doista tvrdi da dopušta, to je zaseban posao za reviziju šifriranja i dozvola. Problemi normalizacije na strani lozinke, poput obrade SASLprep za ne-ASCII AES-256 lozinke, ovdje se jednostavno ne pojavljuju jer nijedan niz ne dolazi do hasha

Kada se primjenjuje PDF_RAW_KEY_ALLOW_UNVERIFIED?

PDF_RAW_KEY_ALLOW_UNVERIFIED pokriva točno jednu situaciju: dokument nema upotrebljiv verifikator jer /Perms nedostaje ili nije dug 16 bajtova, odnosno /U je prekratak za usporedbu. Ne može nadjačati neuspjele dokaze. Pokvarite jednu heksadecimalnu znamenku unutar /Perms i predajte točan ključ s postavljenom opcijom, a PDF Library for Delphi i dalje vraća 0 i 425. Predajte ključ od 32 nulta bajta uz netaknuti verifikator i s postavljenom opcijom, odgovor je isti. Opcija ublažava odsutnost dokaza, nikada proturječje s njim. Budući da su oporavno otvaranje i provjereno otvaranje različita spoznajna stanja, prijavljuju se i zasebno, a DAGetEncryptionKeyValidation prima otvorenu ručku i odgovara jednom od triju konstanti:

  • PDF_RAW_KEY_VALIDATION_VERIFIED (1) znači da je verifikator postojao i podudarao se
  • PDF_RAW_KEY_VALIDATION_UNVERIFIED (2) znači da je ključ prihvaćen samo zato što se nijedan verifikator nije mogao procijeniti, a pozivatelj je izričito zatražio takvu politiku
  • PDF_RAW_KEY_VALIDATION_NONE (0) ono je što prijavljuje ručka otvorena običnom lozinkom
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
   (Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
  // nema verifikatora u ovoj datoteci? pokušaj ponovno uz izričitu politiku oporavka
  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;

Samo za čitanje po konstrukciji i vlasnik toka

Ulaz za sirovi ključ datoteke uvijek otvara izvor s fmOpenRead or fmShareDenyWrite i cijeli Direct Access lanac označava samo za čitanje, pa DAAppendFile odbija pisanje na mjestu i vraća 2 umjesto da pokuša inkrementalno ažuriranje. To nije politika iz koje se ručka može nagovoriti da izađe; postavlja se u konstruktoru prije nego što se datoteka uopće raščlani. Za rad s dokaznim materijalom važno je svojstvo da bajtovi izvora nakon zatvaranja ručke ostanu identični bajt po bajt, a regresijski paket upravo to provjerava i na testnoj datoteci revizije 4 s AES-128 i na onoj revizije 6 s AES-256. Ulaz iz toka na pisanja reagira jednako i dodaje još jedno pravilo: DAOpenFromStreamWithEncryptionKey nikad ne preuzima vlasništvo, pa DACloseFile ostavlja vaš TStream živim, a oslobađate ga sami

Source:= TMemoryStream.Create;
Try
  Source.LoadFromFile(ArchivePath);
  Source.Position:= 0;
  FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
  If FileHandle<> 0 Then
  Try
    // 2 = ručka samo za čitanje: izvezi drugamo, nikad ne dodaj u dokazni materijal
    Assert(Lib.DAAppendFile(FileHandle)= 2);
    Harvest(Lib, FileHandle);
  Finally
    Lib.DACloseFile(FileHandle);
  End;
Finally
  Source.Free;   // ručka nikad nije bila vlasnik ovog toka
End;

Higijena ključa i što izvozi DLL

Zatvaranje lanca prepisuje ključ datoteke, predmemoriju lozinke i izvedene ključeve objekata, a dekodirani ključ briše se u bloku Finally same ulazne točke bez obzira na to je li otvaranje uspjelo. Iza toga krije se pojedinost koja je odnijela stvarno vrijeme otklanjanja pogrešaka: svaka kopija koja mora preživjeti izričito se klonira pomoću SetLength i Move, umjesto da se dodijeli. Dodijelite jedan AnsiString drugome u Delphiju i oba imena dijele jedan međuspremnik uz copy-on-write, pa bi brisanje na strani pozivatelja poništilo ključ koji kriptografski rukovatelj još koristi, a dokument bi se dešifrirao u smeće iz razloga koji nijedan stack trace ne bi objasnio. Samo ulazne točke zasnovane na datoteci prelaze granicu DLL-a, u širokom i ANSI obliku, zajedno s pristupnikom statusa provjere; varijanta TStream ostaje samo za Delphi jer ovisi o životnom vijeku Delphi objekta i semantici referenci koje nemaju pošten prikaz u ravnom C ABI-ju. Ako je vaš alat za oporavak DLL klijent, planirajte prijelaz kroz privremenu datoteku i njezino brisanje pod istim kontrolama koje primjenjujete na ključ

S heksadecimalnim ključem postupajte kao s materijalom vjerodajnice i primijenite pravila rukovanja koja biste dali lozinci dokumenta, a status provjere čuvajte u zapisu koji stvara vaš lanac čuvanja dokaznog materijala, kako bi kasniji čitatelj mogao razlikovati provjereno izdvajanje od nepotvrđenog. Ako procjenjujete Delphi PDF komponentu za forenziku, masovnu arhivu ili migraciju DRM-a, ulazne točke za sirovi ključ, jamstvo samo za čitanje i površina za reviziju šifriranja dio su iste biblioteke, a potpuni popis značajki možete pročitati na stranici proizvoda PDF Library for Delphi