O HotPDF verifica assinaturas CMS ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 e Ed448 em documentos PDF carregados, e assina por meio de provedores conectáveis, de modo que a chave privada nunca precisa viver dentro do seu processo Delphi. Essa segunda parte é o que a maioria das equipes precisa primeiro. Um token de hardware, um serviço remoto de assinatura e um cartão nacional de eID, todos se recusam a entregar uma chave, e até que o pipeline de assinatura esteja separado do armazenamento de chaves, nenhum deles pode ser usado
A separação é o ponto de THPDFSignatureProvider. O HotPDF mantém as partes que ele deve deter — analisar CMS, construir SignedData, dispor o /ByteRange — e delega a única operação que ele não pode deter, que é transformar um digest em uma assinatura com uma chave que não tem permissão para ver. Tudo abaixo decorre dessa divisão
Por que uma assinatura ML-DSA válida falha ao verificar?
Porque o HotPDF recusa ML-DSA em um documento carregado que não declara a extensão para ela. ML-DSA — o esquema de assinatura em reticulado padronizado como FIPS 204, e o motivo pelo qual as pessoas dizem "PDF pós-quântico" — ainda não tem registro na ISO 32000-2. Um PDF que carrega uma está usando um algoritmo que o padrão base não nomeia, e um arquivo que usa silenciosamente um algoritmo sem nome é um arquivo cujo veredito não pode ser reproduzido por mais ninguém
Então o HotPDF torna a reivindicação explícita. EnsureMLDSAExtensions eleva o documento para PDF 2.0 quando permitido e escreve /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> no Catálogo. No lado da leitura, LoadedDocumentDeclaresMLDSAExtension informa se essa declaração sobreviveu, e VerifyLoadedSignatureWithOptions aplica o mesmo teste antes de honrar Options.AllowMLDSA. Definir o flag em um documento não declarado e ele segue desligado — a opção pode afrouxar a política, nunca o requisito estrutural
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'contract-pq.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 720, 0, 'Supply agreement 2026-114');
Pdf.EnsureMLDSAExtensions; // declare before the signature is written
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Chame antes de salvar, não depois. A declaração faz parte do intervalo de bytes assinado, e um Catálogo remendado depois é ou uma alteração não assinada em um arquivo assinado, ou uma segunda revisão que um validador relatará como modificação
Três famílias de algoritmos, um ponto de entrada de verificação
Todas as três famílias chegam por VerifyLoadedSignatureWithOptions, que recebe um índice de assinatura, o stream de origem, um registro THPDFCMSVerifyOptions e um parâmetro de saída para os detalhes da assinatura. O registro tem exatamente três campos, e cada um responde a uma pergunta que antes exigia uma reconstrução
SignatureProvider substitui o seu próprio provedor pelo provedor embutido da plataforma. OpenSSLLibraryPath seleciona uma biblioteca OpenSSL 3, que é o que fornece a verificação Ed25519 e Ed448 em modo puro que o Windows CNG não oferece em toda parte. AllowMLDSA opta pelos algoritmos em reticulado, sujeito à verificação de extensão acima. O OID exato do algoritmo que foi reconhecido volta em THPDFSignatureInfo.SignatureAlgorithmOID, então um log de auditoria pode registrar o que foi verificado em vez do que foi solicitado
var
Opts: THPDFCMSVerifyOptions;
Info: THPDFSignatureInfo;
Status: THPDFSignatureVerifyStatus;
Src: TFileStream;
begin
Opts := THPDFCMSVerifyOptions.Default;
Opts.OpenSSLLibraryPath := 'C:\openssl3\libcrypto-3-x64.dll';
Opts.AllowMLDSA := Pdf.LoadedDocumentDeclaresMLDSAExtension;
Src := TFileStream.Create('contract-pq.pdf', fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignatureWithOptions(0, Src, Opts, Info);
if Status = svValid then
Memo1.Lines.Add('signed with OID ' + string(Info.SignatureAlgorithmOID));
finally
Src.Free;
end;
end;
Ed25519 e Ed448 não precisam de declaração de extensão, porque a ISO 32000-2 já as admite. Precisam, sim, de um provedor que as implemente, o que na maioria das implantações Windows significa apontar OpenSSLLibraryPath para uma biblioteca que você entrega e controla em vez de para qualquer uma que por acaso esteja na máquina
O que um provedor de assinatura de fato promete?
Um provedor promete uma coisa: dado um pedido, devolva um status e, ao assinar, bytes. THPDFSignatureProviderRequest carrega o algoritmo e seu OID, o OID do digest, o comprimento do salt PSS, se a entrada é uma mensagem ou um digest já calculado, a própria entrada, a chave pública ou certificado, um identificador de chave e um identificador de operação. Nada nesse registro é específico do HotPDF — é o vocabulário que um driver de token ou um serviço de assinatura já fala
Três implementações vêm com a biblioteca. THPDFCallbackSignatureProvider envolve funções anônimas, que é o caminho mais curto de uma rotina de assinatura interna existente para uma assinatura PDF funcional. THPDFRemoteSignatureProvider envolve um callback de transporte com limite de retentativas, um registro de cancelamento e limites de tamanho de entrada e assinatura, de modo que um HSM travado não possa se tornar uma aplicação travada. THPDFPKCS11SignatureProvider serializa operações RSA contra uma sessão PKCS#11 pertencente ao chamador, já autenticada, e um handle de chave privada — o HotPDF nunca faz login, nunca vê um PIN e nunca fecha uma sessão que não abriu
var
Provider: THPDFRemoteSignatureProvider;
begin
Provider := THPDFRemoteSignatureProvider.Create(
function(const Req: THPDFSignatureProviderRequest; Attempt: Integer;
out Signature: TBytes): THPDFSignatureProviderStatus
begin
// POST Req.Input to the signing service; Req.KeyIdentifier selects the key
if PostToSigningService(Req.KeyIdentifier, Req.Input, Signature) then
Result := spsValid
else
Result := spsProviderError;
end,
3, // RetryLimit
1048576, // MaxInputBytes
65536); // MaxSignatureBytes
try
// hand Provider to the signing call
finally
Provider.Free;
end;
end;
Por que o enum de status tem seis valores em vez de um booleano
THPDFSignatureProviderStatus distingue spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError e spsCancelled, e colapsá-los custa a capacidade de agir corretamente. Uma assinatura criptograficamente errada (spsInvalid) é um evento de segurança. Um algoritmo que o provedor não implementa (spsUnsupported) é uma lacuna de implantação. Uma falha de transporte (spsProviderError) merece retentativa, e um prompt de token cancelado pelo usuário (spsCancelled) não merece retentativa alguma
A regra para assinatura é estreita: um provedor de assinatura devolve spsValid apenas com uma assinatura não vazia. Provedores de verificação devolvem spsValid ou spsInvalid, e os outros quatro seguem distintos em ambos os caminhos. Se você escrever um provedor, resista à tentação de mapear tudo o que não reconhece para spsInvalid — isso transforma uma DLL ausente em um relato de que a assinatura do cliente é forjada
Onde a assinatura de fato pousa no arquivo
Duas funções conectam provedores a bytes PDF reais. HPDFCMSBuildSignedDataWithProvider constrói CMS desacoplado a partir de um digest SHA-256 do documento, que é o ponto de entrada certo quando o seu fluxo calcula o digest em outro lugar. HPDFCMSSignPDFStreamWithProvider assina um placeholder de assinatura existente em um stream PDF e preserva o pipeline padrão de /ByteRange, que é o ponto de entrada certo quando o próprio HotPDF dispôs o placeholder
Preservar esse pipeline importa mais do que parece. A convenção /ByteRange — duas faixas que ignoram a janela hexadecimal da assinatura — é o que todo validador verifica primeiro, e um caminho baseado em provedor que a reescrevesse quebraria a conformidade PAdES não importando o quão sólida fosse a criptografia. O HotPDF mantém o layout idêntico ao caminho de assinatura embutido, então um documento assinado por um token PKCS#11 verifica com o mesmo código de verificação de assinatura que um assinado a partir de um arquivo PFX. Para as regras de perfil que ficam acima da escolha de algoritmo, veja o guia sobre assinaturas baseline PAdES em Delphi, e para as armadilhas de codificação específicas de ECDSA que antecedem esse modelo de provedor, as notas sobre verificação CMS ECDSA e formatos de assinatura P1363
Uma ordem de migração que não encalha os seus documentos
Prontidão pós-quântica é um problema de cronograma, não um interruptor. Quase nenhum visualizador de PDF implantado valida ML-DSA hoje, então um documento assinado apenas com ela é, do ponto de vista do leitor, um documento com uma assinatura inverificável. A ordem que sobrevive ao contato com arquivos reais é: manter RSA ou ECDSA como a assinatura que um validador vai julgar, adicionar a declaração de extensão e uma segunda assinatura ML-DSA quando uma política exigir evidência resistente a quânticos, e mudar a assinatura primária apenas quando os sistemas consumidores tiverem alcançado o mesmo ponto
O que o HotPDF dá a você hoje é a capacidade de escrever e verificar ambas, a partir do mesmo código, com o algoritmo registrado honestamente no arquivo e no resultado da verificação. O HotPDF é um componente VCL nativo de PDF para Delphi e C++Builder sem runtime externo de PDF, de modo que os caminhos de assinatura e verificação vão dentro do seu executável em vez de ao lado dele — veja a página do componente HotPDF para Delphi para a lista completa de recursos e download de avaliação