Ota lasku-PDF, joka jo kantaa AES-256-salausta, ja pyydä PDFium-komponenttia Delphille ja C++Builderille (PDFiumPas) leimaamaan se PDF/A-arkistointia varten, tai allekirjoittamaan se PAdES:llä, inkrementaalisen päivityksen kautta täyden uudelleenkirjoituksen sijaan. Kirjasto ei pääse sinne paikkaamalla salattuja tavuja suoraan: sen kuusi yhdenmukaisuusmerkin injektoria tunnistavat olemassa olevan /Encrypt-merkinnän ja päästävät lähteen läpi tavulleen muuttumattomana, ja sen PAdES-allekirjoittaja nostaa poikkeuksen sen sijaan, että tuottaisi allekirjoituksen, jota mikään validaattori ei hyväksy
Se on eri kysymys kuin PDF:n, jota et itse luonut, tarkastaminen piilotetun riskin varalta, mikä on oma vain luku -harjoituksensa. Tämä artikkeli koskee saman luottamusrajan kirjoituspuolta: mitä oma koodisi saa tehdä tiedostolle, jonka tavut on jo lukittu jonkun muun salasanan taakse, heti kun tuo koodi yrittää lisätä siihen mitään jälkikäteen
Mitä ISO 32000-1 vaatii, kun päivität salattua PDF:ää?
ISO 32000-1 §7.5.6 vaatii, että inkrementaalisen päivityksen jälki toistaa jokaisen merkinnän edellisestä jäljestä paitsi /Prev-kentän, ja taulukko 15 listaa /Encrypt-kentän merkintöjen joukossa, joita jälki voi kantaa. Pudota se uudesta jäljestä, eikä standardinmukaisella lukijalla ole syytä epäillä puutetta: uusin jälki on auktoritatiivinen, joten lukija, joka ei löydä sieltä /Encrypt-kenttää, päättää, että koko tiedosto on salaamaton, ja yrittää jäsentää vanhemman, yhä salatun rungon tavallisina tavuina. Pidä /Encrypt uudessa jäljessä, mutta kirjoita päivityksen omat oliot selkotekstinä, ja epäonnistuminen vain siirtyy yhden vaiheen myöhemmäksi: lukija tunnistaa salauksen oikein, ajaa jokaisen koskettamansa olion tiedoston salauksen läpi, mukaan lukien uudet, joita ei koskaan salattu alun perinkään, ja saa takaisin kohinaa sisällölle, joka oli täysin luettavaa ennen kuin salauksen purku kosketti sitä. Kumpikin virhe tuottaa tiedoston, joka näyttää tavutasolla tavalliselta, hyvin muodostetulta inkrementaaliselta päivitykseltä, aina siihen asti, kunnes standardinmukainen lukija avaa sen
Kuusi merkin injektoria, yksi v2.14.2-salausportti
PDFiumPas toimittaa kuusi tavutason merkin injektoria, yhden jokaiselle ISO PDF -osajoukolle, jonka se voi merkitä: 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) ja PDF/VT-1 (ISO 16612-2). Jokainen ottaa tavut, jotka PDFiumin oma FPDF_SaveAsCopy on jo kirjoittanut, ja kerrostaa toisen, pienemmän inkrementaalisen päivityksen niiden päälle: uuden XMP-metadatavirran, katalogisanakirjan muokkauksen, joka osoittaa siihen, ja tulostuspainotteisille osajoukoille OutputIntent-tiedon ja ICC-profiilin. v2.14.2:sta lähtien jokainen funktioista InjectPdfAMarkers, InjectPdfXMarkers, InjectPdfUaMarkers, InjectPdfEMarkers, InjectPdfRMarkers ja InjectPdfVTMarkers lukee lähdejäljen ensin, ja jos se raportoi olemassa olevan /Encrypt-merkinnän, kopioi lähteen kohdevirtaan muokkaamattomana ja palautuu välittömästi. Ei XMP:tä, ei OutputIntent-tietoa, ei katalogimuokkausta — kutsuja saa alkuperäisen tiedoston takaisin, tavu tavulta
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;
Sallittu olla salattu ei ole sama kuin turvallinen injektoitavaksi
PDF/E-1 ja PDF/R-1 molemmat nimenomaisesti sallivat isäntäasiakirjansa olevan salattu spesifikaatiotasolla, mikä lukee kuin poikkeus, kunnes katsot, mitä levylle todella täytyy tapahtua. ISO 24517-1 §6.3 sallii salauksen PDF/E-1:lle, ja ISO 23504-1 §6.2.3 sallii sen PDF/R-1:lle edellyttäen, että otsikko ilmoittaa %PDF-2.0. Kumpikaan lauseke ei sano mitään siitä, voiko tavutason jälkikäsittelijä turvallisesti lisätä selkotekstiolion tuohon salattuun säiliöön, eikä se voi, samoista §7.5.6-syistä, jotka koskevat jokaista muuta osajoukkoa. PDFiumPas:n omat yhdenmukaisuusvalidaattorit näille kahdelle profiilille, ValidatePdfECompliance ja ValidatePdfRCompliance, kirjaavat /Encrypt-läsnäolon tarkoituksella merkitsemättä sitä vialliseksi, mikä on oikein vain luku -validaattorille, joka ei koskaan kirjoita tavuakaan. Se on myös helppo malli silmäillä ohi ja olettaa, ettei sisarusinjektori tarvitse erillistä vartijaa, kun injektori on juuri se yksi funktio parissa, jonka todella täytyy kieltäytyä
Purkaako SaveAsPdfX asiakirjasi salauksen hiljaa?
Kyllä, aina kun kuljet julkisten kätevyysmetodien kautta suoran injektorin kutsumisen sijaan. Jokainen funktioista TPdf.SaveAsPdfA, SaveAsPdfX, SaveAsPdfUa, SaveAsPdfE, SaveAsPdfR ja SaveAsPdfVT renderöi nykyisen asiakirjan väliaikaiseen virtaan kutsulla SaveAs(Tmp, saRemoveSecurity) ennen kuin antaa nuo tavut vastaavalle injektorilleen. saRemoveSecurity kartoittuu PDFiumin omaan FPDF_REMOVE_SECURITY-lippuun, joten väliaikainen kopio, jonka injektori vastaanottaa, ei koskaan ollut salattu alun perinkään, eikä injektorin /Encrypt-vartija koskaan saa syytä laueta. Tuloste kantaa PDF/A-, PDF/X-, PDF/UA-, PDF/E-1-, PDF/R-1- tai PDF/VT-1-merkkisi, mutta sitä ei enää suojaa mikään salasana, joka avasi lähteen
Tuo kompromissi on näkymätön, kunnes joku alavirrassa avaa "suojatun" arkistointikopion ilman salasanaa ja huomaa sen vain toimivan. Korjaus ei ole eri metodikutsu; PDFiumPas:lla ei ole saAddSecurity-vastinetta pariutumaan saRemoveSecurity:n kanssa, koska taustalla oleva PDFium-moottori ei koskaan ollut rakennettu kirjoittamaan uutta salausta, vain poistamaan sitä. Jos molemmat ominaisuudet ovat merkityksellisiä yhdelle tiedostolle, salauksen on oltava erillinen vaihe, jonka omistat itse, sovellettuna yhdenmukaisuusmerkkien jälkeen, ei taitettuna samaan SaveAsPdfA-kutsuun
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;
Mitä tapahtuu, kun allekirjoitat salatun PDF:n PAdES:llä?
PDFiumPas kieltäytyy suoralta kädeltä, sen sijaan että hylkäisi pyynnön hiljaa samalla tavalla kuin merkin injektori tekee. Sekä TPdf.SignPades että SignPadesToStream ohjautuvat sisäisen SignPadesBytes-funktion kautta, ja ensimmäinen asia, jonka se tekee lähdejäljen jäsentämisen jälkeen, on tarkistaa /Encrypt-kentän varalta. Jos merkintä on läsnä, se nostaa EPadesCrypto-poikkeuksen viestillä "SignPadesBytes: the source document is encrypted; remove encryption before signing" sen sijaan, että jatkaisi eteenpäin. InjectPadesDssMarkers, funktio, joka upottaa sertifikaatit, OCSP-vastaukset ja CRL:t pitkän aikavälin validointia varten, soveltaa identtistä tarkistusta identtisestä syystä, omalla viestillään: "InjectPadesDssMarkers: the source document is encrypted; remove encryption before embedding DSS validation material"
Päättely tässä on tiukempaa kuin merkin injektoreiden läpipäästö, ja tarkoituksella. Hiljainen läpipäästö on turvallinen PDF/A-leimalle, koska sen ohittaminen jättää sinulle saman pätevän PDF:n, jolla aloitit, vain leimaamattomana. Allekirjoitus ei voi epäonnistua yhtä hiljaa: allekirjoitus, jota ei hiljaa koskaan lisätty, näyttää mille tahansa kutsuvalle koodille, joka vain tarkistaa totuusarvotuloksen, täsmälleen samalta kuin allekirjoitus, joka lisättiin onnistuneesti. EPadesCrypto periytyy tavallisesta Exception-luokasta, joten sen nappaaminen on tavallista poikkeuskäsittelyä, ei erikoinen kontrollivirtaan liittyvä käytäntö, joka pitäisi opetella
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;
Yhdenmukaisuusleimojen, allekirjoitusten ja salauksen järjestäminen
Käytännön korjaus on järjestys, ei eri kirjasto. Sovella PDF/A-, PDF/X-, PDF/UA-, PDF/E-1-, PDF/R-1- tai PDF/VT-1-merkit ensin, lisää mikä tahansa PAdES-allekirjoitus seuraavaksi, ja vasta sitten aja mikä tahansa vaihe putkessasi, joka todella omistaa salauksen, olipa se omistettu PDF-kirjoitin, allekirjoituslaite tai oma AES-toteutuksesi. PDFiumPas:n inkrementaalinen päivityskerros sopii luontevasti tuon sekvenssin keskelle, liittäen pieniä, kohdennettuja olioita tiedostoon, joka on muuten valmis, ja salaus kuuluu loppuun juuri siksi, että se on se yksi toiminto ketjussa, jota PDFiumPas itse ei voi suorittaa tai peruuttaa
Mikään tästä ei muuta sitä, miten PDFiumPas lukee jäljen ja ristiviittausdatan, josta jokainen inkrementaalinen päivitys riippuu, mikä on oma hienovaraisuuslähteensä heti, kun xref-virrat astuvat kuvaan; PDF:n olio- ja xref-virtojen validointi käsittelee, miten sama jäljenlukupolku käsittelee PDF 1.5+ -pakattuja rakenteita. Ja heti kun asiakirja on valmis johonkin voimakkaampaan kuin yhdenmukaisuusleima, PDF:n allekirjoittaminen PAdES B-B -allekirjoituksella Delphissä on paikka, jossa SignPades ottaa ohjat täsmälleen siitä, mihin tämä artikkeli jättää
Tässä kuvatut merkin injektorit ja SignPades-metodit toimitetaan osana PDFium-komponenttia Delphille ja C++Builderille, yhdessä renderöinnin ja vain luku -tarkastuksen kanssa, jonka PDFium tarjoaa natiivisti