Una firma PDF è per gran parte contabilità di byte, ed è nella contabilità dei byte che le cose vanno storte. La crittografia gira su codice sottoposto a revisione da vent'anni, e quella parte non fallisce quasi mai. Ciò che fallisce in produzione è più umile: un segnaposto riservato troppo piccolo per la firma reale, un hash calcolato sul tratto sbagliato del file, oppure un salvataggio dopo la firma che ha silenziosamente riscritto byte che la firma aveva già congelato. Disponete i byte correttamente e il segno di spunta verde arriva da sé
HotPDF copre la firma per Delphi e C++Builder su tre livelli, e scegliete fra essi rispondendo a una sola domanda: dove risiede la chiave privata? Un file PFX su disco richiede una sola chiamata di funzione. Una chiave chiusa in un HSM o in un servizio di firma remoto richiede la sequenza riserva-hash-inserimento, perché nessuna libreria può infilare la mano in un token ed estrarne la chiave. Una firma che deve soddisfare la normativa europea richiede in aggiunta le strutture della baseline PAdES. Le sezioni seguenti seguono questa progressione
Come /ByteRange inchioda i byte firmati
Una firma deve vivere dentro il file che firma, e non può firmare sé stessa. Il PDF aggira il paradosso lasciando un buco. Prima della firma, lo scrittore riserva una voce /Contents di dimensione fissa piena di zeri e registra un array /ByteRange per i due tratti ai lati di essa: tutto ciò che precede il buco, tutto ciò che lo segue. Il firmatario calcola l'hash di quei due tratti e scrive il blob CMS risultante dentro il buco in esadecimale. La trappola sta nella parola fissa. Vi impegnate sulla dimensione di quel buco prima di sapere quanto sarà grande la firma finita, quindi la riserva deve essere una sovrastima sicura. Ottomila byte contengono comodamente una firma CMS separata con una catena di certificati breve
HotPDF separa i due casi in due chiamate, e confonderle è un errore comune agli inizi. AddSignatureField posa un campo vuoto e visibile perché una persona lo firmi in seguito in un visualizzatore. AddSignedSignatureField crea il campo e riserva il buco /Contents, ed è quella che volete ogni volta che a completare la firma sarà del codice anziché una persona. Consegnate a un firmatario esterno un campo vuoto e non avrà nulla da riempire
La via a chiamata singola: firmare da un PFX
Quando il certificato e la sua chiave privata risiedono in un file PFX/PKCS#12 leggibile dal vostro processo, l'intera catena si riduce a una funzione di classe:
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');
Quando questa fallisce, il PDF è raramente il problema. Lo è il PFX. HotPDF legge contenitori protetti con PBES2, cioè derivazione di chiave PBKDF2 su AES-256-CBC. Un PFX esportato da una vecchia procedura guidata dei certificati di Windows, o da OpenSSL prima della 3.0, di solito è invece avvolto in RC2 o 3DES legacy, e semplicemente non viene interpretato. La soluzione è riesportare una volta il contenitore con protezione moderna; l'OpenSSL di oggi lo fa in modo predefinito, e non è una modifica al codice. Perciò quando la firma muore all'istante su un certificato che «funziona ovunque», guardate come è stato creato il PFX prima di sospettare del vostro codice
La via riserva-hash-inserimento per HSM e token
La via a chiamata singola presuppone che il vostro processo possa leggere la chiave come file. Sempre più spesso non può. La chiave risiede in un HSM, su un token USB o dietro l'API di un servizio di firma, e non c'è modo per una libreria di raggiungerla direttamente. HotPDF gestisce quel caso spezzando la firma in passi a livello di byte: scrivere un documento segnaposto, chiedere alla libreria gli intervalli da sottoporre ad hash, passare l'ingresso dell'hash a chi custodisce la chiave, poi innestare il CMS restituito dentro il buco
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Scrive il documento con un buco /Contents riservato
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. Carica i byte salvati; gli offset restituiti partono da 0
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. Calcola l hash dei due tratti e firma esternamente (HSM, token, servizio)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // vostra integrazione: CMS in esadecimale
// 4. Innesta la firma nel buco riservato
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Due dettagli di questa sequenza causano la maggior parte dei fallimenti intermittenti. Il primo è che PreparePDFForSigning lavora sui byte di un file finito. Il segnaposto va scritto e salvato per intero prima che gli offset significhino qualcosa; calcolateli su uno stream ancora in assemblaggio e non combaceranno con i byte di cui poi calcolate l'hash. Il secondo è di nuovo la dimensione della riserva. Gli 8192 byte che avete chiesto devono contenere il CMS finale, e una firma che porta con sé certificati intermedi, o che un servizio decora con attributi firmati, può sforare. InsertSignatureHex non allargherà il buco per fare spazio. Il segnale è una catena che firma senza problemi con un certificato e fallisce con il successivo; la cura è rigenerare il segnaposto con una riserva misurata su una firma reale prodotta dal firmatario effettivo, non tirata a indovinare
Le baseline PAdES e le marche temporali che tengono viva una firma
Se firmate sotto le regole europee, la norma in gioco è la ETSI EN 319 142-1, che impila quattro livelli di baseline PAdES. B-B è la firma semplice. B-T aggiunge una marca temporale attendibile che prova quando è stata apposta. B-LT incorpora il materiale di validazione, i certificati e i dati di revoca, dentro il documento così da poterlo verificare anche anni dopo. B-LTA sovrappone marche temporali di documento periodiche, così le prove sopravvivono agli algoritmi su cui erano state costruite. HotPDF emette le strutture lato documento per ciascun livello:
// Campo firma della baseline PAdES (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Marca temporale di documento: riserva più ampia per token TSA e catena
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
La riserva di 16384 byte sulla marca temporale è voluta. Una autorità di marcatura temporale restituisce un token che si trascina dietro la propria catena di certificati, quindi ha normalmente bisogno di più spazio degli 8 KB che bastano a una firma semplice. Quelle marche temporali di documento sono anche il meccanismo dietro B-LTA: rimarcare temporalmente una firma archiviata ogni pochi anni, con algoritmi ancora attuali, è ciò che mantiene verificabile nel 2040 un documento firmato nel 2026
Una parola sulle stringhe di motivo, luogo e contatto che entrambe le chiamate di campo accettano: sono metadati di comodo e nulla più. HotPDF le conserva come semplici voci di dizionario e le disegna nell'aspetto visibile della firma, ma nessun validatore le confronta con alcunché. Compilatele in modo coerente con i dati del vostro flusso di lavoro, dato che i revisori le leggono, e poi non scambiatele mai per prove. L'affermazione crittografica vera vive interamente nel CMS e nella sua catena di certificati, e un verificatore ignora del tutto il testo visibile
Dopo la firma il file può soltanto crescere
Nel momento in cui una firma esiste, i byte dentro i suoi intervalli sono congelati. L'unico modo legittimo di cambiare il file in seguito è un aggiornamento incrementale ISO 32000-1 §7.5.6, che accoda oggetti nuovi e modificati dopo i byte originali e concatena una nuova sezione di riferimenti incrociati che punta a essi. Fatto così, la firma resta valida per la sua revisione e un visualizzatore riporta lo stato onesto: la revisione firmata è integra, il documento è stato esteso in seguito. Riserializzate invece l'intero file e riscrivete i tratti firmati, il che distrugge la firma anche quando nulla di visibile è cambiato. Lo stesso meccanismo di revisione è anche il modo in cui un documento porta più firme: ogni nuova firma finisce nel proprio aggiornamento incrementale, e i suoi intervalli coprono tutto ciò che la precede, comprese le firme precedenti. La meccanica a sola aggiunta, e quando sia sicuro compattarla, è trattata nell'articolo su object stream e aggiornamenti incrementali
Due confini vanno tenuti a mente in fase di progetto. La modalità di uscita PDF/A di HotPDF rifiuta i campi firma di netto, quindi conformità archivistica e firma incorporata devono viaggiare come file separati. E la firma non dice nulla sulla segretezza: prova chi ha prodotto un documento e che non è cambiato da allora, ma chiunque può comunque leggerlo. Nascondere il contenuto è un compito a parte, gestito dalla cifratura AES-256 e dalla politica dei permessi
Qualunque cosa costruiate, collaudatela con qualcosa di diverso dal codice che ha scritto il file. Aprite l'uscita nel pannello firme di Acrobat e confermate tre cose: la firma è valida, l'identità si concatena fino alla radice che vi aspettavate, e il pannello non segnala modifiche dopo la firma. Poi cambiate un solo byte dentro l'intervallo firmato di una copia usa e getta e confermate che ora il pannello dichiari il documento alterato. Una catena di firma che non avete mai visto respingere un file manomesso è una catena la cui verifica non è stata davvero collaudata
Tutti e tre i livelli di firma sono distribuiti con lo HotPDF Delphi Component per Delphi e C++Builder; la pagina di prodotto rimanda al riferimento completo dell'API di firma