Una firma PDF es principalmente la contabilidad de bytes, y ahí es donde las cosas salen mal. La criptografía se ejecuta en código que ha sido auditado durante dos décadas, y esa parte casi nunca falla. Lo que falla en producción es más humilde: un marcador de posición (placeholder) reservado demasiado pequeño para la firma real, un hash calculado sobre el tramo incorrecto del archivo, o un "guardar" después de firmar que reescribió silenciosamente los bytes que la firma ya había congelado. Distribuya los bytes correctamente y la marca de verificación verde se cuidará sola
HotPDF cubre la firma para Delphi y C++Builder en tres niveles, y usted elige entre ellos respondiendo una pregunta: ¿dónde reside la clave privada? Un archivo PFX en disco necesita una sola llamada de función. Una clave bloqueada en un HSM o un servicio de firma remota necesita la secuencia reservar-hash-insertar, porque ninguna biblioteca puede acceder a un token y extraer la clave. Una firma que tiene que satisfacer la regulación europea necesita las estructuras base PAdES además de eso. Las siguientes secciones siguen esa progresión
Cómo /ByteRange fija los bytes firmados
Una firma tiene que vivir dentro del archivo que firma, y no puede firmarse a sí misma. PDF sortea la paradoja dejando un agujero. Antes de firmar, el escritor reserva una entrada /Contents de tamaño fijo llena de ceros y registra un arreglo /ByteRange para los dos tramos a cada lado: todo antes del agujero, todo después. El firmante calcula el hash de esos dos tramos y escribe el blob CMS resultante en el agujero como hexadecimal. La trampa está en la palabra fijo. Usted se compromete con el tamaño de ese agujero antes de saber qué tan grande será la firma terminada, por lo que la reserva tiene que ser una sobreestimación segura. Ocho kilobytes contienen cómodamente una firma CMS independiente con una cadena de certificados corta
HotPDF divide los dos casos en dos llamadas, y confundirlos es un error común al principio. AddSignatureField deja un campo vacío y visible para que una persona lo firme más tarde en un visor. AddSignedSignatureField crea el campo y reserva el agujero /Contents, que es el que usted desea siempre que el código, en lugar de un humano, vaya a completar la firma. Entregue a un firmante externo un campo vacío y no tendrá nada que llenar
La ruta de una llamada: firmar desde un PFX
Cuando el certificado y su clave privada residen en un archivo PFX/PKCS#12 que su proceso puede leer, toda la canalización se reduce a una función de clase:
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');
Cuando esto falla, el PDF rara vez es el problema. El PFX lo es. HotPDF lee contenedores protegidos con PBES2, lo que significa derivación de claves PBKDF2 sobre AES-256-CBC. Un PFX exportado por un asistente de certificados antiguo de Windows, o por OpenSSL antes de la versión 3.0, normalmente está envuelto en RC2 heredado o 3DES en su lugar, y simplemente no se analizará. La solución es volver a exportar el contenedor una vez con protección moderna; hoy en día OpenSSL hace esto de forma predeterminada, y no es un cambio de código. Así que, cuando la firma muere instantáneamente en un certificado que "funciona en todas partes", observe cómo se creó el PFX antes de sospechar de su propio código
La ruta reservar-hash-insertar para HSMs y tokens
La ruta de una llamada asume que su proceso puede leer la clave como un archivo. Cada vez más, no puede. La clave reside en un HSM, en un token USB o detrás de la API de un servicio de firma, y no hay forma de que una biblioteca la alcance directamente. HotPDF maneja eso dividiendo la firma en pasos a nivel de bytes: escribir un documento de marcador de posición, pedirle a la biblioteca los rangos de hash, pasar la entrada del hash a lo que sea que contenga la clave, y luego empalmar el CMS devuelto de nuevo en el agujero
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;
Dos detalles en esta secuencia causan la mayoría de las fallas intermitentes. El primero es que PreparePDFForSigning funciona sobre los bytes de un archivo terminado. El marcador de posición (placeholder) tiene que ser escrito y guardado por completo antes de que las posiciones (offsets) signifiquen algo; cálculelas contra un flujo que aún se está ensamblando y no se alinearán con los bytes a los que eventualmente les aplicará el hash. El segundo es el tamaño de la reserva, de nuevo. Los 8192 bytes que solicitó tienen que contener el CMS final, y una firma que transporta certificados intermedios, o una que un servicio decora con atributos firmados, puede sobrepasarlo. InsertSignatureHex no agrandará el agujero para hacer espacio. El indicio es una canalización que firma bien con un certificado y falla con el siguiente; la cura es regenerar el marcador de posición con una reserva medida a partir de una firma real producida por el firmante real, no adivinada
Niveles base PAdES y las marcas de tiempo que mantienen viva una firma
Si está firmando bajo las reglas europeas, el estándar en juego es ETSI EN 319 142-1, que apila cuatro niveles base PAdES. B-B es la firma simple. B-T agrega una marca de tiempo confiable que prueba cuándo se realizó. B-LT incrusta el material de validación, los certificados y los datos de revocación, dentro del documento para que aún pueda verificarse años después. B-LTA superpone marcas de tiempo de documentos periódicas en la parte superior, por lo que la evidencia sobrevive a los algoritmos sobre los que se construyó. HotPDF emite las estructuras del lado del documento para cada nivel:
// 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);
La reserva de 16384 bytes en la marca de tiempo es deliberada. Una autoridad de marcas de tiempo devuelve un token que arrastra su propia cadena de certificados, por lo que habitualmente necesita más espacio que los 8 KB con los que se conforma una firma simple. Esas marcas de tiempo de documento también son la maquinaria detrás de B-LTA: volver a poner una marca de tiempo a una firma archivada cada pocos años, con algoritmos que aún están vigentes, es lo que mantiene un documento que usted firmó en 2026 verificable en 2040
Unas palabras sobre las cadenas de motivo (reason), ubicación (location) y contacto (contact) que aceptan ambas llamadas de campo: son metadatos de conveniencia y nada más. HotPDF los almacena como entradas de diccionario simples y los pinta en la apariencia visible de la firma, pero ningún validador los verifica contra nada. Lhénelos consistentemente a partir de los datos de su flujo de trabajo, ya que los auditores sí los leen, y luego nunca los confunda con evidencia. La verdadera afirmación criptográfica vive enteramente en el CMS y su cadena de certificados, y un verificador ignora por completo el texto visible
Después de firmar, el archivo solo puede crecer
En el momento en que existe una firma, los bytes dentro de sus rangos se congelan. La única forma legítima de cambiar el archivo después es una actualización incremental ISO 32000-1 §7.5.6, que anexa objetos nuevos y cambiados después de los bytes originales y encadena una nueva sección de referencias cruzadas hacia ellos. Hecho de esa manera, la firma permanece válida para su revisión y un visor reporta el estado honesto: la revisión firmada está intacta, el documento fue extendido posteriormente. Si vuelve a serializar el archivo completo en su lugar y reescribe los tramos firmados, destruirá la firma incluso cuando nada visible cambie. El mismo mecanismo de revisión es también la forma en que un documento contiene varias firmas: cada nueva firma aterriza en su propia actualización incremental, y sus rangos cubren todo lo anterior, incluyendo las firmas tempranas. La mecánica de solo anexar (append-only), y cuándo es seguro compactarla, se cubren en el artículo sobre flujos de objetos y actualizaciones incrementales
Vale la pena tener en mente dos límites mientras se diseña. El modo de salida PDF/A de HotPDF rechaza de plano los campos de firma, por lo que la conformidad de archivo y una firma incrustada tienen que enviarse como archivos separados. Y la firma no dice nada sobre el secreto: prueba quién produjo un documento y que no ha cambiado desde entonces, pero cualquiera puede leerlo. Ocultar el contenido es un trabajo separado, manejado por la encriptación AES-256 y la política de permisos
Independientemente de lo que construya, pruébelo con algo distinto del código que escribió el archivo. Abra la salida en el panel de firmas de Acrobat y confirme tres cosas: la firma es válida, la identidad se encadena a la raíz que esperaba, y el panel no reporta cambios desde la firma. Luego invierta un solo byte dentro del rango firmado de una copia desechable y confirme que el panel ahora llama alterado al documento. Una canalización de firma a la que nunca ha visto rechazar un archivo manipulado es una cuya verificación realmente no ha sido probada
Los tres niveles de firma se incluyen con el HotPDF Component para Delphi y C++Builder; la página del producto enlaza a la referencia completa de la API de firmas