Before a viewer shows the green tick on a signed PDF, it does three mechanical things: it reads the /ByteRange array from the signature dictionary, hashes exactly the two byte spans that array describes, and verifies the CMS signature stored — as hex — in the /Contents entry that sits between those spans. Nearly every production signing failure traces back to one of those three steps being arranged wrong: a placeholder too small for the final signature blob, a hash computed over the wrong bytes, or a post-signing save that rewrote bytes the ranges already covered. The cryptography almost never fails. The byte bookkeeping does.
UK teams should align this hotpdf digital signatures pades delphi workflow with local governance, audit, and data quality requirements before production release
For UK environments, align the hotpdf digital signatures pades delphi implementation with local quality gates that include governance approval, versioned fixture baselines, and evidence retention for every publication candidate. Keep an explicit review log for accessibility, redaction policy, and data residency checks so your deployment audit is repeatable without changing output binaries
UK technical governance addendum
HotPDF gives Delphi and C++Builder applications three tiers of signing support — one-call PFX signing, an external-signer workflow for HSMs and signing services, and PAdES-profile fields with document timestamps. They are presented here in that order, because each tier exists to handle a failure mode of the one before it.
The ByteRange contract in ISO 32000-1 §12.8
A PDF signature must live within the file it signs, which creates a chicken-and-egg problem: the signature value cannot cover itself. The format solves it with a hole. The writer reserves a fixed-size /Contents entry filled with zeros, and /ByteRange records two spans — everything before the hole and everything after it. The signer hashes those two spans, and the resulting CMS structure is written into the hole as hexadecimal. The consequence engineers trip over: the reservation size is frozen before signing, so the final signature, certificates and all, has to fit a hole whose size was chosen earlier. Around 8 KB accommodates a detached CMS signature with a short certificate chain.
HotPDF exposes the distinction directly. AddSignatureField creates an empty visible field for someone to sign later in a viewer; AddSignedSignatureField creates the field and reserves the /Contents hole for programmatic completion. Picking the wrong one is a classic first-week mistake: an empty field gives an external signer nothing to fill.
One call when the key is a PFX file
When the signing certificate and private key live in a PFX/PKCS#12 file, the entire pipeline collapses into a class function:
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');
The failure that dominates support traffic here has nothing to do with the PDF: the PFX container itself. HotPDF reads PFX files protected with PBES2 — PBKDF2 key derivation with AES-256-CBC. Containers exported by older Windows certificate wizards, or by OpenSSL releases before 3.0, default to legacy RC2/3DES protection and will not parse. The remedy is a one-time re-export of the container with modern parameters (current OpenSSL does this by default), not a code change. Check this first when signing fails immediately on a certificate that 'works everywhere else.'
External signing: reserve, hash, insert
The one-call path assumes the private key is a file your process can read. Production signing keys increasingly are not — they sit in an HSM, a USB token, or a remote signing service, and no library can call them directly. For that topology HotPDF splits the workflow into byte-level steps: write a placeholder document, compute the hash ranges, hand the hash input to whatever holds the key, and splice the returned CMS back in.
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Write the document with a reserved /Contents hole
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. Load the saved bytes; the returned offsets are 0-based
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. Hash both spans and sign externally (HSM, token, service)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // your integration: returns CMS as hex
// 4. Splice the signature into the reserved hole
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Two constraints in this sequence cause intermittent production failures when missed. Firstly, PreparePDFForSigning operates on the bytes of a fully saved file — the placeholder document must be written to completion before the ranges mean anything; computing ranges against an in-flight stream yields offsets that no longer match the final serialisation. Secondly, the 8192-byte reservation must fit the final CMS. A signature that embeds intermediate certificates, or one returned by a service that adds signed attributes, can exceed it — and InsertSignatureHex cannot enlarge the hole. The symptom is a job that succeeds with one certificate and fails with another; the fix is regenerating the placeholder with a larger reservation, sized from a real signature produced by the actual signer.
PAdES baselines and document timestamps
European signature regulation builds on ETSI EN 319 142-1, which defines four PAdES baseline levels: B-B is the basic signature; B-T adds a trusted timestamp proving when it was made; B-LT embeds the validation material — certificates and revocation data — within the document; B-LTA adds periodic document timestamps so the evidence survives algorithm aging. HotPDF creates the document-side structures for this lifecycle:
// PAdES baseline signature field (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Document timestamp: larger reservation for the TSA token and chain
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
Note the 16384-byte reservation on the timestamp: a timestamp authority's token carries its own certificate chain, so it routinely outgrows the 8 KB that suffices for a plain signature. Document timestamps are also the mechanism behind B-LTA maintenance — re-timestamping a signed archive every few years with current algorithms is what keeps a 2026 signature verifiable in 2040.
The reason, location, and contact strings accepted by both field calls deserve a policy note: they are stored as plain dictionary entries and rendered into the visible appearance, but nothing verifies them. They document intent for human readers — 'Contract approval', a city, a mailbox — and auditors will read them, so populate them consistently from workflow data. Just never treat the visible text as evidence: the cryptographic statement lives entirely in the CMS and its certificate chain, and a validator ignores the appearance completely.
Why signed files must only grow
Once a signature exists, the bytes within its ranges are frozen forever. ISO 32000-1 §7.5.6 incremental updates are the only legitimate way to change the file afterwards: new and modified objects are appended after the original bytes, with a new cross-reference section chaining back. The signature stays valid for its revision, and viewers report the honest state — 'signed revision intact, document modified afterwards.' A full re-serialisation, by contrast, rewrites the signed spans and destroys the signature outright, even if not a single visible element changed. The mechanics of append-only saves, and when to compact them, are covered by the article on object streams and incremental updates.
One library-level constraint rounds out the planning picture: HotPDF's PDF/A output mode rejects signature fields, so archival conformance and embedded signatures must be split across separate deliverables instead of combined in one file. And signing is orthogonal to confidentiality — a signature proves origin and integrity but hides nothing, which is the territory of AES-256 encryption and permission policy.
Acceptance testing for a signing pipeline should be independent of the code that produced the file. Open the output in Acrobat's signature panel and confirm three states: the signature is valid, the identity chains to the expected root, and the panel reports no changes after signing. Then corrupt one byte within the signed range of a copy and confirm the same panel reports the document as altered — a pipeline that has never been seen to fail validation is a pipeline whose validation has not actually been tested.
Questions that come up in signing code reviews
How big should the /Contents reservation be?
8192 bytes for a detached CMS with a short chain; 16384 when timestamps or embedded intermediates are involved. Measure the CMS your real signer produces and add headroom — the reservation cannot grow later.
Can one document carry two signatures?
Yes. Each signature lives in its own incremental revision, and the second signature's ranges cover the first — which is exactly how counter-signing workflows are built.
Does signing protect the document content?
No. A signature provides integrity and origin evidence; anyone can still read the file. Confidentiality requires encryption, configured independently.
All three signing tiers ship with the HotPDF Component for Delphi and C++Builder; the product page links the complete signature API reference.