Una firma PDF es sobre todo contabilidad de bytes, y la contabilidad de bytes es donde las cosas se tuercen. La criptografía corre sobre código 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 reservado demasiado pequeño para la firma real, un hash calculado sobre el tramo equivocado del archivo, o un "guardar" tras firmar que reescribió en silencio bytes que la firma ya había congelado. Disponga los bytes correctamente y la marca verde se ocupa de sí misma
HotPDF cubre la firma para Delphi y C++Builder en tres niveles, y se elige entre ellos respondiendo a una sola pregunta: ¿dónde vive la clave privada? Un archivo PFX en disco necesita una única llamada a función. Una clave encerrada en un HSM o en un servicio de firma remoto necesita la secuencia reservar-hashear-insertar, porque ninguna biblioteca puede meterse en un token y sacar la clave. Una firma que tenga que satisfacer la normativa europea necesita además las estructuras PAdES baseline. Las secciones siguientes 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 hueco. Antes de firmar, el escritor reserva una entrada /Contents de tamaño fijo llena de ceros y registra un array /ByteRange con los dos tramos a cada lado: todo lo anterior al hueco, todo lo posterior. El firmante calcula el hash de esos dos tramos y escribe el blob CMS resultante en el hueco como hexadecimal. La trampa está en la palabra fijo. Uno se compromete con el tamaño de ese hueco antes de saber cuánto ocupará la firma terminada, así que la reserva tiene que ser una sobreestimación segura. Ocho kilobytes albergan con holgura una firma CMS separada con una cadena de certificados corta
HotPDF separa los dos casos en dos llamadas, y confundirlas es un error temprano habitual. AddSignatureField deja un campo visible vacío para que una persona lo firme más tarde en un visor. AddSignedSignatureField crea el campo y reserva el hueco de /Contents, que es el que se quiere siempre que sea el código, y no un humano, quien complete la firma. Entregue a un firmante externo un campo vacío y no tendrá nada que rellenar
La ruta de una sola llamada: firmar desde un PFX
Cuando el certificado y su clave privada están en un archivo PFX/PKCS#12 que su proceso puede leer, todo el pipeline 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. Lo es el PFX. HotPDF lee contenedores protegidos con PBES2, es decir, derivación de clave PBKDF2 sobre AES-256-CBC. Un PFX exportado por un asistente de certificados de Windows antiguo, o por OpenSSL anterior a 3.0, suele estar envuelto en RC2 o 3DES heredados, y sencillamente no se analizará. La solución es volver a exportar el contenedor una vez con protección moderna; el OpenSSL actual lo hace por defecto, y no es un cambio de código. Así que cuando la firma muere al instante con un certificado que "funciona en todas partes", mire cómo se creó el PFX antes de sospechar de su propio código
La ruta reservar-hashear-insertar para HSM y tokens
La ruta de una sola llamada da por hecho que su proceso puede leer la clave como un archivo. Cada vez más no puede. La clave está en un HSM, en un token USB o detrás de la API de un servicio de firma, y una biblioteca no tiene forma de alcanzarla directamente. HotPDF lo resuelve dividiendo la firma en pasos a nivel de bytes: escribir un documento marcador, pedir a la biblioteca los rangos del hash, pasar la entrada del hash a lo que custodie la clave y después empalmar el CMS devuelto en el hueco
var
Doc: THotPDF;
Fs: TFileStream;
PdfBytes, HashInput, SigHex: AnsiString;
R1Start, R1Len, R2Start, R2Len, CStart, CLen: Integer;
begin
// 1. Escribir el documento con un hueco /Contents reservado
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. Cargar los bytes guardados; los desplazamientos devueltos empiezan en 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. Hashear ambos tramos y firmar externamente (HSM, token, servicio)
HashInput := Copy(PdfBytes, R1Start + 1, R1Len) +
Copy(PdfBytes, R2Start + 1, R2Len);
SigHex := SignWithHsm(HashInput); // su integración: devuelve el CMS en hexadecimal
// 4. Empalmar la firma en el hueco reservado
THotPDF.InsertSignatureHex(PdfBytes, SigHex);
Fs := TFileStream.Create('signed.pdf', fmCreate);
try
Fs.WriteBuffer(PdfBytes[1], Length(PdfBytes));
finally
Fs.Free;
end;
end;
Dos detalles de esta secuencia causan la mayoría de los fallos intermitentes. El primero es que PreparePDFForSigning trabaja sobre los bytes de un archivo terminado. El marcador tiene que escribirse y guardarse por completo antes de que los desplazamientos signifiquen algo; calcúlelos contra un stream que todavía se está ensamblando y no coincidirán con los bytes que finalmente hashee. El segundo es, de nuevo, el tamaño de la reserva. Los 8192 bytes que pidió tienen que albergar el CMS final, y una firma que arrastre certificados intermedios, o una que un servicio decore con atributos firmados, puede sobrepasarlos. InsertSignatureHex no agrandará el hueco para hacer sitio. La señal delatora es un pipeline que firma bien con un certificado y falla con el siguiente; el remedio es regenerar el marcador con una reserva medida a partir de una firma real producida por el firmante de verdad, no adivinada
Los baselines PAdES, y los sellos de tiempo que mantienen viva una firma
Si firma bajo las reglas europeas, la norma en juego es ETSI EN 319 142-1, que apila cuatro niveles PAdES baseline. B-B es la firma simple. B-T añade un sello de tiempo confiable que demuestra cuándo se hizo. B-LT incrusta el material de validación, los certificados y los datos de revocación, dentro del documento para que aún pueda comprobarse años después. B-LTA superpone sellos de tiempo de documento periódicos, de modo que la evidencia sobreviva a los algoritmos con los que se construyó. HotPDF emite las estructuras del lado del documento para cada nivel:
// Campo de firma PAdES baseline (ETSI EN 319 142-1)
Pdf.CurrentPage.AddPAdESSignatureField(
'ApprovalSig', Rect(50, 100, 350, 150), 'B-B',
'Contract approval', 'Boston, MA', 'legal@example.com');
// Sello de tiempo de documento: reserva mayor para el token de la TSA y su cadena
Pdf.CurrentPage.AddDocumentTimestampSignature('ArchiveTS', 16384);
La reserva de 16384 bytes del sello de tiempo es deliberada. Una autoridad de sellado de tiempo devuelve un token que arrastra su propia cadena de certificados, así que habitualmente necesita más espacio que los 8 KB con los que una firma simple se conforma. Esos sellos de tiempo de documento son también la maquinaria detrás de B-LTA: volver a sellar una firma archivada cada pocos años, con algoritmos todavía vigentes, es lo que mantiene verificable en 2040 un documento firmado en 2026
Una palabra sobre las cadenas de motivo, ubicación y contacto 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 contrasta con nada. Rellénelos de forma coherente a partir de los datos de su flujo de trabajo, ya que los auditores sí los leen, y después nunca los confunda con evidencia. La afirmación criptográfica real vive por completo en el CMS y su cadena de certificados, y un verificador ignora el texto visible por completo
Después de firmar, el archivo solo puede crecer
En el momento en que existe una firma, los bytes dentro de sus rangos quedan congelados. La única forma legítima de cambiar el archivo después es una actualización incremental según ISO 32000-1 §7.5.6, que añade los objetos nuevos y modificados tras los bytes originales y encadena una nueva sección de referencias cruzadas hacia ellos. Hecho así, la firma sigue siendo válida para su revisión y el visor informa del estado honesto: la revisión firmada está intacta, el documento se amplió después. Vuelva a serializar el archivo completo en su lugar y reescribirá los tramos firmados, lo que destruye la firma aunque nada visible haya cambiado. Ese mismo mecanismo de revisiones es también la forma en que un documento lleva varias firmas: cada nueva firma aterriza en su propia actualización incremental, y sus rangos cubren todo lo anterior, incluidas las firmas previas. La mecánica de solo anexar, y cuándo es seguro compactarla, se trata en el artículo sobre flujos de objetos y actualizaciones incrementales
Conviene tener presentes dos límites mientras diseña. El modo de salida PDF/A de HotPDF rechaza de plano los campos de firma, así que la conformidad de archivado y una firma incrustada tienen que entregarse como archivos separados. Y firmar no dice nada sobre el secreto: demuestra quién produjo un documento y que no ha cambiado desde entonces, pero cualquiera puede seguir leyéndolo. Ocultar el contenido es un trabajo aparte, a cargo del cifrado AES-256 y la política de permisos
Construya 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 encadena hasta la raíz que esperaba y el panel no informa de cambios desde la firma. Después cambie un solo byte dentro del rango firmado de una copia desechable y confirme que el panel declara ahora el documento como alterado. Un pipeline de firma al que nunca ha visto rechazar un archivo manipulado es uno cuya verificación no se ha probado de verdad
Los tres niveles de firma se incluyen con el HotPDF Delphi Component para Delphi y C++Builder; la página del producto enlaza la referencia completa de la API de firma