En PDF-signatur handlar mest om bytebokföring, och det är där det går fel. Kryptografin körs på kod som granskats i två decennier, och den delen fallerar nästan aldrig. Det som fallerar i produktion är mer jordnära: en platshållare reserverad för liten för den verkliga signaturen, en hash tagen över fel sträcka av filen, eller ett "spara" efter signering som tyst skrev om byte signaturen redan hade frusit. Lägg ut byten rätt och den gröna bocken tar hand om sig själv
HotPDF täcker signering för Delphi och C++Builder på tre nivåer, och du väljer mellan dem genom att besvara en fråga: var bor den privata nyckeln? En PFX-fil på disk behöver ett enda funktionsanrop. En nyckel inlåst i en HSM eller en fjärrsigneringstjänst behöver sekvensen reservera-hasha-infoga, eftersom inget bibliotek kan nå in i en token och dra ut nyckeln. En signatur som måste tillfredsställa europeisk reglering behöver PAdES-baslinjestrukturerna utöver det. Avsnitten nedan följer den progressionen
Hur /ByteRange fastställer de signerade byten
En signatur måste leva inuti filen den signerar, och den kan inte signera sig själv. PDF kringgår paradoxen genom att lämna ett hål. Före signering reserverar skrivaren en /Contents-post med fast storlek full av nollor och registrerar en /ByteRange-array för de två sträckorna på var sida om det: allt före hålet, allt efter. Signeraren hashar de två sträckorna och skriver den resulterande CMS-klumpen in i hålet som hexadecimal. Fällan sitter i ordet fast. Du binder dig till storleken på det hålet innan du vet hur stor den färdiga signaturen kommer att bli, så reservationen måste vara en självsäker överskattning. Åtta kilobyte rymmer bekvämt en fristående CMS-signatur med en kort certifikatkedja
HotPDF delar upp de två fallen i två anrop, och att blanda ihop dem är ett vanligt nybörjarmisstag. AddSignatureField släpper ett tomt, synligt fält för en person att signera senare i en visare. AddSignedSignatureField skapar fältet och reserverar /Contents-hålet, vilket är det du vill ha närhelst kod, snarare än en människa, ska fullborda signaturen. Ge en extern signerare ett tomt fält och den har inget att fylla i
Enanropsvägen: signering från en PFX
När certifikatet och dess privata nyckel sitter i en PFX/PKCS#12-fil din process kan läsa, reduceras hela pipelinen till en klassfunktion:
if THotPDF.SignPDFWithPFX('invoice-unsigned.pdf', 'invoice-signed.pdf',
'company-cert.pfx', 'pfx-password') then
Writeln('Signed: invoice-signed.pdf')
else
raise Exception.Create('PFX signing failed');
När det här misslyckas är PDF-filen sällan problemet. PFX-filen är det. HotPDF läser behållare skyddade med PBES2, det vill säga PBKDF2-nyckelhärledning över AES-256-CBC. En PFX exporterad av en äldre Windows-certifikatguide, eller av OpenSSL före 3.0, är i stället vanligtvis inlindad i äldre RC2 eller 3DES, och den kommer helt enkelt inte att kunna parsas. Åtgärden är att exportera behållaren på nytt en gång med modernt skydd; dagens OpenSSL gör detta som standard, och det är ingen kodändring. Så när signeringen dör direkt på ett certifikat som "fungerar överallt annars", titta på hur PFX-filen skapades innan du misstänker din egen kod
Vägen reservera-hasha-infoga för HSM:er och tokens
Enanropsvägen förutsätter att din process kan läsa nyckeln som en fil. Allt oftare kan den inte det. Nyckeln sitter i en HSM, på en USB-token, eller bakom en signeringstjänsts API, och det finns inget sätt för ett bibliotek att nå den direkt. HotPDF hanterar det genom att bryta ner signeringen i byteniva-steg: skriv ett platshållardokument, be biblioteket om hash-intervallen, skicka hash-indatan till vad som än håller nyckeln, splitsa sedan den returnerade CMS:en tillbaka in i hålet
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Skriv dokumentet med ett reserverat /Contents-hål
Doc := THotPDF.Create(nil);
try
Doc.FileName := 'placeholder.pdf';
Doc.BeginDoc;
Doc.CurrentPage.AddSignedSignatureField('Sig1',
Rect(50, 100, 350, 150), 8192, 'adbe.pkcs7.detached',
'Contract approval', 'Boston, MA', 'legal@example.com');
Doc.EndDoc;
finally
Doc.Free;
end;
// 2. Läs in de sparade byten; de returnerade förskjutningarna är 0-baserade
Fs := TFileStream.Create('placeholder.pdf', fmOpenRead);
try
SetLength(PdfBytes, Fs.Size);
Fs.ReadBuffer(PdfBytes[1], Fs.Size);
finally
Fs.Free;
end;
THotPDF.PreparePDFForSigning(PdfBytes, R1Start, R1Len, R2Start, R2Len,
CStart, CLen);
// 3. Hasha båda sträckorna och signera externt (HSM, token, tjänst)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // din integration: returnerar CMS som hex
// 4. Splitsa in signaturen i det reserverade hålet
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Två detaljer i den här sekvensen orsakar de flesta av de intermittenta felen. Den första är att PreparePDFForSigning arbetar på byten i en färdig fil. Platshållaren måste vara skriven och sparad i sin helhet innan förskjutningarna betyder något; beräkna dem mot en ström som fortfarande håller på att sättas ihop och de kommer inte att stämma överens med de byte du så småningom hashar. Den andra är reservationsstorleken, igen. De 8192 byte du bad om måste rymma den slutgiltiga CMS:en, och en signatur som bär mellanliggande certifikat, eller en som en tjänst dekorerar med signerade attribut, kan sträcka sig förbi det. InsertSignatureHex kommer inte att växa hålet för att skapa plats. Tecknet är en pipeline som signerar fint med ett certifikat och misslyckas med nästa; boten är att regenerera platshållaren med en reservation uppmätt från en verklig signatur producerad av den faktiska signeraren, inte gissad
PAdES-baslinjer, och tidsstämplarna som håller en signatur vid liv
Om du signerar under europeiska regler är standarden i spel ETSI EN 319 142-1, som staplar fyra PAdES-baslinjenivåer. B-B är den enkla signaturen. B-T lägger till en betrodd tidsstämpel som bevisar när den gjordes. B-LT bäddar in valideringsmaterialet, certifikaten och återkallelsedata, inuti dokumentet så att det fortfarande kan kontrolleras år senare. B-LTA lägger periodiska dokumenttidsstämplar ovanpå, så att beviset överlever algoritmerna det byggdes på. HotPDF genererar de dokumentsidiga strukturerna för varje nivå:
// PAdES-baslinjesignaturfält (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Dokumenttidsstämpel: större reservation för TSA-token och -kedjan
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
16384-byte-reservationen på tidsstämpeln är avsiktlig. En tidsstämpelmyndighet returnerar en token som drar med sig sin egen certifikatkedja, så den behöver rutinmässigt mer plats än de 8 KB en enkel signatur nöjer sig med. De dokumenttidsstämplarna är också maskineriet bakom B-LTA: att omtidsstämpla en arkiverad signatur var några år, med algoritmer som fortfarande är aktuella, är det som håller ett dokument du signerade 2026 verifierbart 2040
Ett ord om orsaks-, plats- och kontaktsträngarna som båda fältanropen accepterar: de är bekvämlighetsmetadata och inget mer. HotPDF lagrar dem som vanliga ordbokposter och målar in dem i det synliga signaturutseendet, men ingen validator kontrollerar dem mot något. Fyll i dem konsekvent från din arbetsflödesdata, eftersom granskare faktiskt läser dem, men misstag dem aldrig för bevis. Det faktiska kryptografiska påståendet lever helt i CMS:en och dess certifikatkedja, och en verifierare ignorerar den synliga texten fullständigt
Efter signering får filen bara växa
I samma ögonblick en signatur existerar är byten inom dess intervall frusna. Det enda legitima sättet att ändra filen efteråt är en inkrementell uppdatering enligt ISO 32000-1 §7.5.6, som lägger till nya och ändrade objekt efter de ursprungliga byten och kedjar ett färskt korsreferensavsnitt tillbaka till dem. Gjort på det sättet förblir signaturen giltig för sin revision och en visare rapporterar det ärliga tillståndet: den signerade revisionen är intakt, dokumentet utökades efteråt. Serialisera om hela filen i stället och du skriver om de signerade sträckorna, vilket förstör signaturen även när ingenting synligt ändrades. Samma revisionsmekanism är också hur ett dokument bär flera signaturer: varje ny signatur hamnar i sin egen inkrementella uppdatering, och dess intervall täcker allt före den, inklusive de tidigare signaturerna. Append-only-mekaniken, och när det är säkert att komprimera dem, täcks i artikeln om objektströmmar och inkrementella uppdateringar
Två gränser är värda att hålla i minnet medan du designar. HotPDF:s PDF/A-utdataläge avvisar signaturfält rakt av, så arkivefterlevnad och en inbäddad signatur måste levereras som separata filer. Och signering säger ingenting om hemlighållande: den bevisar vem som producerade ett dokument och att det inte har ändrats sedan dess, men vem som helst kan fortfarande läsa det. Att dölja innehållet är ett separat jobb, hanterat av AES-256-kryptering och behörighetspolicy
Vad du än bygger, testa det med något annat än koden som skrev filen. Öppna utdatan i Acrobats signaturpanel och bekräfta tre saker: signaturen är giltig, identiteten kedjar till roten du förväntade dig, och panelen rapporterar inga ändringar sedan signering. Vänd sedan om ett enda byte inuti det signerade intervallet i en engångskopia och bekräfta att panelen nu kallar dokumentet ändrat. En signeringspipeline du aldrig sett avvisa en manipulerad fil är en vars verifiering inte riktigt har testats
Alla tre signeringsnivåer levereras med HotPDF Delphi Component för Delphi och C++Builder; produktsidan länkar den fullständiga signatur-API-referensen