HotPDF krypterer en PDF for spesifikke sertifikatinnehavere gjennom ISO 32000s public-key security handler: EnablePubKeyEncryption tar et tilfeldig frø på 20 byte, og hver mottaker får sin egen CMS-konvolutt, bygget av AddPubKeyRecipientCertificate for RSA-nøkler (RSA-OAEP key transport) eller AddPubKeyAgreementRecipientWithSecret for elliptisk-kurve-nøkler (ECDH på P-256, P-384, P-521, X25519 eller X448). Ingen deler et passord; den som holder en matchende privatnøkkel, åpner filen
Bruksområdet er alltid en eller annen versjon av den samme historien. Et kvartals revisjonspakke går til tre eksterne gjennomgripere, juridisk avdeling vil at hver av dem skal lese den, bare én av dem får lov til å skrive den ut, og ingen vil ha et passord liggende i en e-posttråd ved siden av vedlegget. Passordkryptering kan ikke uttrykke det. Sertifikatkryptering kan det, fordi hver mottaker låser opp dokumentet med en nøkkel de allerede holder, og hver mottaker kan bære et annet tillatelsessett inne i sin egen konvolutt
Hvordan skiller sertifikatbasert PDF-kryptering seg fra et passord?
En public-key-kryptert PDF utleder filnøkkelen sin fra et tilfeldig frø pluss de eksakte bytene i hver mottakerkonvolutt, ikke fra noe et menneske taster inn. Handleren er beskrevet i ISO 32000-1 §7.6.4 (§7.6.5 i ISO 32000-2), og konvoluttene er CMS EnvelopedData-strukturer som definert i RFC 5652. HotPDF skriver /Filter /Adobe.PubSec med /SubFilter /adbe.pkcs7.s5; for AES-256 betyr det /V 5 og en /DefaultCryptFilter-oppføring under /CF med /CFM /AESV3, og /Recipients-arrayen bor inne i den kryptofilteret. Hver konvolutt krypterer 24 byte: frøet på 20 byte fulgt av den mottakerens 32-bits tillatelsesord. /P-verdien i krypteringsordboken er bare en plassholder, fordi de ekte tillatelsene reiser inne i hver konvolutt. Ved lasting pakker en leser opp én konvolutt, gjenoppretter frøet, og hasher frøet sammen med hver konvolutt i /Recipients-rekkefølge (SHA-256 for AES-256, SHA-1 for de eldre siferne) for å bygge filnøkkelen på nytt. Hvis du fortsatt vurderer denne modellen mot vanlige passord, dekker veiledningen om AES-256 passordkryptering og tillatelsesflagg den andre siden av den avveiningen
Å skrive RSA-mottakere med EnablePubKeyEncryption
For RSA-sertifikater kaller du EnablePubKeyEncryption med aes256, og deretter AddPubKeyRecipientCertificate én gang per DER-kodet sertifikat før BeginDoc. Hjelperen bygger en RSAES-OAEP-konvolutt i prosessen med THPDFRSAOAEPHash-verdier for OAEP-digesten og MGF1-digesten (rohSHA256, rohSHA384 eller rohSHA512), og den krypterer konvoluttinnholdet med AES-256-CBC
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFRSA;
procedure WriteAuditPack(const OutFile: string);
var
Pdf: THotPDF;
Seed: AnsiString;
begin
SetLength(Seed, 20); // nøyaktig 20 byte, selv for AES-256
AESGenerateRandomBytes(@Seed[1], Length(Seed));
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := OutFile;
Pdf.EnablePubKeyEncryption(Seed, aes256, True); // standard nøkkeltype er aes128
// Gjennomgriper A får skrive ut; gjennomgriper B kan bare lese og trekke ut
Pdf.AddPubKeyRecipientCertificate(TFile.ReadAllBytes('reviewer-a.cer'),
[prPrint, prPrint12bit, prExtractContent], rohSHA256, rohSHA256);
Pdf.AddPubKeyRecipientCertificate(TFile.ReadAllBytes('reviewer-b.cer'),
[prExtractContent]);
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 720, 0, 'Q3 audit pack');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Tre detaljer i den oppstillingen bærer last. For det første er frølengden fast på 20 byte for hver nøkkeltype, AES-256 inkludert; EnablePubKeyEncryption reiser på enhver annen lengde. For det andre er standarden til EnablePubKeyEncryption aes128, og begge sertifikathjelperne nekter å kjøre med mindre nøkkeltypen er aes256, så å glemme det andre argumentet gir deg unntaket «certificate envelopes require aes256». De legacysifrene (k40, k128, aes128) virker fortsatt, men bare gjennom AddPubKeyRecipient med en konvolutt du har bygget et annet sted. For det tredje er AES-256 public-key-kryptering en PDF 2.0-funksjon, så HotPDF hever dokumentversjonen til 2.0 automatisk. Med StrictVersionLock satt på en lavere versjon, returnerer EnablePubKeyEncryption uten å aktivere noe, og feilen viser seg bare på neste linje som «call EnablePubKeyEncryption first». Å bytte kryptering under en inkrementell oppdatering reiser EInvalidOpException med én gang
Å legge til ECDH-mottakere: P-256, P-384, P-521, X25519 og X448
For elliptisk-kurve-sertifikater skriver AddPubKeyAgreementRecipientWithSecret en CMS key-agreement-mottaker (KeyAgreeRecipientInfo, KARI-strukturen fra RFC 5753, med X25519- og X448-profilen fra RFC 8418) og beregner den ECDH-delte hemmeligheten i prosessen. Du velger kurven med en THPDFPubKeyAgreementScheme-verdi: pkasECDHP256, pkasECDHP384, pkasECDHP521, pkasX25519 eller pkasX448. Skjemaet må matche nøkkelen i sertifikatet, ellers reiser kallet «Certificate key does not match the requested agreement scheme». Under panseret får hver konvolutt en fersk tilfeldig 32-byte UKM, en nøkkelkrypteringsnøkkel utledet med stdDH KDF (SHA-256 for P-256 og X25519, SHA-384 for P-384, SHA-512 for P-521 og X448), og en AES-256 key wrap som definert i RFC 3394. Den delte hemmeligheten i seg selv kommer fra ren Pascal-kurvekode, uten noen plattform-kryptoleverandør involvert; artikkelen om ren Pascal NIST-kurve-aritmetikk forklarer hvordan det laget ble bygget og verifisert. For Montgomery-kurvene kan hele det efemere nøkkelparet genereres lokalt:
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFCrypt, HPDFPubSec,
HPDFKeyAgreement;
procedure AddLegalRecipient(Pdf: THotPDF);
var
Scalar, OriginatorPublic: TBytes;
begin
// Fersk efemer skalar per konvolutt; clamping skjer inne i stigen
SetLength(Scalar, 32);
AESGenerateRandomBytes(@Scalar[0], Length(Scalar));
try
OriginatorPublic := HPDFX25519PublicFromScalar(Scalar);
Pdf.AddPubKeyAgreementRecipientWithSecret(
TFile.ReadAllBytes('legal-x25519.cer'),
[prPrint, prExtractContent], pkasX25519,
OriginatorPublic, Scalar,
[]); // OwnPublicPoint: bare meningsfullt for NIST-kurvene
finally
HPDFSecureClearBytes(Scalar);
end;
end;
NIST-kurvene krever mer av kalleren. HotPDF leverer public-key-hjelpere bare for X25519 og X448 (HPDFX25519PublicFromScalar, HPDFX448PublicFromScalar), så for P-256, P-384 og P-521 genererer du det efemere nøkkelparet med eget verktøy og sender en big-endian skalar på nøyaktig feltstørrelsen (32, 48 eller 66 byte) pluss det matchende ukomprimerte 0x04||X||Y-punktet som OriginatorPublicKey. HotPDF validerer mottakerpunktet mot kurveligningen, men den kan ikke sjekke at originatorens offentlige nøkkel faktisk tilhører skalaren din. Umatchede halvdeler produserer fortsatt en fullt velformet konvolutt som ingen mottaker kan åpne, noe som er grunnen til at en rundturs-lasting hører hjemme i testsuiten din, ikke bare en filstørrelsessjekk
Hvorfor betyr rekkefølgen på /Recipients noe?
Rekkefølgen på /Recipients betyr noe fordi filnøkkelen er en digest over frøet og hver konvolutt i array-rekkefølge, så skriver og leser må hashe de samme bytene i samme rekkefølge. HotPDF beholder konvoluttene i den rekkefølgen du legger dem til og skriver dem uendret, noe som betyr at du kan legge til mottakere i vilkårlig rekkefølge, men ingenting nedstrøms kan omstokke, re-enkode eller «rydde opp i» den arrayen. De fleste ekte feilene på dette området var en variant av det temaet, der de to sidene hashet litt forskjellige byte:
- Å lagre dynamiske arrayer i en
TListviaAddbeholder bare en rå peker mens referansetellingen blir igjen hos den lokale variabelen. NesteSetLengthfrigjør bufferen og kan gjenbruke den, så hver plass endte opp med å aliasere siste konvolutt, og fler-mottaker-filer utledet feil nøkkel. Fiksen er å lagre en eiet kopi medList.Add(Pointer(System.Copy(Bytes))) - Konvoluttoppakking parser DER-en på stedet, og nøkkelgjenopprettingspasset hashet opprinnelig de samme live-arrayene. Leseren tar nå øyeblikksbilder av rene kopier av hver konvolutt før noen opppakking rører dem, og digeresten løper over øyeblikksbildene
- Binær DER sendt gjennom en Unicode
TStringListfår byte på eller over$80re-enkodet av kodesiden, så HotPDF lagrer konvolutter som hekstekst internt - Krypterte og binære strenger må skrives som heksstrenger. En literal streng er underlagt linjeslutt-normalisering, der CR, LF og CRLF alle blir en enkelt LF (ISO 32000-1 §7.3.4.2), og det omskriver stille chifferteksten. HotPDF sender hver
/Recipients-oppføring som en heksstreng og fritar den fra strengkryptering, siden hver leser trenger konvoluttene før den holder noen nøkkel - Den første byten i en DER
BIT STRINGteller ubrukte biter og må være null for byte-justerte nøkler. Å la den stå uinitialisert etterSetLengthskrev hva som helst som lå på stakken, og en streng opppakker avviste originatornøkkelen, så en fil kunne innimellom nekte å åpne med nøyaktig nøkkelen den var skrevet for - Når samme nøkkel fortsatt ikke kan dekryptere, sammenlign lag for lag: filnøkkelen, så chiffertekst-prefikset (IV-en), så objektnøkkelen, så klarteksten. Feilen bor rett etter det første laget som er uenig
Hvordan åpner du en sertifikatkryptert PDF med en privatnøkkel?
For å åpne en sertifikatkryptert PDF, registrer det private nøkkelmaterialet før du kaller LoadFromFile, for HotPDF gjenoppretter filnøkkelen under den strukturelle passeringen. Tildel en RSA- eller EC-nøkkel parsert med HPDFParsePFX til PubSecKeyMaterial, legg til flere RSA-nøkler med AddPubSecKeyMaterial, og registrer rå ECDH-skalarer med AddPubSecAgreementKeyMaterial(CurveOID, PrivateScalar, OwnPublicPoint), med konstantene HPDFOIDX25519, HPDFOIDX448, HPDFOIDECP256, HPDFOIDECP384 eller HPDFOIDECP521. NIST-kurvene krever mottakerens eget ukomprimerte offentlige punkt; Montgomery-kurvene ignorerer det
uses
System.SysUtils, System.IOUtils, HPDFDoc, HPDFPFX, HPDFKeyAgreement;
procedure OpenAuditPack(const LegalScalar: TBytes);
var
Reader: THotPDF;
begin
Reader := THotPDF.Create(nil);
try
Reader.AutoLaunch := False;
Reader.PubSecKeyMaterial :=
HPDFParsePFX(TFile.ReadAllBytes('reviewer-a.pfx'), 'pfx-password');
Reader.AddPubSecAgreementKeyMaterial(HPDFOIDX25519, LegalScalar, nil);
// Valgfritt: plukk konvolutten direkte i stedet for å prøve alle
Reader.PubSecRecipientQuery :=
function(Context: Pointer; RecipientCount: Integer): Integer
begin
Result := -1; // -1 = prøv hver konvolutt i rekkefølge
end;
Reader.LoadFromFile('audit-pack.pdf', '');
Writeln('Pages: ', Reader.GetLoadedPageCount);
finally
Reader.Free;
end;
end;
Uten en callback prøver HotPDF hver konvolutt mot hver registrerte nøkkel: primærnøkkelen først, så hver ekstra RSA-nøkkel, så EC-materialet. PubSecRecipientQuery mottar konvoluttantallet og returnerer en nullbasert indeks eller -1, og en indeks utenfor arrayen reiser et unntak i stedet for å klemmes inn. Merk at AddPubSecKeyMaterial bare godtar RSA-materiale (den insisterer på en modul og en privat eksponent), så EC-nøkler hører hjemme i PubSecKeyMaterial eller AddPubSecAgreementKeyMaterial. Når ingen nøkkel pakker opp noen konvolutt, returnerer gjenopprettingstrinnet uten en filnøkkel i stedet for å reise, så verifiser at innholdet du forventer, faktisk ble dekryptert i stedet for å stole på at lastekallet returnerte
Hva HotPDF ikke garanterer
HotPDF garanterer at dens egen skriver og leser er enige byte for byte, og den bygger konvolutter som følger CMS-strukturene sitert over. Den garanterer ikke at ethvert PDF-visningsprogram åpner enhver kombinasjon. Støtten for RSA-OAEP key transport og for X25519- eller X448-mottakere varierer mellom lesere og versjoner, og vi har ikke publisert kompatibilitetsresultater for de kombinasjonene. Hvis et dokument må åpnes i et spesifikt visningsprogram, krypter en testfil for et testsertifikat av samme nøkkeltype og åpne den der før du forplikter deg til et skjema. Tillatelser båret i konvolutten forblir policy som konform programvare hedrer, nøyaktig som de gjør under passordkryptering. Frøkvaliteten er også ditt ansvar: AESGenerateRandomBytes er der for den jobben, og HotPDF visker ut sin kopi av frøet så snart filnøkkelen er utledet. Hvis du også trenger en streng, strøm eller et vedlegg til å bruke et annet kryptofilter, viser veiledningen om kryptofilter-policy for StmF, StrF og EFF hvilke filternavn public-key-handleren godtar
Sertifikatkryptering, RSA-OAEP- og ECDH-mottakerkonvolutter og lasting av privatnøkkel leveres alle i HotPDF Delphi PDF-komponenten, ved siden av passordkryptering, digitale signaturer og resten av ISO 32000-verktøysettet for Delphi og C++Builder