Tehnički članak

Otvaranje šifrovanih PDF-ova sirovim ključem u Delphi-ju

PDF Library for Delphi može da otvori šifrovani PDF iz njegovog sirovog ključa za šifrovanje fajla umesto iz lozinke. DAOpenFileWithEncryptionKey prihvata ključ kao heksadecimalni tekst, proverava ga prema proverivaču koji je već smešten u encryption dictionary i vraća read-only Direct Access handle; DAOpenFromStreamWithEncryptionKey radi isto za TStream čije vlasništvo ostaje kod pozivaoca. Obe funkcije stigle su u v3.496.0

Situacija je uska, ali stvarna. Forenzički angažman vam preda ključ oporavljen iz memorijske slike, bez lozinke. Masovna arhivska obrada ima deset hiljada dokumenata čiji ključevi fajlova stoje u escrow bazi jer je izvorni DRM sistem pre nekoliko godina prestao da izdaje lozinke. Migracija sa ugašenog rights-management proizvoda ostavi vam materijal ključa i ništa drugo. U svakom od tih slučajeva kredencijal koji držite jeste rezultat izvoda ključa, a ne ulazni podatak, i nijedan parametar lozinke u API-ju ne može da ga primi

Zašto ključ za šifrovanje fajla nije lozinka?

Lozinka i ključ za šifrovanje fajla nalaze se na suprotnim stranama izvoda ključa u PDF standardnom security handler-u (ISO 32000-1 §7.6.3). Handler uzima lozinku, meša je sa /O, /P, ID-jem fajla i hash-om specifičnim za reviziju, pa proizvodi ključ fajla. Ako ključ fajla ubacite u slot za lozinku, dobićete besmislen podatak koji se hash-uje u drugi besmislen podatak, zbog čega je potrebna zasebna ulazna tačka, a ne zastavica na DAOpenFile

Mesto na koje se sirovi ključ može ubaciti određeno je revizijom. Revizije 2 do 4 i dalje izvode poseban ključ po objektu iz ključa fajla, broja objekta, generacionog broja i, za AESV2, AES soli, pa posedovanje ključa fajla uopšte ne omogućava preskakanje izvoda po objektu. Revizije 5 do 7 koriste 32-bajtni ključ fajla direktno za AES-256, bez koraka po objektu. Zajednički sloj za obe grupe jeste sam ključ fajla i to je jedino mesto na kojem PDF Library for Delphi prihvata spolja zadat ključ. Ako ipak imate lozinku, ostanite na običnoj putanji i prepustite životnom ciklusu ponovnog pokušaja lozinke zaštićenom od pogrešnog prvog pokušaja da je obradi, jer sirovi-key entry point namerno ukida nekoliko pogodnosti koje putanja sa lozinkom zadržava

Koji ulaz prihvata DAOpenFileWithEncryptionKey?

Samo ASCII heksadecimalni tekst bez prefiksa, bez razmaka i parne dužine, pri čemu broj dekodiranih bajtova mora tačno da odgovara reviziji šifrovanja. Za revizije 2 do 4 očekivana dužina dolazi iz /Length u encryption dictionary: višekratnik od 8 bitova između 5 i 16 bajtova, sa podrazumevanih 40 bitova kada /Length nedostaje. Za revizije 5 do 7 ona je tačno 32 bajta i nema pregovaranja. Prefiks 0x, neparan broj cifara, više od 64 heksadecimalna znaka, nepoznat bit u Options ili dokument koji uopšte nije šifrovan daju isti ishod: handle 0 i LastErrorCode postavljen na PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID, što je 425. Strogoća je namerna. Blagi parser koji ukloni razmake i dopuni kratak ulaz nulama rado će pretvoriti skraćeni paste sa clipboard-a u kredencijal, a zatim će otkazati mnogo kasnije i mnogo manje jasno

Var
  Lib: TPDFlib;
  FileHandle, PageRef: Integer;
Begin
  Lib:= TPDFlib.Create;
  Try
    // KeyHex ima 32 heksadecimalna znaka za AES-128 R4, 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 grešaka ostaju različiti kako bi batch posao mogao da razdvoji grešku operatera od problema sa dokaznim materijalom: 411 kada fajl ne postoji, 401 kada ne može da se otvori za čitanje, 409 kada je cross-reference struktura pokvarena. Sve što je povezano sa ključem sabija se u 425, namerno, jer bi raw-key entry point koji prijavljuje koji deo ključa je pogrešan bio oracle

Šta verifikacija zaista dokazuje?

PDF Library for Delphi dokazuje da prosleđeni ključ pripada ovom dokumentu, koristeći proverivač koji encryption dictionary već sadrži, a provera se razlikuje po reviziji. Revizija 2 ponovo računa RC4 šifrovanje 32-bajtne standardne padding niske i poredi svih 32 bajta sa /U. Revizije 3 i 4 hash-uju padding zajedno sa ID-jem fajla, izvršavaju RC4 prolaz i 19 rundi izvedenih XOR-om, pa porede prvih 16 bajtova /U. Revizije 5 do 7 dešifruju 16-bajtnu nisku /Perms sa nulom kao IV-jem i istovremeno proveravaju četiri nezavisne stvari: little-endian permission word prema /P, četiri bajta FF na pozicijama 5 do 8, zastavicu encrypt-metadata kao T ili F i marker adb na pozicijama 10 do 12

Kada proverivač postoji, ali se ne poklapa, otvaranje se bezuslovno odbija. To vredi reći jasno jer je upravo to garancija na kojoj počiva cela funkcija. Obratite pažnju i na ono što verifikacija nije: ona kaže da ključ dešifruje ovaj fajl, a ne da vam je neko dozvolio da ga koristite. Permission word izvučen iz /Perms jeste dokaz o ključu, a ne dozvola, i ako želite da znate šta dokument zaista tvrdi da dopušta, to je zaseban posao za audit šifrovanja i dozvola. Problemi normalizacije na strani lozinke, kao što je SASLprep obrada AES-256 lozinki koje nisu ASCII, ovde se jednostavno ne pojavljuju jer nijedan string ne stiže do hash-a

