Tehnički članak

Revizija enkripcije i dozvola PDF-a u Delphi-ju pomoću PDFlibPas-a

Zastavica dozvole (permission flag) nije bezbednosni mehanizam. Bit koji kaže "zabranjeno kopiranje" živi unutar istog rečnika /Encrypt kao i sama kriptografija, što mu daje privid sprovođenja pravila koji zapravo nema, i onog trenutka kada ovo dvoje počnete da posmatrate kao jedno, vaša revizija (audit) počinje da daje pogrešne odgovore. Jedino pitanje koje vredi postaviti u vezi sa jednim PDF-om nije "da li je šifrovan". Ono je specifičnije i teže: koji algoritam, koja revizija rukovaoca bezbednošću (security handler revision), koja od dve lozinke je postavljena, koji bitovi dozvola su zahtevani i koje delove datoteke enkripcija zapravo dotiče. Datoteka može biti formalno šifrovana, a praktično otvorena. Može odbiti da bude pročitana, a ipak ostaviti svoje metapodatke u čistom tekstu (plaintext). Može zaključati štampanje u zastavici koju je bilo koji pregledač slobodan da ignoriše. Revizija PDF-a znači odvojeno rešavanje svakog od ovih pitanja, a PDFlibPas, losLab-ov PDF mehanizam za Delphi i C++Builder, izlaže svako od njih i kroz ravni API sa celobrojnim hendlerima i kroz tipizirani sloj klasa

Šta rečnik /Encrypt zapravo beleži

Standard ISO 32000-1 §7.6 definiše bezbednost dokumenata kroz nekoliko unosa u rečniku, a PDFlibPas ih preslikava jedan na jedan u zapisu TPDFEncryption. Verzija filtera V i revizija R biraju porodicu algoritama. Length nosi veličinu ključa. Bitovi dozvole se nalaze u P, stringovi za validaciju vlasničke (owner) i korisničke (user) lozinke u O i U (sa dodatim OE i UE za AES-256), zastavica EncryptMetadata ide uporedo, a još tri polja imenuju kriptografske filtere (crypt filters) primenjene na stringove, tokove (streams) i ugrađene datoteke respektivno

Vrednost ovog zapisa je u tome što on ništa ne tumači umesto vas. On vam vraća sirovi rečnik i prepušta vam da donesete zaključke, što je upravo ono što je potrebno jednoj reviziji. Slučaj "čisti tekst unutar šifrovanog" (plaintext-inside-encrypted) se pojavljuje u StringFilterIdentity i StreamFilterIdentity: kada je bilo koji od njih tačan, odgovarajući podaci prolaze kroz Identity filter netaknuti, bez obzira na to šta izveštava šifrovani status dokumenta. Skener koji se zaustavi na konstataciji "prisutan je /Encrypt rečnik" nazvaće takvu datoteku zaštićenom iako njeni stringovi i tokovi leže u čitljivom obliku. Ista nijansa upravlja metapodacima. Kada je EncryptMetadata netačno, XMP paket ostaje čitljiv svakom indeksatoru, dok sadržaj stranice nije, što je vredno znati onog trenutka kada se vaša pravila usmeravanja (routing rules) oslanjaju na polje naslova ili autora

Kratka bezbednosna provera pomoću ravnog API-ja

Za većinu procesnih linija (pipelines), četiri ravna poziva daju odgovore na svakodnevna pitanja. Poziv LoadFromFile vraća 1 u slučaju uspeha, a kada je dokument otvoren, inspektori enkripcije daju izveštaj u odnosu na njegovo dešifrovano stanje:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', UserPassword) <> 1 then
      raise Exception.Create('Open failed: wrong password or damaged file');
    Writeln('status    : ', PDF.EncryptionStatus);     // decrypted / encrypted / unknown
    Writeln('algorithm : ', PDF.EncryptionAlgorithm);  // RC4 vs AES family
    Writeln('strength  : ', PDF.EncryptionStrength);   // key length class
    Writeln('owner pw? : ', PDF.CheckPassword(CandidatePassword));
  finally
    PDF.Free;
  end;
end;

Poziv CheckPassword je važniji nego što to njegova jednoslojna signatura sugeriše. PDF definiše dve lozinke sa nejednakom snagom. Korisnička (user) lozinka je neophodna da bi se datoteka uopšte otvorila. Vlasnička (owner) lozinka daje puna prava i zaobilazi svaki bit dozvole. Bajtovi na disku su identični u oba slučaja, ali sesija otvorena pod vlasničkom lozinkom može da radi stvari koje sesija pod korisničkom lozinkom ne može, pa revizija koja ne beleži koji je akreditiv predstavljen beleži samo pola istine. Sloj klasa čini ovu razliku upitnom. Metode TPDFDocument.HasUserPassword and HasOwnerPassword prijavljuju šta datoteka zahteva, dok IsUserPassword i IsOwnerPassword prijavljuju koja lozinka je zapravo otvorila trenutnu sesiju. Zabeležite tu činjenicu. Nikada nemojte beležiti same vrednosti lozinki

