Tutorial: assinatura de PDF PAdES em Delphi

Um passo a passo completo que pega uma nota fiscal em PDF não assinada e produz um documento PAdES-B-T com um timestamp RFC 3161 embutido — o nível esperado pelo eIDAS, pelos verificadores AdES e pela maioria dos portais do setor público. Você vai configurar um provedor de chaves (smart card PKCS#11 ou Windows certificate store), escolher o perfil PAdES certo e verificar o arquivo assinado de ponta a ponta.

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

O que você vai construir

Uma aplicação VCL Forms que carrega um PDF, assina com smart card ou PFX, embute um timestamp confiável e grava um arquivo PAdES verificável em disco.

Escolha um nível PAdES

PAdES-B-B (básico), PAdES-B-T (com timestamp), PAdES-B-LT (long-term com dados de revogação) ou PAdES-B-LTA (long-term com archive timestamp). PAdES-B-T é o padrão sensato para notas fiscais, contratos e a maioria dos fluxos do setor público.

Carregue uma chave

O sgcSign suporta 10 provedores de chave. Este tutorial cobre o TsgcPKCS11KeyProvider para tokens em hardware e o TsgcWindowsStoreKeyProvider para certificados já instalados no Windows. O mesmo caminho de código funciona para PFX, PEM, Azure Trusted Signing, AWS KMS e Google KMS.

Assine e verifique

O TsgcSignPDF conduz o pipeline de assinatura, o TsgcSignProfile_PAdES configura o perfil e o TsgcSignatureVerifier valida o documento final. Cada etapa expõe eventos para que você inspecione o que está acontecendo a cada estágio.

Adicione as units

Cada provedor de chaves vive em sua própria unit. Referencie apenas o provedor que você precisa para não linkar units que não vai usar.

Cláusula uses Delphi

Para este tutorial, trazemos dois provedores de chave (PKCS#11 e Windows Store) e as units de assinatura PAdES. O sgcSign_TSA é o cliente de timestamp RFC 3161 — o perfil PAdES o usa automaticamente quando você define uma URL de TSA.

Se você só tem um arquivo PFX, troque sgcSign_KeyProvider_PKCS11 por sgcSign_KeyProvider_PFX e use TsgcPFXKeyProvider como mostrado no Início Rápido de 5 minutos.

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

Configure um provedor de chaves

Duas escolhas comuns — um token de hardware PKCS#11 (smart cards, USB HSMs) ou um certificado já presente no Windows certificate store.

Smart card PKCS#11

Aponte ModulePath para a DLL PKCS#11 do fabricante (por exemplo, C:\Windows\System32\eTPKCS11.dll para SafeNet). A propriedade Slot seleciona o slot do token — use 0 para o primeiro token disponível. O PIN é entregue pelo evento OnPinPrompt, de modo que nunca fica armazenado no código.

Chamar LoadFromToken abre uma sessão, encontra o certificado de assinatura por label ou thumbprint e prepara o handle da chave privada. Nenhum byte sai do token — a assinatura RSA-PSS ou ECDSA acontece, de fato, no 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;

Windows certificate store

Se seu certificado já está instalado no Windows store — por exemplo, porque você usa um cartão eID qualificado com um mini-driver do fornecedor — use o TsgcWindowsStoreKeyProvider. O provedor conversa com a CNG, então SHA-256/SHA-384 funcionam independentemente do CSP original.

Faça o match por subject, por thumbprint ou por número de série. Definir StoreLocation como slCurrentUser usa o store pessoal; slLocalMachine usa o store da máquina e normalmente exige direitos 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;

Configure o perfil PAdES-B-T

O PAdES-B-T adiciona um timestamp RFC 3161 à assinatura para que verificadores possam provar que o documento foi assinado antes de um ponto conhecido no tempo — mesmo que o certificado de assinatura mais tarde expire ou seja revogado.

Propriedades do perfil

Level seleciona o nível AdES — palB, palT, palLT ou palLTA. Para PAdES-B-T defina como palT; o perfil então espera um TSA.URL não vazio.

HashAlgorithm tem como padrão shaSHA256, que é o que todo verificador moderno espera. SHA-384 e SHA-512 também estão disponíveis; SHA-1 intencionalmente não é oferecido, pois o eIDAS não o aceita mais.

O sub-objeto SignatureField controla onde o widget visível de assinatura é colocado dentro do PDF — número da página, coordenadas do retângulo e campos opcionais de texto reason / location / contact. Deixe vazio para assinaturas invisíveis (o padrão).

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;

Assine o PDF

Conecte o provedor de chave e o perfil ao TsgcSignPDF e depois chame SignFile com os nomes dos arquivos de entrada e saída.

Chamada completa de assinatura

SignFile lê o PDF de entrada, calcula o digest do documento, chama o provedor de chave para produzir uma assinatura, busca um timestamp na TSA e grava o resultado. Se a entrada já contém assinaturas, o sgcSign acrescenta uma atualização incremental — as assinaturas originais continuam válidas.

O evento OnProgress reporta cada fase (reading, hashing, signing, timestamping, writing), útil para barras de progresso em arquivos de muitos 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;

Verifique o PDF assinado

Um documento assinado só é útil se verifica. O TsgcSignatureVerifier valida a assinatura criptográfica, a cadeia de certificados e o timestamp embutido em uma única chamada.

Lendo o resultado

O método VerifyFile retorna um TsgcSignatureReport com um detalhamento por assinatura. Para cada entrada você recebe Status (svValid, svInvalid, svUnknown), o subject do signatário, o nível AdES efetivamente detectado, o valor do timestamp e quaisquer problemas de cadeia ou revogação.

Compare Report.Level com o nível esperado — você queria palT, então qualquer verificação que reporte palB significa que a requisição de timestamp falhou silenciosamente. A URL da TSA é o suspeito de sempre.

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;

O que costuma dar errado

Uma lista curta de problemas que mais vemos quando desenvolvedores assinam seu primeiro documento PAdES.

TSA retorna HTTP 200 mas com resposta malformada

Algumas TSAs são apenas HTTPS e rejeitam URLs http:// puro com erro suave. Se Report.Level volta como palB quando você pediu palT, troque a URL da TSA pelo equivalente https://. A maioria das TSAs públicas publica os dois schemes.

Certificado sem extended key usage

Certificados de teste frequentemente não têm o EKU id-kp-documentSigning. O Adobe Reader exibe um aviso mesmo que a assinatura criptográfica seja válida. Para produção, peça à sua CA um certificado com Document Signing EKU explícito.

Documentos PDF/A precisam de PAdES-B-LT

Arquivos PDF/A-2 e PDF/A-3 exigem que toda informação de revogação esteja embutida no próprio documento. Use palLT ou palLTA — o sgcSign vai coletar respostas OCSP e CRLs na hora da assinatura e embuti-las no dicionário DSS.

Sessões de smart card expiram

Alguns drivers PKCS#11 fecham a sessão após alguns segundos de inatividade. Mantenha a instância do TsgcPKCS11KeyProvider viva durante toda a operação de assinatura e chame Free só depois que SignFile retornar.

Para onde ir a partir daqui

Arquivamento de longo prazo, perfis por país, ou levar a assinatura para fora do desktop e para um servidor centralizado.

21 perfis por país

One-liners para VeriFactu, FatturaPA, KSeF, FACTUR-X e os perfis europeus de contrato de trabalho — todos construídos sobre o mesmo motor PAdES / XAdES.

Leia mais →

sgcSign Server

Leve a assinatura para um daemon self-hosted — API REST, integração com GitHub Actions, Authenticode e ClickOnce em cima do PAdES.

Leia mais →

10 provedores de chave

Além de PFX e Windows store — Azure Trusted Signing, AWS KMS, Google KMS, HashiCorp Vault, Certum e provedores de nuvem CSC v2.

Leia mais →

Pronto para assinar seu primeiro PDF?

Baixe o trial, rode este tutorial contra sua própria nota fiscal, licencie o sgcSign quando subir para produção.