Ta en fakturaPDF som allerede bærer AES-256-kryptering og be PDFium-komponenten for Delphi og C++Builder (PDFiumPas) om å stemple den PDF/A for arkivbevaring, eller signere den med PAdES, gjennom en inkrementell oppdatering snarere enn en full omskriving. Biblioteket kommer ikke dit ved å lappe de krypterte bytene direkte: dens seks konformitets-markør-injektorer oppdager en eksisterende /Encrypt-oppføring og lar kilden passere gjennom byte-for-byte uendret, og dens PAdES-signerer kaster et unntak i stedet for å utstede en signatur ingen validator vil akseptere
Det er et annet spørsmål enn å revidere en PDF man ikke selv opprettet for skjult risiko, noe som er sin egen skrivebeskyttede øvelse. Denne artikkelen handler om skrivesiden av den samme tillitsgrensen: hva din egen kode har lov til å gjøre med en fil hvis bytes allerede er låst bak noen andres passord, i det øyeblikket den koden prøver å legge til noe som helst i etterkant
Hva krever ISO 32000-1 når man oppdaterer en kryptert PDF?
ISO 32000-1 §7.5.6 krever at en inkrementell oppdaterings trailer gjentar hver oppføring fra den forrige traileren unntatt /Prev, og Tabell 15 lister /Encrypt blant oppføringene en trailer kan bære. Dropp den fra den nye traileren, og en konform leser har ingen grunn til å tvile på utelatelsen: den nyeste traileren er autoritativ, så en leser som ikke finner noen /Encrypt der, bestemmer at hele filen er ukryptert og prøver å parse den eldre, fortsatt kryptere-kroppen som rene bytes. Behold /Encrypt i den nye traileren, men skriv oppdateringens egne objekter som klartekst, og feilen flytter seg bare ett trinn senere: leseren oppdager kryptering korrekt, kjører hvert objekt den rører, gjennom filens chiffer, inkludert de nye som aldri var kryptert i utgangspunktet, og får støy tilbake for innhold som var perfekt lesbart før dekryptering rørte det. Enten feil produserer en fil som ser ut som en normal, velformet inkrementell oppdatering på byte-nivå, helt til en konform leser åpner den
Seks markør-injektorer, én v2.14.2-krypteringssperre
PDFiumPas leverer seks byte-nivå-markør-injektorer, én for hver ISO PDF-delmengde den kan merke: PDF/A (ISO 19005), PDF/X (ISO 15930), PDF/UA (ISO 14289-1), PDF/E-1 (ISO 24517-1), PDF/R-1 (ISO 23504-1), og PDF/VT-1 (ISO 16612-2). Hver av dem tar bytene PDFiums egen FPDF_SaveAsCopy allerede skrev, og legger en andre, mindre inkrementell oppdatering oppå dem: en ny XMP-metadata-strøm, en katalog-ordbok-redigering som peker på den, og for de trykk-orienterte delmengdene en OutputIntent og ICC-profil. Fra og med v2.14.2 leser hver eneste av InjectPdfAMarkers, InjectPdfXMarkers, InjectPdfUaMarkers, InjectPdfEMarkers, InjectPdfRMarkers, og InjectPdfVTMarkers kildetraileren først, og hvis den rapporterer en eksisterende /Encrypt-oppføring, kopierer kilden gjennom til destinasjonsstrømmen umodifisert og returnerer umiddelbart. Ingen XMP, ingen OutputIntent, ingen katalog-redigering — kalleren får den opprinnelige filen tilbake, byte for byte
var
Src, Dst: TFileStream;
Opts: TPdfXSaveOptions;
begin
Src := TFileStream.Create('signed-encrypted-proof.pdf', fmOpenRead);
Dst := TFileStream.Create('pdfx-attempt.pdf', fmCreate);
try
Opts.Conformance := pxc4;
InjectPdfXMarkers(Src, Dst, Opts);
// pdfx-attempt.pdf is byte-identical to the source: still encrypted,
// no /GTS_PDFXVersion, no OutputIntent. Nothing was written, and
// nothing was corrupted either
finally
Dst.Free;
Src.Free;
end;
end;
Tillatt å være kryptert er ikke det samme som trygt å injisere inn i
PDF/E-1 og PDF/R-1 tillater begge eksplisitt at vertsdokumentet deres krypteres på spesifikasjonsnivå, noe som leser som et unntak inntil man ser hva som faktisk må skje på disk. ISO 24517-1 §6.3 tillater kryptering for PDF/E-1, og ISO 23504-1 §6.2.3 tillater det for PDF/R-1 forutsatt at headeren deklarerer %PDF-2.0. Ingen av klausulene sier noe om hvorvidt en byte-nivå-etterbehandler trygt kan legge et klartekst-objekt til den krypterte containeren, og det kan den ikke, av de samme §7.5.6-grunnene som gjelder for hver annen delmengde. PDFiumPas' egne konformitetsvalidatorer for disse to profilene, ValidatePdfECompliance og ValidatePdfRCompliance, registrerer tilstedeværelsen av /Encrypt med hensikt uten å flagge det som en defekt, noe som er korrekt for en skrivebeskyttet validator som aldri skriver en byte. Det er også et lett mønster å skumme forbi og anta at søster-injektoren ikke trenger noen separat vakt, når injektoren er den ene funksjonen i paret som faktisk må nekte
Dekrypterer SaveAsPdfX dokumentet ditt stille?
Ja, hver gang man går gjennom de offentlige bekvemmelighetsmetodene i stedet for å kalle en injektor direkte. Hver eneste av TPdf.SaveAsPdfA, SaveAsPdfX, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfR, og SaveAsPdfVT gjengir det gjeldende dokumentet til en midlertidig strøm med SaveAs(Tmp, saRemoveSecurity) før den overleverer de bytene til den tilhørende injektoren. saRemoveSecurity kartlegges mot PDFiums eget FPDF_REMOVE_SECURITY-flagg, så den midlertidige kopien injektoren mottar, var aldri kryptert i utgangspunktet, og injektorens /Encrypt-vakt har aldri noen grunn til å utløses. Utdataen bærer dine PDF/A-, PDF/X-, PDF/UA-, PDF/E-1-, PDF/R-1-, eller PDF/VT-1-markører, men den er ikke lenger beskyttet av hvilket som helst passord som åpnet kilden
Den avveiningen er usynlig inntil noen nedstrøms åpner den «beskyttede» arkivkopien uten passord og legger merke til at den bare fungerer. Løsningen er ikke et annet metodekall; PDFiumPas har ingen saAddSecurity-motpart å pare med saRemoveSecurity, fordi den underliggende PDFium-motoren aldri ble bygget for å skrive ny kryptering, bare for å fjerne den. Hvis begge egenskapene betyr noe for én fil, må kryptering være et separat trinn man selv eier, anvendt etter konformitetsmarkørene, ikke foldet inn i det samme SaveAsPdfA-kallet
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.Password := 'open-secret'; // needed to open the source at all
Pdf.FileName := 'signed-encrypted-invoice.pdf';
Pdf.LoadDocument;
Pdf.SaveAsPdfA('invoice-pdfa.pdf', pac2b);
// invoice-pdfa.pdf now declares PDF/A-2b, but SaveAs(saRemoveSecurity)
// ran first inside SaveAsPdfA: the output opens without a password
finally
Pdf.Free;
end;
end;
Hva skjer når man signerer en kryptert PDF med PAdES?
PDFiumPas nekter rett ut, snarere enn å droppe forespørselen stille slik en markør-injektor gjør. Både TPdf.SignPades og SignPadesToStream ruter gjennom en intern SignPadesBytes, og det første den gjør etter å ha parset kildetraileren, er å sjekke etter /Encrypt. Hvis oppføringen er til stede, kaster den EPadesCrypto med meldingen «SignPadesBytes: the source document is encrypted; remove encryption before signing» i stedet for å fortsette videre. InjectPadesDssMarkers, funksjonen som bygger inn sertifikater, OCSP-svar, og CRL-er for langtidsvalidering, anvender den identiske sjekken av den identiske grunnen, med sin egen melding: «InjectPadesDssMarkers: the source document is encrypted; remove encryption before embedding DSS validation material»
Resonnementet her er strengere enn markør-injektorenes gjennomslipp, og med hensikt. Et stille gjennomslipp er trygt for et PDF/A-stempel fordi å hoppe over det etterlater deg med den samme gyldige PDF-en man startet med, bare umerket. Signering kan ikke feile like stille: en signatur som stille aldri ble lagt til, ser, for enhver kallende kode som bare sjekker et boolsk resultat, nøyaktig ut som en signatur som ble lagt til vellykket. EPadesCrypto nedstammer fra den vanlige Exception-klassen, så å fange den er normal unntaksbehandling, ikke en spesiell kontrollflyt-konvensjon man må lære
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.Password := 'open-secret';
Pdf.FileName := 'encrypted-contract.pdf';
Pdf.LoadDocument;
try
Pdf.SignPades('encrypted-contract-signed.pdf', 'A1B2C3D4E5F6...');
except
on E: EPadesCrypto do
// E.Message: 'SignPadesBytes: the source document is encrypted;
// remove encryption before signing'
raise Exception.Create('Decrypt the contract before signing: '+ E.Message);
end;
finally
Pdf.Free;
end;
end;
Å sekvensere konformitetsstempler, signaturer, og kryptering
Den praktiske løsningen er rekkefølge, ikke et annet bibliotek. Anvend PDF/A-, PDF/X-, PDF/UA-, PDF/E-1-, PDF/R-1-, eller PDF/VT-1-markører først, legg til en eventuell PAdES-signatur deretter, og først da kjør hvilket som helst trinn i pipelinen din som faktisk eier kryptering, enten det er en dedikert PDF-skriver, en signeringsapparat, eller din egen AES-implementasjon. PDFiumPas' inkrementelle-oppdaterings-lag passer naturlig inn i midten av den sekvensen, og legger til små, målrettede objekter på en fil som ellers er ferdig, og kryptering hører hjemme på slutten, nettopp fordi det er den ene operasjonen i kjeden PDFiumPas selv ikke kan utføre eller reversere
Ingenting av dette endrer hvordan PDFiumPas leser traileren og kryssreferanse-dataen enhver inkrementell oppdatering avhenger av, noe som er sin egen kilde til subtilitet så snart xref-strømmer kommer inn i bildet; å validere en PDFs objekt- og xref-strømmer dekker hvordan den samme trailer-lesings-veien håndterer PDF 1.5+ komprimerte strukturer. Og når et dokument er klart for noe sterkere enn et konformitetsstempel, tar å signere en PDF med en PAdES B-B-signatur i Delphi over med SignPades nøyaktig der denne artikkelen slutter
Markør-injektorene og SignPades-metodene beskrevet her følger med som en del av PDFium-komponenten for Delphi og C++Builder, sammen med gjengivelsen og den skrivebeskyttede inspeksjonen PDFium leverer nativt