HotPDF Delphi PDF komponentas ISO 32000-1 §7.6.5 crypt filtrų modelį įgyvendina kaip tris nepriklausomas politikas, o ne vieną jungiklį: ConfigureCryptFilterDefaults atskirai priskiria eilučių filtrą /StrF, srautų filtrą /StmF ir įterptųjų failų filtrą /EFF, SetStreamCryptFilter perrašo vieną srautą, o GetLoadedCryptFilterInfo praneša, ką deklaruoja įeinantis failas. Dauguma užšifruotų PDF sąveikos klaidų gyvena tarpuose tarp šių trijų
Štai gedimas, kuris atveda žmones į šį sluoksnį. Komanda pateikia dokumentą, kurio puslapio turinys turi likti įskaitomas tolesniam įrankiui, o prisegtas turinys – ne, todėl nustato /EFF /StdCF ir palieka /StmF /Identity. Acrobat jį atveria be problemų. Suderinama trečiosios šalies skaityklė priedą grąžina kaip šifruoto teksto šiukšles, nes /EFF yra gamintojo pusės politika, nurodanti, kuris filtras taikomas įterptiesiems failams, o bendras skaitytuvas nepažymėtą srautą vis tiek sprendžia per /StmF. Pataisymas nėra kita /EFF reikšmė. Pataisymas – aiškus /Crypt filtras pačiame įterptojo failo sraute
Ką iš tikrųjų valdo crypt filtrų sluoksnis
Crypt filtrai yra tarp šifravimo algoritmo ir objektų grafiko, jie sprendžia, kuriuos objektus algoritmas paliečia, o ne kaip jis veikia. Šifravimo žodyne esantis /CF žodynas vardus susieja su filtrų apibrėžtimis, kurių kiekviena turi /CFM metodą, pasirenkamą /Length ir /AuthEvent. Tada trys aukščiausio lygio įrašai /StrF, /StmF ir /EFF parenka, kuris iš pavadintų filtrų taikomas eilutėms, srautams be aiškaus filtro ir įterptiesiems failams. HotPDF sąmoningai riboja, ką jo integruotos tvarkyklės gali rašyti. ConfigureCryptFilterDefaults priima tik aktyviai tvarkyklei rezervuotus vardus: Standard saugumo tvarkyklė išveda /StdCF arba /Identity, viešojo rakto tvarkyklė – /DefaultCryptFilter arba /Identity, o bet kuris kitas vardas iškvietimo vietoje kelia EArgumentException. Kitų vardų filtrus, kuriuos įrašė išoriniai gamintojai, HotPDF vis tiek išsaugo įkėlimo, tikrinimo ir suderinamumo perrašymo keliuose, todėl kaip rašyklė ji konservatyvi, o kaip skaityklė – atlaidi. Taikomi dar du saugikliai: iškvietimas kelia EInvalidOpException, kai dokumento serializavimas jau prasidėjo, ir dar kartą, jei dokumentas yra prieauginio atnaujinimo būsenoje, nes šifravimo politikos negalima pakeisti tarp to paties failo redakcijų
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;
// eilutės šifruojamos, puslapio srautai atviri, priedai šifruojami
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;
Vieną apribojimą verta įvardyti iš anksto, nes jis tikrinamas vėlai ir nustebina. Pavadintiems HotPDF crypt filtrams būtinas aes128, aes256 arba aesgcm dokumento šifravimas. Sukonfigūruokite filtro politiką virš RC4 k40 arba k128 ir šifravimo įjungimo metu veikiantis tikrinimo etapas kelia klaidą, o ne tyliai pakeičia rakto tipą. Tai ta pati dizaino laikysena kaip ir likusiame AES-256 PDF šifravimo kelyje Delphi aplinkoje: atmesti dviprasmišką konfigūraciją, o ne spėti, ką iškvietėjas turėjo omenyje
Kodėl /Length įrašas reiškia du skirtingus dalykus?
Nes specifikacija jį apibrėžia dviem skirtingais vienetais, priklausomai nuo saugumo tvarkyklės, o HotPDF turi gerbti abu. Crypt filtro žodyne, kurio /CFM yra /V2, /Length Standard saugumo tvarkyklėje išreiškiamas baitais, o viešojo rakto tvarkyklėje – bitais. Šifravimo žodyno /Length, esantis šalia /V (ISO 32000-1 §7.6.2), visada išreiškiamas bitais. Perskaitę filtro žodyną su /Length 16, Standard faile turite 128 bitų raktą, o viešojo rakto faile – atmestą failą. Įkeltą konfigūraciją HotPDF normalizuoja. /V2 filtro /Length padauginamas iš aštuonių tik tada, kai failas nešifruotas viešuoju raktu, jei filtro savas įrašas praleistas, grįžtama prie dokumento lygio /Length, o rezultatas saugomas THPDFCryptFilterInfo.KeyLengthBits. AESV2 pririštas prie 128 bitų, o AESV3 ir AESV4 – prie 256, nes šie metodai neturi derinamo rakto ilgio. Griežtoji dalis ateina toliau: priimami tik 40 ir 128 bitų /V2. Filtras, kurio rezultatas yra bet koks kitas ilgis, pranešamas kaip nepasiekiamas ir operacija nepavyksta, o ne suapvalinama iki 128, tikintis, kad dauguma gamintojų vis tiek turėjo omenyje 128. Tylus rakto ilgio normalizavimas yra būdas išsiųsti failą, kuris iššifruojamas jūsų kompiuteryje ir niekur kitur
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 ir /StmF pagal nutylėjimą yra Identity; /EFF pagal nutylėjimą yra /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;
Ką garantuoja /CFM /None ir kuo /Identity skiriasi?
Jie pasiekia tą patį rezultatą skirtingais keliais, o jų supainiojimas sugadina paieškas. Pavadintas filtras, kurio /CFM yra /None, ir pavadintas filtras, kuriame /CFM visai praleistas, abu reiškia, kad tas filtras neatlieka jokio šifravimo ar iššifravimo – HotPDF trūkstamą įrašą prieš spręsdama susieja su None, todėl abu tampa hcfmNone su įrašytu nuliniu rakto ilgiu. /Identity yra kitokios rūšies: tai rezervuotas vardas, kuris visai apeina /CF paiešką, todėl dokumentas gali nurodyti /Identity jo niekur neapibrėždamas /CF. PDF vardai skiria didžiąsias ir mažąsias raides, todėl dar viena įgyvendinimo detalė neprivalo būti derybų objektas: jokia crypt filtro paieška negali ignoruoti raidžių registro. HotPDF /CF požodyno vardus, filtro /Length įrašą ir srauto /Type patikrą sprendžia skirdama raidžių registrą. Failas, kuris apibrėžia /stdcf, kai /StmF rodo į /StdCF, yra sugadintas, o laikant abu raktus vienodais aptinkama autoriaus klaida virstų tyliai kiekvienam dokumento srautui pritaikytu neteisingu raktu
Kaip užtikrinti /EFF įterptųjų failų srautuose
Kai /EFF skiriasi nuo /StmF, įterptojo failo srautui reikia aiškaus pradinio /Crypt įrašo jo /Filter ir atitinkančio /DecodeParms žodyno, kuriame /Name yra toje pačioje masyvo pozicijoje. HotPDF tai apskaičiuoja kiekvienam srautui išsaugojimo metu: aptinka /Type /EmbeddedFile, paveldi sukonfigūruotą įterptojo failo filtrą ir aiškų /Crypt žymeklį išveda tik tada, kai paveldėtas vardas skiriasi nuo efektyvaus numatytojo srauto. Kai /EFF ir /StmF sutampa, žymeklis nerašomas, nes skaitytuvas vis tiek išspręstų tą patį filtrą. Tada masyvo pozicija tampa tokia pat svarbi kaip vardas. Skaitydama srautą HotPDF ieško /Crypt įrašo /Filter, įrašo jo indeksą ir tame pačiame indekse ieško /DecodeParms masyve, kad rastų /Name. /Crypt, esantis 0 indekse, su parametrais 1 indekse išsprendžiamas į /Identity, o ne į jūsų filtrą. Dėl tos pačios priežasties rašyklė papildo parametrų masyvą null, kai srautas anksčiau turėjo /Filter, bet neturėjo /DecodeParms: pozicijos turi likti sulygiuotos
Po tuo slypi dar aštresni spąstai. Jei esamas /Filter arba /DecodeParms yra netiesioginis objektas – tai įprasta failuose, kuriuos kuria generatoriai, dalijantys vieną filtrų masyvą tarp daugelio srautų – įterpus /Crypt vietoje būtų pakeistas bendras filtrų grafas ir sugadintas kiekvienas kitas į jį rodantis srautas. HotPDF išsprendžia netiesioginį objektą ir pirmiausia klonuoja jį į tiesioginį, srautui privatų objektą, išvalydama objekto ir generacijos numerius, kad pradinis netiesioginis šaknis niekada nebūtų įdėtas į naują masyvą. Srautui, kuriame jau naudotas ASCIIHexDecode, serializuotas rezultatas yra /Filter [ /Crypt /ASCIIHexDecode ] su /DecodeParms [ << /Type /CryptFilterDecodeParms /Name /StdCF >> ... ]. Ta pati pozicijų disciplina valdo ir kiekvieną kitą filtrų grandinę, įskaitant tas, kuriomis einate išgaudami vaizdus iš įkelto PDF per jų dekodavimo filtrus
// Editor jau turi įkeltą dokumentą, o ContentStream yra
// THPDFStreamObject, kurio /Filter yra netiesioginis /ASCIIHexDecode vardas
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');
// Tuščias vardas panaikina perrašą ir kito išsaugojimo metu pašalina
// pasenusį /Crypt įrašą kartu su jo dekodavimo parametrais
Editor.SetStreamCryptFilter(ContentStream, '');
Editor.SaveLoadedDocument('cleared.pdf');
Ar objektų srautai paveldi dokumento /Encrypt politiką?
Ne, o tokia prielaida yra patikimas būdas sukurti šiukšles. Objektų srautas turi laikytis tikrosios /StmF politikos arba savo aiškaus /Crypt žymeklio: vien /Encrypt žodyno buvimas nereiškia, kad kiekvienas /ObjStm konteineris tapo šifruotu tekstu. Dokumente su /StmF /Identity objektų srautai yra atviri, nors jo eilutės visiškai užšifruotos, o dekoderis, kuris vis tiek juos iššifruoja, į išskleidimo etapą paduoda tai, kas niekada nebuvo deflate išvestis
Verta dar kartą perskaityti pasekmę narių objektams. Pagal ISO 32000-1 §7.5.7 eilutės užšifruotame objektų sraute jau yra atviros, kai pats konteineris iššifruotas, todėl antras jų iššifravimas būtų dvigubas. HotPDF nuo to saugo tikrindama, ar kiekvieno 2 tipo objekto konteineris buvo šifruotas, ir praleisdama objektą, kai taip buvo, o praleidimus kaupia XRefProbeDecryptObjStmSkips kaip tiesioginį įrodymą, kad saugiklis suveikė. Kai konteineris buvo atviras, narių eilutės niekada nebuvo niekuo uždengtos, todėl HotPDF materializuoja tuos narius ir kiekvienam atskirai pritaiko /StrF, raktą sudarydama pagal nario objekto numerį ir generaciją, o ne pagal talpinančio /ObjStm objekto numerį. Apverskite šias taisykles mišrios politikos faile ir kiekviena eilutė kiekviename suglaudintame objekte taps triukšmu. Konteinerio lygio taisyklės plačiau aprašytos pastabose apie PDF objektų srautus ir prieauginius atnaujinimus
Kur HotPDF atsisako spėti
Crypt filtrų semantika neegzistuoja žemiau /V 4, todėl HotPDF aiškia klaida atmeta bet kokį srauto perrašą tokiame faile, užuot įrašiusi /Crypt žymeklį, kurio joks suderinamas skaitytuvas negerbtų. Tas pats galioja skaitymo pusėje: šifravimo žodynas su /V, mažesniu nei 4, išvalo visus tris įkeltų filtrų vardus, nes ten nėra ko pranešti. Dar trys ribos įgyvendintos sąmoningai:
- Nesutampantis su
Identitysrauto filtras viešojo rakto šifruotame dokumente atmetamas, nes konkrečiam srautui skirta politika viešojo rakto tvarkyklėje reikalautų konkrečiam srautui skirtos gavėjo apgaubo, kurio HotPDF dar neišveda - Viešojo rakto šifruoti įterptieji failai, kurių
/EFFskiriasi nuo efektyvaus/StmF, atmetami dėl tos pačios priežasties, o ne įrašomi tokia forma, kuri niekam neiššifruojama - AES-256 tiesioginio failo spartusis kelias taikomas tik tada, kai eilutės, srautai ir įterptieji failai išsprendžiami į tą patį crypt filtro metodą ir nė vienas failo objektas neturi aiškaus
/Crypt; mišri politika arba atviri metaduomenys priverčia grįžti į viso objektų grafo kelią
Nė vienas iš šių atvejų nėra našumo sprendimas. Jie žymi vietas, kur neteisingas spėjimas duoda PDF, kuris vienoje peržiūros programoje atsidaro, kitoje sugenda, o kūrėjui nieko nesako, kol klientas nepraneša. Atsisakymas ties ConfigureCryptFilterDefaults arba išsaugojimo metu kainuoja vieną išimtį, o tyliai neteisingai užrakintas įterptasis failas kainuoja palaikymo ciklą. Jei Delphi arba C++Builder kuriate programinę įrangą, kuri gamina arba vartoja užšifruotą PDF – selektyviai atvirus puslapio srautus su šifruotais priedais, PDF 2.0 užšifruotų duomenų apvalkalus arba sąveiką su failais, kurių crypt filtrų politikos pasirinkote ne jūs – čia aprašyta crypt filtrų API pateikiama naujausiame HotPDF Delphi PDF komponente kartu su šifravimo, objektų srautų ir prieauginių atnaujinimų keliais, kuriais ji remiasi