PDFium Component signerar en PDF med en digital PAdES B-B-signatur via sin metod SignPades: den laddar dokumentet, hashar det signerade byte-intervallet, bygger en CAdES CMS-struktur och lägger till signaturen som en inkrementell uppdatering. Den kryptografiska motorn är endast för Windows, så skydda varje anrop med PadesCryptoAvailable innan du signerar
Situationen är välbekant. Ett PDF-kontrakt landar på ditt skrivbord, juridikavdelningen vill ha det digitalt signerat innan det skickas iväg, och du sträcker dig efter samma PDFium-version som du redan använder för att rendera och inspektera dokument, bara för att upptäcka att PDFium inte alls kan skriva en signatur. Dess signatur-API är strikt skrivskyddat. PDFium Component överbryggar det glappet genom att äga hela signeringspipelinen i Pascal, från hash-funktionen upp till injiceringen på bytenivå, och den här artikeln går igenom den pipelinen från början till slut
Varför kan inte PDFium skriva en digital signatur?
PDFium exponerar signaturer som skrivskyddade objekt och erbjuder inget som skapar dem. FPDFSignatureObj_*-familjen låter dig räkna upp en befintlig signatur, läsa dess /Contents och inspektera dess /ByteRange, men det finns ingen motsvarighet som bygger en signaturordbok, reserverar en /Contents-plats eller skriver ett byte-intervall; inkrementell sparande finns (FPDF_SaveAsCopy med FPDF_INCREMENTAL) men bär inte på någon signeringskrok. Varje komponent som signerar en PDF ovanpå PDFium måste därför generera varje signatur-byte själv, vilket är anledningen till att PDFium Component bygger maskineriet från tre rena Pascal-enheter. FPC 3.2.2 levereras med md5 och sha1 men inte SHA-2 överhuvudtaget, och Delphis System.Hash SHA-256-API är inte källkodskompatibelt med FPC, så FPdfSha256 är a fristående FIPS 180-4-implementering som håller varje CMS-kodväg på en TSHA256Digest-typ utan kompilatorförgreningar. FPdfAsn1 tillhandahåller DER-kodaren och läsaren som CMS-strukturerna behöver, och FPdfCms monterar CAdES SignedData ovanpå båda
Hur signerar du en PDF digitalt i Delphi?
Ladda dokumentet och anropa sedan SignPades med ett certifikatavtryck (thumbprint). PDFium Component löser det avtrycket mot Current User-lagret "MY" för certifikat, hämtar det matchande certifikatet och dess privata nyckel och skriver en signerad kopia till den sökväg du anger
uses
PDFium, FPdfCrypto;
procedure SignContract(const AThumbprint: string);
var
Pdf: TPdf;
begin
if not PadesCryptoAvailable then
raise Exception.Create('PAdES signing requires the Windows CNG backend');
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf'; // the document to sign
Pdf.Active := True;
// Second argument: SHA-1 thumbprint of a certificate in the Current
// User "MY" store. First argument: destination for the signed copy.
if not Pdf.SignPades('contract-signed.pdf', AThumbprint) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
PadesCryptoAvailable är kontrollen du kontrollerar först, varje gång. På Windows returnerar den True och crypt32/ncrypt-motorn är aktiv; på alla andra plattformar returnerar den False och ett signeringsanrop skulle utlösa EPadesCrypto. Att behandla den kontrollen som obligatorisk hindrar ett bygge för Linux eller macOS från att misslyckas vid körtid på en väg som inte kan fungera där. Själva avtrycket är SHA-1-hashen av certifikatet, samma värde som Windows certifikathanterare visar på fliken Detaljer, och det namnger en specifik undertecknare utan att någonsin placera nyckelmaterial i din källkod
Vad som ingår i CMS: signerade attribut och RFC 5652
En PAdES-bassignatur är inte en rå RSA-signatur över filen; det är a CAdES CMS SignedData-struktur som bär på en obligatorisk uppsättning signerade attribut, och FPdfCms.BuildSignedData genererar exakt den uppsättningen: content-type, message-digest och signing-certificate-v2, det ESS-attribut som binder signaturen till undertecknarens certifikat genom hash. En detalj här stjälper nästan alla handskrivna CMS-implementeringar. RFC 5652 §5.4 kräver att sammandraget för signerade attribut beräknas över DER SET OF-kodningen, taggen 0x31, medan samma attribut färdas inuti SignerInfo under taggen IMPLICIT [0], 0xA0. PDFium Component kodar attributuppsättningen en gång, sammandrar 0x31-formen och skriver sedan om endast den ledande taggbyten till 0xA0 för överföring, så att en buffert tjänar båda rollerna utan en andra genomgång av trädet
var
Pdf: TPdf;
Opts: TPadesSignOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := 'a1b2c3d4e5f6...'; // signer in the MY store
Opts.Reason := 'I approve this agreement';
Opts.Location := 'Berlin, DE';
Opts.ContentsSize := 16384; // hex width of /Contents
if not Pdf.SignPades('contract-signed.pdf', Opts) then
raise Exception.Create('Signing failed');
finally
Pdf.Free;
end;
end;
Hur passar ByteRange och /Contents-platsen ihop?
En signatur måste täcka hela filen utom de byte som håller i signaturen själv, och PAdES löser den cirkulariteten med en platshållare med fast bredd som SignPadesBytes hanterar exakt. Den reserverar en /Contents-hexsträng på ContentsSize byte (16384 som standard, bekvämt större än ett typiskt CMS SignedData), serialiserar den inkrementella uppdateringen för att lokalisera den exakta offsetten för platsen, och beräknar sedan /ByteRange som två intervall som omsluter platsen, allt före hexsträngens inledande avgränsare och allt efter dess avslutande avgränsare. SHA-256 körs endast över dessa två intervall. Den färdiga CMS-strukturen hex-kodas in i den reserverade platsen, nollutfylls till den fasta bredden, och den inkrementella korsreferensuppdateringen läggs till. Eftersom bredden är bestämd i förväg förskjuter fyllningen av platsen ingen efterföljande byte, vilket är hela anledningen till att byte-intervallet förblir giltigt; originaldokumentets byte bevaras ordagrant, så att en tidigare signatur på samma fil överlever intakt precis som ISO 32000-1 §12.8.1 för inkrementell signering kräver
Windows CNG-motorn och dess begränsningar
PDFium Component signerar endast på Windows, och den gränsen är medveten. FPdfCryptoWin binder crypt32.dll och ncrypt.dll dynamiskt, vilket inte lägger till något DLL-beroende vid kompilering, och signeringskedjan är standard-CNG: öppna MY-lagret, hitta certifikatet genom hashen, hämta dess privata nyckelpekare via CryptAcquireCertificatePrivateKey och anropa NCryptSignHash. RSA med PKCS#1 v1.5, RSA-PSS och ECDSA stöds alla. ECDSA behöver en korrigering som de andra inte behöver, eftersom NCryptSignHash returnerar det råa IEEE P1363 r-och-s-paret medan CMS förväntar sig en DER ECDSA-Sig-Value SEQUENCE, så motorn kodar om det enligt RFC 5480
var
Pdf: TPdf;
Opts: TPadesSignOptions;
Output: TFileStream;
begin
if not PadesCryptoAvailable then
Exit; // no signing backend on this platform
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := ReadThumbprintFromConfig;
Pdf := TPdf.Create(nil);
Output := TFileStream.Create('contract-signed.pdf', fmCreate);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Pdf.SignPadesToStream(Output, Opts);
finally
Output.Free;
Pdf.Free;
end;
end;
Var PAdES B-B upphör
PAdES B-B är baslinjen, botten av de fyra PAdES-nivåerna: den bevisar vem som signerade och att inga byte har ändrats sedan dess, och inget mer än så. En B-B-signatur bär inte på någon betrodd tidsstämpel, så den kan inte bevisa när signeringen skedde, och den bäddar inte in några återkallelse-data, så en verifierare måste år senare själv hämta certifikatkedjan och dess status. Dessa glapp är precis vad de högre nivåerna stänger. När du behöver en signeringstid en granskare kommer att acceptera, flyttar tillägg av en RFC 3161-tidsstämpel och DSS för långsiktig validering signaturen till B-T och högre; när du vill läsa tillbaka en färdig signatur och bekräfta vilken nivå den nådde är inspektion av en PDF-signatur och dess PAdES-nivå det tillhörande verktyget; och innan du signerar något överhuvudtaget berättar granskning av en PDF för säkerhetsrisker vad du är på väg att sätta ditt namn på
Metoderna SignPades som visas här levereras med PDFium Component för Delphi och C++Builder, tillsammans med den skrivskyddade signaturinspektion som PDFium erbjuder direkt