Tutorial: Firma de PDF con PAdES en Delphi

Recorrido completo que parte de un PDF de factura sin firmar y produce un documento PAdES-B-T con un sello de tiempo RFC 3161 embebido — el nivel exigido por eIDAS, los verificadores AdES y la mayoría de portales del sector público. Configurarás un proveedor de claves (tarjeta inteligente PKCS#11 o almacén de certificados de Windows), elegirás el perfil PAdES adecuado y verificarás el archivo firmado de principio a fin.

PAdES-B-B / B-T / B-LT / B-LTA
PKCS#11 / Almacén Windows / PFX
Delphi 7 – RAD Studio 13

Lo que vas a construir

Una aplicación VCL Forms que carga un PDF, lo firma con una tarjeta inteligente o un PFX, embebe un sello de tiempo confiable y escribe un archivo PAdES verificable en disco.

Elige un nivel PAdES

PAdES-B-B (básico), PAdES-B-T (con sello de tiempo), PAdES-B-LT (largo plazo con datos de revocación) o PAdES-B-LTA (largo plazo con sello de tiempo de archivo). PAdES-B-T es la elección sensata por defecto para facturas, contratos y la mayoría de flujos del sector público.

Carga una clave

sgcSign admite 10 proveedores de claves. Este tutorial cubre TsgcPKCS11KeyProvider para tokens hardware y TsgcWindowsStoreKeyProvider para certificados ya instalados en Windows. La misma ruta de código funciona con PFX, PEM, Azure Trusted Signing, AWS KMS y Google KMS.

Firma y verifica

TsgcSignPDF conduce el pipeline de firma, TsgcSignProfile_PAdES configura el perfil y TsgcSignatureVerifier valida el documento final. Cada paso expone eventos para que puedas inspeccionar lo que ocurre en cada fase.

Añade las units

Cada proveedor de claves vive en su propia unit. Referencia sólo el proveedor que necesitas para no enlazar units que no vas a usar.

Cláusula uses de Delphi

Para este tutorial incluimos dos proveedores de claves (PKCS#11 y Almacén Windows) y las units de firma PAdES. sgcSign_TSA es el cliente de sello de tiempo RFC 3161 — el perfil PAdES lo usa automáticamente cuando estableces una URL de TSA.

Si sólo tienes un archivo PFX, sustituye sgcSign_KeyProvider_PKCS11 por sgcSign_KeyProvider_PFX y usa TsgcPFXKeyProvider como se muestra en la Guía Rápida de 5 minutos.

uPDFSigning.pas
uses
  Classes, SysUtils,
  // sgc
  sgcSign_KeyProvider_PKCS11,
  sgcSign_KeyProvider_WindowsStore,
  sgcSign_PDF,
  sgcSign_Profile_PAdES,
  sgcSign_TSA,
  sgcSign_Verifier;

Configura un proveedor de claves

Dos opciones habituales — un token hardware PKCS#11 (tarjetas inteligentes, HSM USB) o un certificado ya presente en el almacén de certificados de Windows.

Tarjeta inteligente PKCS#11

Apunta ModulePath a la DLL PKCS#11 del fabricante (por ejemplo C:\Windows\System32\eTPKCS11.dll para SafeNet). La propiedad Slot selecciona la ranura del token — usa 0 para el primer token disponible. El PIN se entrega vía el evento OnPinPrompt, así nunca queda almacenado en el código fuente.

Llamar a LoadFromToken abre una sesión, busca el certificado de firma por etiqueta o huella y prepara el handle de la clave privada. Ningún byte sale del token — la firma real RSA-PSS o ECDSA se realiza dentro del dispositivo.

step1-pkcs11.pas
var
  vKeyProvider: TsgcPKCS11KeyProvider;
begin
  vKeyProvider := TsgcPKCS11KeyProvider.Create(nil);
  try
    vKeyProvider.ModulePath := 'C:\Windows\System32\eTPKCS11.dll';
    vKeyProvider.Slot := 0;
    vKeyProvider.CertificateLabel := 'My Signing Cert';
    vKeyProvider.OnPinPrompt :=
      procedure(Sender: TObject; var aPIN: string)
      begin
        aPIN := InputBox('Smart card', 'PIN:', '');
      end;
    vKeyProvider.LoadFromToken;
    // vKeyProvider is now ready to sign
  finally
    // keep alive until signing finishes, then Free
  end;
end;

Almacén de certificados de Windows

Si tu certificado ya está instalado en el almacén de Windows — por ejemplo porque usas un eID cualificado con un mini-driver del fabricante — usa TsgcWindowsStoreKeyProvider. El proveedor habla con CNG, así que SHA-256/SHA-384 funcionan independientemente del CSP original.

Coincide por sujeto, por huella o por número de serie. Establecer StoreLocation a slCurrentUser usa el almacén personal; slLocalMachine usa el almacén de máquina y normalmente requiere derechos de administrador.

step1-winstore.pas
var
  vKeyProvider: TsgcWindowsStoreKeyProvider;
begin
  vKeyProvider := TsgcWindowsStoreKeyProvider.Create(nil);
  vKeyProvider.StoreLocation := slCurrentUser;
  vKeyProvider.StoreName := 'MY';
  vKeyProvider.Thumbprint :=
    '1234ABCD5678EF901234567890ABCDEF12345678';
  vKeyProvider.LoadFromStore;
end;

Configura el perfil PAdES-B-T

PAdES-B-T añade un sello de tiempo RFC 3161 a la firma para que los verificadores puedan demostrar que el documento se firmó antes de un instante conocido — incluso si el certificado de firma caduca o se revoca más adelante.

Propiedades del perfil

Level selecciona el nivel AdES — palB, palT, palLT o palLTA. Para PAdES-B-T ponlo a palT; el perfil exige entonces un TSA.URL no vacío.

HashAlgorithm tiene por defecto shaSHA256, que es lo que esperan todos los verificadores modernos. SHA-384 y SHA-512 también están disponibles; SHA-1 deliberadamente no se ofrece porque eIDAS ya no lo acepta.

El sub-objeto SignatureField controla dónde se coloca el widget de firma visible dentro del PDF — número de página, coordenadas del rectángulo y campos opcionales de motivo / ubicación / contacto. Déjalo vacío para firmas invisibles (el comportamiento por defecto).

step2-profile.pas
var
  vProfile: TsgcSignProfile_PAdES;
begin
  vProfile := TsgcSignProfile_PAdES.Create(nil);
  vProfile.Level := palT;
  vProfile.HashAlgorithm := shaSHA256;

  // RFC 3161 timestamp authority
  vProfile.TSA.URL := 'http://timestamp.digicert.com';
  vProfile.TSA.HashAlgorithm := shaSHA256;

  // Optional visible signature widget
  vProfile.SignatureField.Visible := True;
  vProfile.SignatureField.Page := 1;
  vProfile.SignatureField.Rect := Rect(50, 50, 250, 120);
  vProfile.SignatureField.Reason := 'Approved';
  vProfile.SignatureField.Location := 'Madrid';
end;

Firma el PDF

Conecta el proveedor de claves y el perfil a TsgcSignPDF y llama a SignFile con los nombres del archivo de entrada y salida.

Llamada completa de firma

SignFile lee el PDF de entrada, calcula el digest del documento, llama al proveedor de claves para producir una firma, obtiene un sello de tiempo de la TSA y escribe el resultado. Si la entrada ya contiene firmas, sgcSign añade una actualización incremental — las firmas originales siguen siendo válidas.

El evento OnProgress informa de cada fase (lectura, hashing, firma, sellado de tiempo, escritura), útil para barras de progreso en archivos de varios MB.

step3-sign.pas
var
  vSigner: TsgcSignPDF;
begin
  vSigner := TsgcSignPDF.Create(nil);
  try
    vSigner.KeyProvider := vKeyProvider;
    vSigner.Profile := vProfile;
    vSigner.OnProgress :=
      procedure(Sender: TObject; const aPhase: string; aPct: Integer)
      begin
        Memo1.Lines.Add(Format('%s — %d%%', [aPhase, aPct]));
      end;
    vSigner.SignFile('invoice.pdf', 'invoice-signed.pdf');
  finally
    vSigner.Free;
  end;
end;

Verifica el PDF firmado

Un documento firmado sólo es útil si verifica. TsgcSignatureVerifier valida la firma criptográfica, la cadena de certificación y el sello de tiempo embebido en una sola llamada.

Lectura del resultado

El método VerifyFile devuelve un TsgcSignatureReport con un desglose por firma. Para cada entrada obtienes Status (svValid, svInvalid, svUnknown), el sujeto firmante, el nivel AdES realmente detectado, el valor del sello de tiempo y cualquier problema de cadena o revocación.

Compara Report.Level contra el nivel esperado — tú querías palT, así que cualquier verificación que reporte palB significa que la petición de sello de tiempo falló silenciosamente. La URL de la TSA suele ser la culpable.

step4-verify.pas
var
  vVerifier: TsgcSignatureVerifier;
  vReport: TsgcSignatureReport;
  i: Integer;
begin
  vVerifier := TsgcSignatureVerifier.Create(nil);
  try
    vReport := vVerifier.VerifyFile('invoice-signed.pdf');
    for i := 0 to vReport.SignatureCount - 1 do
    begin
      Memo1.Lines.Add('Signer:    ' + vReport.Signatures[i].Subject);
      Memo1.Lines.Add('Level:     ' + vReport.Signatures[i].LevelAsString);
      Memo1.Lines.Add('Status:    ' + vReport.Signatures[i].StatusAsString);
      Memo1.Lines.Add('TSA time:  ' +
        DateTimeToStr(vReport.Signatures[i].TimestampUTC));
    end;
  finally
    vVerifier.Free;
  end;
end;

Lo que suele fallar

Una lista breve de los problemas que más vemos cuando los desarrolladores firman su primer documento PAdES.

La TSA devuelve HTTP 200 pero una respuesta malformada

Algunas TSAs son sólo HTTPS y rechazan las URLs http:// en claro con un error suave. Si Report.Level sale como palB cuando pediste palT, cambia la URL de la TSA por su equivalente https://. La mayoría de TSAs públicas publican ambos esquemas.

El certificado no tiene extended key usage

Los certificados de prueba suelen carecer del EKU id-kp-documentSigning. Adobe Reader mostrará una advertencia aunque la firma criptográfica sea válida. Para producción, solicita a tu CA un certificado con EKU Document Signing explícito.

Los documentos PDF/A necesitan PAdES-B-LT

Los archivos PDF/A-2 y PDF/A-3 requieren que toda la información de revocación esté embebida dentro del propio documento. Usa palLT o palLTA — sgcSign recopilará respuestas OCSP y CRLs en el momento de la firma y las embeberá en el diccionario DSS.

Las sesiones de tarjeta inteligente caducan

Algunos drivers PKCS#11 cierran la sesión tras unos segundos de inactividad. Mantén viva la instancia TsgcPKCS11KeyProvider durante toda la operación de firma y llama a Free sólo después de que SignFile retorne.

Por dónde seguir

Archivado a largo plazo, perfiles por país, o sacar la firma del escritorio y llevarla a un servidor centralizado.

21 perfiles por país

Configuraciones de una línea para VeriFactu, FatturaPA, KSeF, FACTUR-X y los perfiles europeos de contrato laboral — todos construidos sobre el mismo motor PAdES / XAdES.

Leer más →

sgcSign Server

Lleva la firma a un servicio autoalojado — API REST, integración con GitHub Actions, Authenticode y ClickOnce sobre PAdES.

Leer más →

10 proveedores de claves

Más allá de PFX y almacén Windows — Azure Trusted Signing, AWS KMS, Google KMS, HashiCorp Vault, Certum y proveedores cloud CSC v2.

Leer más →

¿Listo para firmar tu primer PDF?

Descarga la versión de prueba, ejecuta este tutorial contra tu propia factura y licencia sgcSign cuando lo lleves a producción.