Lestvica snage, gde "AES-256" označava dve stvari

Ravne funkcije Encrypt i EncryptFile uzimaju celobrojnu vrednost Strength sa pet značajnih vrednosti: 0 za 40-bitni RC4, 1 za 128-bitni RC4, 2 za 128-bitni AES čitljiv od verzije Acrobat 7, 3 za 256-bitni AES uveden sa verzijom Acrobat 9, i 4 za 256-bitni AES koji zahteva Acrobat X i noviji

Zanimljiv deo je to što su i 3 i 4 označeni kao AES-256, a ne predstavljaju istu šemu. Vrednost 3 se mapira u reviziju 5 bezbednosnog rukovaoca (security handler), što je prelazni dizajn koji je isporučio Acrobat 9 i koji ISO nikada nije usvojio. Vrednost 4 se mapira u reviziju 6, čija je funkcija izvođenja ključa (key-derivation function) ojačana i standardizovana u ISO 32000-2. Za dokument koji kreirate danas ne postoji razlog da izaberete 3 umesto 4. Za reviziju je ovaj jaz odlučujući: politika koja glasi "AES-256 prema standardu ISO 32000-2" zadovoljena je isključivo revizijom R6, a R5 datoteka koja sebe naziva AES-256 pada na toj politici dok prolazi kroz jednostavnu proveru jačine. Sloj klasa ih drži razdvojenim po nazivu, esAES256Bit za R5 naspram esAES256BitAcroX za R6, a svojstvo EncryptionAcroX odgovara na pitanje o reviziji pomoću jedne logičke vrednosti (boolean)

Bitovi dozvola i sitna slova o dužini ključa

Poziv EncodePermissions pakuje osam zastavica u ceo broj koji funkcije Encrypt i EncryptFile očekuju. Štampanje, kopiranje, izmena i dodavanje beleški čine osnovni skup. Popunjavanje polja, kopiranje radi pristupačnosti, sastavljanje (assemble) i štampanje u punom kvalitetu čine prošireni skup. Sitna slova, koja i sam demo primer enkripcije u biblioteci jasno navodi, jesu to da proširene četiri opcije stupaju na snagu tek na jačini od 128 bita i više. Zastavica za štampanje u punom kvalitetu potpada pod isto pravilo: obrišite je da biste forsirali štampanje niske rezolucije i 40-bitni dokument će vas ignorisati, jer to smanjenje kvaliteta takođe zahteva 128-bitnu ili jaču enkripciju. Kodirajte politiku "samo štampa niske rezolucije" u 40-bitnu datoteku i svaki pregledač će je ipak odštampati u punom kvalitetu

Dublje pitanje je ko zapravo primenjuje bilo koji od ovih bitova, a odgovor je — niko kome možete verovati. Dozvole su instrukcije za usaglašene čitače, a ne kriptografska ograničenja. Ključ za dešifrovanje je identičan bez obzira na to da li je kopiranje dozvoljeno ili zabranjeno, tako da zaključani skup dozvola samo održava poštenje kod poštenih pregledača. Čitač koji odluči da ignoriše ove bitove ne suočava se ni sa kakvom kriptografskom preprekom. Ako je obaveza sprečavanje ekstrakcije, a ne samo obeshrabrivanje iste, datoteci je potrebna korisnička lozinka, a radnom toku (workflow) su potrebne kontrole na nivou procesa oko nje, i revizorski izveštaj bi trebalo da navede pod kojim od ova dva režima se svaka datoteka zapravo nalazi, umesto da tretira zastavicu dozvole kao bravu

Postavljanje politike i dokazivanje da je primenjena

Primena enkripcije na postojeće datoteke ne zahteva njihovo učitavanje u stablo objekata. Metoda EncryptFile obrađuje ulaz u izlaz u jednom pozivu, a revizorska petlja ponovo otvara rezultat da bi potvrdila šta je završilo na disku. Isporučeni demo primer enkripcije prati isti oblik "upisivanje pa ponovno čitanje":

var
  PDF: TPDFlib;
  R: Integer;
begin
  PDF := TPDFlib.Create;
  try
    R := PDF.EncryptFile('in.pdf', 'out.pdf', 'owner-secret', 'user-secret', 4,
      PDF.EncodePermissions(1, 0, 0, 0,    // print allowed; copy/change/notes denied
                            0, 0, 0, 1));  // extended set: full-quality print only
    if (R = 1) and (PDF.LoadFromFile('out.pdf', 'user-secret') = 1) then
    begin
      Writeln('algorithm = ', PDF.EncryptionAlgorithm);
      Writeln('strength  = ', PDF.EncryptionStrength);
      Writeln('owner pw accepted: ', PDF.CheckPassword('owner-secret'));
    end;
  finally
    PDF.Free;
  end;
end;