Kada se primenjuje PDF_RAW_KEY_ALLOW_UNVERIFIED?

PDF_RAW_KEY_ALLOW_UNVERIFIED pokriva tačno jednu situaciju: dokument nema upotrebljiv proverivač, zato što /Perms nedostaje ili nema 16 bajtova, ili je /U prekratak za poređenje. Ne može da preglasa neuspešan dokaz. Ako oštetite jednu heksadecimalnu cifru unutar /Perms i uz uključenu opciju prosledite ispravan ključ, PDF Library for Delphi i dalje vraća 0 i 425. Ako uz uključenu opciju prosledite ključ od 32 nulta bajta prema netaknutom proverivaču, odgovor je isti. Opcija ublažava odsustvo dokaza, nikada protivrečnost dokazu. Pošto su oporavljeno otvaranje i verifikovano otvaranje različita epistemološka stanja, prijavljuju se zasebno, a ne sabijaju u povratnu vrednost, i DAGetEncryptionKeyValidation uzima otvoreni handle i odgovara jednom od tri konstanta:

  • PDF_RAW_KEY_VALIDATION_VERIFIED (1) znači da je proverivač postojao i da se poklopio
  • PDF_RAW_KEY_VALIDATION_UNVERIFIED (2) znači da je ključ prihvaćen samo zato što proverivač nije mogao da se proceni i što je pozivalac izričito zatražio tu politiku
  • PDF_RAW_KEY_VALIDATION_NONE (0) prijavljuje običan handle otvoren lozinkom
FileHandle:= Lib.DAOpenFileWithEncryptionKey(CaseFile, KeyHex);
If (FileHandle= 0)And
   (Lib.LastErrorCode= PDFLIB_ERROR_RAW_ENCRYPTION_KEY_INVALID) Then
  // nema proverivača u ovom fajlu? pokušaj ponovo uz eksplicitnu 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;

Read-only po konstrukciji i vlasništvo nad stream-om

Ulazna tačka za fajl sa sirovim ključem uvek otvara izvor sa fmOpenRead or fmShareDenyWrite i čitav Direct Access lanac označava kao read-only, pa DAAppendFile odbija pisanje u mestu i vraća 2 umesto da pokuša inkrementalno ažuriranje. To nije politika o kojoj možete pregovarati sa handle-om; postavlja se u konstruktoru pre nego što se fajl uopšte parsira. Kod dokaznog materijala važno je da bajtovi izvora ostanu identični nakon zatvaranja handle-a, a regression suite to potvrđuje na fixture-u za AES-128 reviziju 4 i na fixture-u za AES-256 reviziju 6. Stream ulazna tačka isto se ponaša pri upisu i dodaje još jedno pravilo: DAOpenFromStreamWithEncryptionKey nikada ne preuzima vlasništvo, pa DACloseFile ostavlja vaš TStream živim i vi ga sami oslobađate

Source:= TMemoryStream.Create;
Try
  Source.LoadFromFile(ArchivePath);
  Source.Position:= 0;
  FileHandle:= Lib.DAOpenFromStreamWithEncryptionKey(Source, KeyHex);
  If FileHandle<> 0 Then
  Try
    // 2 = read-only handle: izvezi drugde, nikada ne dodaj u dokazni materijal
    Assert(Lib.DAAppendFile(FileHandle)= 2);
    Harvest(Lib, FileHandle);
  Finally
    Lib.DACloseFile(FileHandle);
  End;
Finally
  Source.Free;   // handle nikada nije posedovao ovaj stream
End;

Higijena ključa i ono što DLL izvozi

Zatvaranje lanca prepisuje ključ fajla, keš lozinke i izvedene ključeve objekata, a dekodirani ključ se briše u Finally bloku same ulazne tačke bez obzira na to da li je otvaranje uspelo. Iza toga postoji nijansa koja je odnela stvarno vreme za debagovanje: svaka kopija koja mora da preživi eksplicitno se klonira kombinacijom SetLength i Move, umesto dodeljivanja. Ako u Delphi-ju jedan AnsiString dodelite drugom, oba imena dele isti bafer uz copy-on-write, pa bi brisanje na strani pozivaoca obrisalo ključ koji crypt handler još koristi, a dokument bi se dešifrovao u đubre iz razloga koji nijedan stack trace ne bi objasnio. Samo file-based ulazne tačke prelaze DLL granicu, u wide i ANSI oblicima, zajedno sa access-om za status validacije; varijanta sa TStream ostaje samo za Delphi, jer zavisi od životnog ciklusa Delphi objekta i semantike referenci koja nema poštenu reprezentaciju u ravnom C ABI-ju. Ako je vaš recovery alat DLL klijent, planirajte staging u privremeni fajl i njegovo brisanje pod istim kontrolama koje primenjujete na ključ

Prema heksadecimalnom ključu postupajte kao prema kredencijalu, uz pravila rukovanja koja biste dali lozinci dokumenta, a status validacije čuvajte u logu koji prati lanac čuvanja dokaza, kako bi kasniji čitalac mogao da razlikuje verifikovanu ekstrakciju od one bez potvrde. Ako procenjujete Delphi PDF komponentu za forenziku, masovnu arhivu ili migraciju DRM-a, ulazne tačke sa sirovim ključem, read-only garancija i površina za audit šifrovanja pripadaju istoj biblioteci, a punu listu funkcija možete pročitati na stranici proizvoda PDF Library for Delphi