HotPDF Delphi PDF komponenta implementira model crypt filter-a iz ISO 32000-1 §7.6.5 kao tri nezavisne politike, a ne kao jedan prekidač: ConfigureCryptFilterDefaults zasebno dodeljuje string filter /StrF, stream filter /StmF i embedded-file filter /EFF, SetStreamCryptFilter prepisuje izbor za jedan stream, a GetLoadedCryptFilterInfo prijavljuje šta ulazni fajl deklariše. Većina interop grešaka kod šifrovanih PDF-ova živi u prazninama između ta tri izbora
Evo neuspeha koji ljude šalje u ovaj sloj. Tim isporuči dokument u kojem sadržaj stranice mora da ostane čitljiv nizvodnom alatu, ali priloženi payload ne sme, pa postavi /EFF /StdCF i ostavi /StmF /Identity. Acrobat ga otvara bez problema. Usklađeni reader treće strane vrati prilog kao šifrovano đubre, jer je /EFF politika na strani proizvođača o tome koji filter važi za embedded fajlove, dok opšti reader neoznačeni stream i dalje razrešava kroz /StmF. Popravka nije drugačija vrednost /EFF. Popravka je eksplicitni /Crypt filter na samom embedded-file stream-u
Čime crypt filter sloj zaista upravlja
Crypt filter-i stoje između algoritma šifrovanja i grafa objekata i odlučuju koje objekte algoritam dodiruje, a ne kako algoritam radi. /CF dictionary unutar encryption dictionary-ja mapira imena na definicije filter-a, a svaka nosi metod /CFM, opciono /Length i /AuthEvent. Tri vršna unosa /StrF, /StmF i /EFF zatim biraju koji imenovani filter važi za stringove, stream-ove bez eksplicitnog filter-a i embedded fajlove. HotPDF namerno ograničava ono što njegovi ugrađeni handler-i mogu da upišu. ConfigureCryptFilterDefaults prihvata samo rezervisana imena aktivnog handler-a: Standard security handler emituje /StdCF ili /Identity, public-key handler emituje /DefaultCryptFilter ili /Identity, a sve drugo podiže EArgumentException na mestu poziva. Filter-i koje spoljašnji proizvođači upišu pod drugim imenima i dalje se čuvaju pri učitavanju, inspekciji i compatibility-rewrite putanjama, pa je HotPDF konzervativan kao writer, a popustljiv kao reader. Važe još dve zaštite: poziv podiže EInvalidOpException kada serijalizacija dokumenta počne, kao i kada je dokument u inkrementalnom ažuriranju, jer se politika šifrovanja ne može menjati između revizija istog fajla
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := 'wrapper.pdf';
Pdf.OwnerPassword := 'owner-secret';
Pdf.UserPassword := 'open-secret';
Pdf.CryptKeyLength := aes128;
// stringovi šifrovani, stream-ovi stranice u otvorenom tekstu, prilozi šifrovani
Pdf.ConfigureCryptFilterDefaults('StdCF', 'Identity', 'StdCF');
Pdf.ActivateProtection := True;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(50, 50, 0, 'Visible stream operators');
Pdf.AddDocumentAttachment('payload.bin', 'Encrypted payload');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Jedno ograničenje vredi navesti odmah, jer se proverava kasno i iznenađuje ljude. Imenovani crypt filter-i u HotPDF-u zahtevaju aes128, aes256 ili aesgcm šifrovanje dokumenta. Konfigurišite politiku filter-a nad RC4 k40 ili k128 i validacioni prolaz koji se izvršava kada se šifrovanje uključi podiže grešku, umesto da nečujno promoviše tip ključa. To je isti stav kao i u ostatku putanje AES-256 PDF šifrovanja u Delphi-ju: odbiti dvosmislenu konfiguraciju umesto nagađanja šta je pozivalac mislio
Zašto unos /Length znači dve različite stvari?
Zato što ga specifikacija definiše u dve različite jedinice u zavisnosti od security handler-a, a HotPDF mora da poštuje obe. U crypt filter dictionary-ju čiji je /CFM /V2, unos /Length izražava se u bajtovima kod Standard security handler-a, a u bitovima kod public-key handler-a. /Length u encryption dictionary-ju koji stoji uz /V (ISO 32000-1 §7.6.2) uvek je u bitovima. Pročitajte filter dictionary sa /Length 16 i dobili ste ključ od 128 bitova u fajlu sa Standard handler-om, ali odbijen fajl kod public-key handler-a. HotPDF ovo normalizuje kada snimi učitanu konfiguraciju. Množi /Length filter-a /V2 sa osam samo kada fajl nije šifrovan public-key metodom, vraća se na /Length nivoa dokumenta kada filter nema svoj, i rezultat čuva u THPDFCryptFilterInfo.KeyLengthBits. AESV2 je fiksiran na 128 bitova, a AESV3 i AESV4 na 256, jer ti metodi nemaju pregovaračku veličinu ključa. Strogi deo dolazi zatim: prihvataju se samo /V2 od 40 i 128 bitova. Filter koji se razreši na bilo koju drugu dužinu prijavljuje se kao nedostupan i operacija pada, umesto da se zaokruži na 128 uz pretpostavku da su proizvođači uglavnom mislili na 128. Nečujna normalizacija dužine ključa način je da isporučite fajl koji se dešifruje na vašoj mašini, a nigde drugde
var
Reader: THotPDF;
Info: THPDFCryptFilterInfo;
I: Integer;
begin
Reader := THotPDF.Create(nil);
try
Reader.AutoLaunch := False;
if Reader.LoadFromFile('incoming.pdf', 'open-secret') <> 1 then
Exit;
// /StrF i /StmF podrazumevano su Identity; /EFF je podrazumevano /StmF
WriteLn(Reader.LoadedStringCryptFilterName); // StdCF
WriteLn(Reader.LoadedStreamCryptFilterName); // Identity
WriteLn(Reader.LoadedEmbeddedFileCryptFilterName); // StdCF
for I := 0 to Reader.GetLoadedCryptFilterCount - 1 do
if Reader.GetLoadedCryptFilterInfo(I, Info) then
if (Info.Method = hcfmV2) and
not (Info.KeyLengthBits in [40, 128]) then
raise Exception.CreateFmt(
'crypt filter /%s: unsupported V2 key length %d',
[String(Info.Name), Info.KeyLengthBits]);
finally
Reader.Free;
end;
end;
Šta garantuje /CFM /None i po čemu se /Identity razlikuje?
Do istog ishoda dolaze različitim putanjama, a njihovo mešanje lomi pretrage. Imenovani filter čiji je /CFM /None i imenovani filter kojem /CFM uopšte nedostaje oba znače da taj filter ne vrši šifrovanje ni dešifrovanje — HotPDF mapira nedostajući unos na None pre razrešavanja, pa oba završe na hcfmNone sa zabeleženom dužinom ključa nula. /Identity je drugačije vrste: to je rezervisano ime koje u potpunosti zaobilazi pretragu /CF, pa dokument može da referencira /Identity a da ga nigde ne definiše u /CF. PDF imena razlikuju velika i mala slova, što čini još jedan detalj implementacije neupitnim: nijedna pretraga crypt filter-a ne sme biti case-insensitive. HotPDF razrešava imena podrečnika /CF, unos /Length filter-a i proveru stream /Type kroz case-sensitive dictionary lookup. Fajl koji definiše /stdcf, dok /StmF pokazuje na /StdCF, neispravan je, a tretiranje ta dva ključa kao istog pretvorilo bi uočljivu grešku autora u pogrešan ključ koji se tiho primenjuje na svaki stream dokumenta
Kako da /EFF zaista važi na embedded-file stream-ovima
Kada se /EFF razlikuje od /StmF, embedded-file stream-u je potreban eksplicitni početni /Crypt unos u njegovom /Filter i odgovarajući /DecodeParms dictionary sa /Name na istoj poziciji niza. HotPDF ovo rešava po stream-u u trenutku čuvanja: otkriva /Type /EmbeddedFile, nasleđuje podešeni embedded-file filter i emituje eksplicitni /Crypt marker samo kada se to nasleđeno ime razlikuje od efektivnog podrazumevanog filter-a stream-a. Kada se /EFF i /StmF slažu, marker se ne upisuje, jer bi reader ionako razrešio isti filter. Pozicija u nizu tada je jednako važna kao i ime. Kada HotPDF ponovo čita stream, skenira /Filter za /Crypt unos, beleži njegov indeks i zatim traži /Name na istom indeksu u nizu /DecodeParms. /Crypt na indeksu 0 uparen sa parametrima na indeksu 1 razrešava se u /Identity, a ne u vaš filter. Zato writer popunjava parametarski niz null vrednošću kada je stream ranije imao /Filter, ali nije imao /DecodeParms: pozicije moraju ostati poravnate
Ispod toga postoji oštrija zamka. Ako su postojeći /Filter ili /DecodeParms indirektni objekti — što je uobičajeno u fajlovima generatora koji jedan niz filter-a dele između više stream-ova — umetanje /Crypt na mestu promenilo bi deljeni graf filter-a i pokvarilo svaki drugi stream koji na njega pokazuje. HotPDF razrešava indirektni objekat i prvo ga klonira u direktni objekat privatan za stream, brišući brojeve objekta i generacije tako da izvorni indirektni koren nikada ne bude ugrađen u novi niz. Za stream koji je već koristio ASCIIHexDecode, serijalizovani rezultat je /Filter [ /Crypt /ASCIIHexDecode ] sa /DecodeParms [ << /Type /CryptFilterDecodeParms /Name /StdCF >> ... ]. Ista disciplina pozicija upravlja svakim drugim lancem filter-a, uključujući one kroz koje prolazite pri izdvajanju slika iz učitanog PDF-a kroz njihove decode filter-e
// Editor već drži učitani dokument, a ContentStream je
// THPDFStreamObject čiji je /Filter indirektno /ASCIIHexDecode ime
Editor.OwnerPassword := 'owner-secret';
Editor.UserPassword := 'open-secret';
Editor.CryptKeyLength := aes128;
Editor.ConfigureCryptFilterDefaults('StdCF', 'Identity');
Editor.SetStreamCryptFilter(ContentStream, 'StdCF');
Editor.ActivateProtection := True;
Editor.SaveLoadedDocument('out.pdf');
// Prazno ime briše override i pri sledećem čuvanju uklanja zastareli /Crypt
// unos zajedno sa njegovim decode parametrima
Editor.SetStreamCryptFilter(ContentStream, '');
Editor.SaveLoadedDocument('cleared.pdf');
Da li object stream-ovi nasleđuju /Encrypt politiku dokumenta?
Ne, a pretpostavka da je nasleđuju siguran je način da proizvedete đubre. Object stream mora da prati stvarnu /StmF politiku ili sopstveni eksplicitni /Crypt marker: samo prisustvo /Encrypt dictionary-ja ne pretvara svaki /ObjStm kontejner u ciphertext. Dokument sa /StmF /Identity ima plaintext object stream-ove iako su njegovi stringovi potpuno šifrovani, a decoder koji ih ipak dešifruje prosleđuje inflate fazi ulaz koji nikada nije bio rezultat deflate-a
Posledica za članske objekte vredi pročitati dvaput. Prema ISO 32000-1 §7.5.7, stringovi unutar šifrovanog object stream-a već su plaintext kada se sam kontejner dešifruje, pa bi njihovo ponovno dešifrovanje bilo double-decrypt. HotPDF to štiti tako što proverava da li je kontejner svakog objekta tipa 2 bio šifrovan i preskače objekat kada jeste, a preskakanja broji u XRefProbeDecryptObjStmSkips kao direktan dokaz da se zaštita aktivirala. Kada je kontejner bio plaintext, članski stringovi nikada nisu bili pokriveni ničim, pa HotPDF materijalizuje te članove i na svaki pojedinačno primenjuje /StrF — sa ključem, baš kao što implementacija radi, prema broju objekta člana i generaciji, a ne prema broju objekta koji sadrži /ObjStm. Obrnite to na fajlu sa mešovitom politikom i svaki string u svakom kompresovanom objektu dekodiraće se u šum. Pravila na nivou kontejnera detaljnije su obrađena u beleškama o PDF object stream-ovima i inkrementalnim ažuriranjima
Gde HotPDF odbija da nagađa
Semantika crypt filter-a ne postoji ispod /V 4, pa HotPDF odbija svaki override po stream-u na takvom fajlu eksplicitnom greškom, umesto da upiše /Crypt marker koji nijedan usklađeni reader ne bi poštovao. Isto važi i pri čitanju: encryption dictionary sa /V ispod 4 briše sva tri učitana imena filter-a, jer tamo nema ničega što bi se prijavilo. Još tri granice namerno se sprovode:
- Filter po stream-u različit od
Identityna dokumentu šifrovanom public-key metodom odbija se, jer politika specifična za stream pod public-key handler-om zahteva envelope primaoca specifičan za stream, koji HotPDF još ne emituje - Embedded fajlovi šifrovani public-key metodom čiji se
/EFFrazlikuje od efektivnog/StmFodbijaju se iz istog razloga, umesto da se upišu u obliku koji niko ne može da dešifruje - Brza putanja AES-256 za direktne fajlove primenjuje se samo kada se stringovi, stream-ovi i embedded fajlovi razrešavaju na isti metod crypt filter-a i nijedan objekat u fajlu nema eksplicitni
/Crypt; mešovita politika ili plaintext metadata primoravaju fallback na punu putanju kroz graf objekata
Nijedna od ovih odluka nije odluka o performansama. One označavaju mesta na kojima pogrešno nagađanje daje PDF koji se otvara u jednom viewer-u, otkazuje u drugom i programeru ne daje nikakav signal sve dok se klijent ne javi. Odbijanje na ConfigureCryptFilterDefaults ili pri čuvanju košta jednog izuzetka; tiho pogrešno ključan embedded fajl košta jedan support ciklus. Ako pravite Delphi ili C++Builder softver koji proizvodi ili troši šifrovani PDF — selektivno plaintext sadržaj stranice sa šifrovanim prilozima, PDF 2.0 šifrovane payload wrapper-e ili interop sa fajlovima čije crypt filter politike niste vi birali — ovde opisani crypt filter API isporučuje se u aktuelnoj HotPDF Delphi PDF komponenti, zajedno sa putanjama za šifrovanje, object stream-ove i inkrementalna ažuriranja na kojima počiva