Timovi koji rade na nivou dokumenata dobijaju istu operaciju sa tipiziranim skupovima umesto pakovanja bitova, što prolazi reviziju koda sa daleko manje napora:

if not Doc.Encrypt('owner-secret', 'user-secret', esAES256BitAcroX,
  [ppCanPrint], [ppCanPrintFull]) then
  raise Exception.Create('Encryption failed');

U svakom slučaju, korak ponovnog čitanja nije opciona ceremonija. On detektuje greške u implementaciji koje bi se inače pojavile mesecima kasnije na mašini klijenta: stara verzija biblioteke koja tiho degradira zahtevanu jačinu, izlazna putanja koja nikada nije upisana jer je direktorijum bio samo za čitanje, ili ceo broj dozvola čiji su argumenti otišli pogrešnim redosledom. Sva tri slučaja prolaze lokalni test ("smoke test"), a padaju u realnom radu, dok ponovno otvaranje izlaza pretvara svaki od njih u izuzetak koji vidite tokom pokretanja koje je kreiralo datoteku. Metoda GetEncryptionFingerprint vraća kompaktan iznos koji možete sačuvati uz zapis o poslu, tako da kasnija poređenja mogu reći da li dva izlaza dele istu konfiguraciju enkripcije bez ponovnog otvaranja bilo kojeg od njih

Lažno pozitivni rezultati revizije za koje vredi pisati kod

Nekoliko šablona pouzdano navodi bezbednosne skenere na pogrešan zaključak, a svaki od njih proizilazi iz sažimanja višedelnog pitanja u odgovor tipa da-ili-ne. Identity crypt filter je najčistiji primer. Rečnik /Encrypt je prisutan, datoteka se prijavljuje kao šifrovana, a ipak stringovi i tokovi prolaze kroz Identity filter nepromenjeni, pa je stvarni sadržaj u čistom tekstu. Rešenje je čitanje svojstava StringFilterIdentity i StreamFilterIdentity pre nego što bilo šta proglasite zaštićenim

Podela metapodataka je suptilnija. Zastavica EncryptMetadata se može razlikovati od ostatka dokumenta u oba smera, ostavljajući šifrovanu datoteku sa čitljivim XMP paketom ili, ređe, obrnuto. Tvrdnja "datoteka je šifrovana" ne govori ništa o tome da li su šifrovani i njeni metapodaci, što postaje važno onog trenutka kada indeksator ili pravilo usmeravanja posegne za naslovom. Ugrađene datoteke dodaju treću osu: PDF dozvoljava namenski filter šifrovanja samo za priloge, tako da prilozi mogu biti jedini šifrovani deo inače otvorenog dokumenta, ili jedini deo u čistom tekstu unutar šifrovanog dokumenta. Zabeležite ova tri dodeljena filtera kao odvojena polja za stringove, tokove i ugrađene datoteke, i nijedna od ovih zamki vas ne može uhvatiti. Sačuvajte jednu logičku vrednost (boolean) i pogrešan poziv je samo pitanje vremena

Uklanjanje enkripcije i njen izbor za nove datoteke

Revizija se često završava odlukom o uklanjanju zaštite, a mehanika tu ne predstavlja prepreku. Metoda DecryptFile(InputFileName, OutputFileName, Password) upisuje dešifrovanu kopiju bez potpunog učitavanja, a poziv Decrypt na učitanom dokumentu radi isto to u memoriji kada je datoteka već otvorena. Oba zahtevaju važeću lozinku. Nijedan ne zaobilazi kriptografiju. Prava prepreka je bezbednosna politika, a ne kod, pa učinite da vaša pravila prijema jasno definišu kada je uklanjanje dozvoljeno i zabeležite klasu lozinke koja je to odobrila, jer sam tehnički korak ne ostavlja nikakav trag

Izbor za novi izlaz je uži nego što sugeriše pet vrednosti jačine (Strength). Koristite jačinu 4, AES-256 revizija 6, osim ako ne morate da otvarate datoteku u pregledačima starijim od verzije Acrobat X. Jačina 2, AES-128, predstavlja pragmatičan minimum za stariju flotu pregledača koja se ne može nadograditi. RC4 opcije 0 i 1 su tu da biste mogli da čitate i vršite reviziju istorijskih arhiva, a ne da biste sa njima proizvodili bilo šta novo. Posezanje za njima u projektu iz 2026. godini znak je da je zahtev iz prethodnog procesa zastareo

Stanje enkripcije se direktno prenosi na odluke o potpisivanju, pošto radni sto koji validira i potpisuje dokumente zahteva istu disciplinu ponovnog čitanja na koju se oslanja i ova revizija. Ta tema je pokrivena u članku o radnom stolu za usklađenost i potpisivanje. Kada se grupa operacija primenjuje preko EncryptFile na hiljadama velikih dokumenata, vodič za direktan pristup velikim PDF-ovima pokazuje kako zadržati potrošnju memorije ravnom tokom rada. Kompletna referenca za API enkripcije nalazi se na stranici proizvoda PDFlibPas