HotPDF verifierar ML-DSA-44-, ML-DSA-65-, ML-DSA-87-, Ed25519- och Ed448-CMS-signaturer i laddade PDF-dokument, och det signerar genom pluggbara leverantörer så att den privata nyckeln aldrig behöver leva inuti din Delphi-process. Den andra halvan är den del de flesta team behöver först. En hårdvarutoken, en fjärrsigneringstjänst och ett nationellt eID-kort vägrar samtliga att lämna över en nyckel, och fram till att signeringens pipeline separeras från nyckellagret kan inget av dem användas alls
Uppdelningen är poängen med THPDFSignatureProvider. HotPDF behåller de delar det bör äga — tolka CMS, bygga SignedData, lägga upp /ByteRange — och delegerar den enda operationen det inte kan äga, vilket är att förvandla ett digest till en signatur med en nyckel det inte får se. Allt som följer nedan härrör från den uppdelningen
Varför misslyckas en giltig ML-DSA-signatur med att verifieras?
Eftersom HotPDF vägrar ML-DSA på ett laddat dokument som inte deklarerar tillägget för det. ML-DSA — det gittersignaturschema som standardiserats som FIPS 204, och anledningen till att folk säger "postkvantum-PDF" — har ingen ISO 32000-2-registrering än. En PDF som bär en sådan använder en algoritm som basstandarden inte namnger, och en fil som tyst använder en onämnd algoritm är en fil vars utslag inte kan reproduceras av någon annan
Så HotPDF gör anspråket explicit. EnsureMLDSAExtensions höjer dokumentet till PDF 2.0 där det är tillåtet och skriver /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> i katalogen. På läsningssidan rapporterar LoadedDocumentDeclaresMLDSAExtension huruvida den deklarationen överlevde, och VerifyLoadedSignatureWithOptions tillämpar samma test innan den kommer att respektera Options.AllowMLDSA. Sätt flaggan på ett odeklarerat dokument så förblir den av — optionen kan luckra policy, aldrig det strukturella kravet
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;
Anropa det innan du sparar, inte efteråt. Deklarationen är del av det signerade byteintervallet, och en katalog patchad i efterhand är antingen en osignerad ändring av en signerad fil eller en andra revision som en validator kommer rapportera som en modifiering
Tre algoritmfamiljer, en verifieringsingång
Alla tre familjerna kommer genom VerifyLoadedSignatureWithOptions, som tar ett signaturindex, källströmmen, en THPDFCMSVerifyOptions-post och en ut-parameter för signaturdetaljerna. Posten har exakt tre fält, och var och en besvarar en fråga som tidigare krävde en ombyggnad
SignatureProvider byter ut den inbyggda plattformsleverantören mot din egen. OpenSSLLibraryPath väljer ett OpenSSL 3-bibliotek, vilket är det som tillhandahåller den rena-läges Ed25519- och Ed448-verifieringen som Windows CNG inte erbjuder överallt. AllowMLDSA anmäler sig till gitteralgoritmerna, med den tilläggskontroll som anges ovan. Den exakta algoritm-OID som identifierades kommer tillbaka i THPDFSignatureInfo.SignatureAlgorithmOID, så en revisionslogg kan registrera vad som verifierades i stället för vad som begärdes
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 och Ed448 behöver ingen tilläggsdeklaration, eftersom ISO 32000-2 redan antar dem. De behöver en leverantör som implementerar dem, vilket på de flesta Windows-distributioner betyder att peka OpenSSLLibraryPath på ett bibliotek du skeppar och kontrollerar i stället för på det som råkar finnas på maskinen
Vad lovar en signeringsleverantör faktiskt?
En leverantör lovar en sak: givet en förfrågan, returnera en status och, vid signering, byte. THPDFSignatureProviderRequest bär algoritmen och dess OID, digest-OID, PSS-saltlängd, huruvida indata är ett meddelande eller ett redan beräknat digest, själva indata, den offentliga nyckeln eller certifikatet, en nyckelidentifierare och en operationsidentifierare. Ingenting i den posten är HotPDF-specifikt — det är vokabulären en token-drivrutin eller en signeringstjänst redan talar
Tre implementeringar skeppas med biblioteket. THPDFCallbackSignatureProvider sveper in anonyma metoder, vilket är den kortaste vägen från en befintlig intern signeringrutin till en fungerande PDF-signatur. THPDFRemoteSignatureProvider sveper in en transport-callback med en återförsöksgräns, ett avbrottsregister och gränser för indata- och signaturstorlek, så en hängd HSM inte kan bli en hängd applikation. THPDFPKCS11SignatureProvider serialiserar RSA-operationer mot en anropar-ägd, redan autentiserad PKCS#11-session och privanyckel-referens — HotPDF loggar aldrig in, ser aldrig en PIN och stänger aldrig en session det inte öppnade
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;
Varför status-enumen har sex värden i stället för en boolean
THPDFSignatureProviderStatus skiljer spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError och spsCancelled, och att kollapsa dem kostar dig förmågan att agera korrekt. En signatur som är kryptografiskt fel (spsInvalid) är en säkerhetshändelse. En algoritm som leverantören inte implementerar (spsUnsupported) är ett distributionsgap. Ett transportfel (spsProviderError) är värt att försöka igen, och en användaravbruten token-prompt (spsCancelled) är inte värt att försöka igen alls
Regeln för signering är snäv: en signeringsleverantör returnerar spsValid endast med en icke-tom signatur. Verifieringsleverantörer returnerar spsValid eller spsInvalid, och de övriga fyra förblir åtskilda på båda vägarna. Om du skriver en leverantör, motstå frestelsen att mappa allt du inte känner igen på spsInvalid — det förvandlar en saknad DLL till en rapport om att kundens signatur är förfalskad
Var signaturen faktiskt landar i filen
Två funktioner kopplar leverantörer till riktiga PDF-byte. HPDFCMSBuildSignedDataWithProvider bygger fristående CMS från ett dokument-SHA-256-digest, vilket är rätt ingång när ditt arbetsflöde beräknar digest annorstädes. HPDFCMSSignPDFStreamWithProvider signerar en befintlig signaturplatshållare i en PDF-ström och bevarar den standardiserade /ByteRange-pipelinen, vilket är rätt ingång när HotPDF själv lade ut platshållaren
Att bevara den pipelinen betyder mer än det låter. /ByteRange-konventionen — två intervall som hoppar över hex-signaturfönstret — är vad varje validator kontrollerar först, och en leverantörsbaserad väg som skrev om den skulle bryta PAdES-efterlevnad oavsett hur sund kryptografin var. HotPDF håller layouten identisk med den inbyggda signeringsvägen, så ett dokument signerat genom en PKCS#11-token verifieras med samma signaturverifieringskod som ett signerat från en PFX-fil. För de profilregler som sitter ovanför algoritmvalet, se genomgången av PAdES-baslinjesignaturer i Delphi, och för de ECDSA-specifika kodningsfällor som föregår denna leverantörsmodell, noterna om ECDSA CMS-verifiering och P1363-signaturformat
En migrationsordning som inte lämnar dina dokument i sticket
Postkvantumberedskap är ett schemaproblem, inte en strömbrytare. Nästan ingen utplacerad PDF-visare validerar ML-DSA idag, så ett dokument signerat med bara det är, från läsarens perspektiv, ett dokument med en overifierbar signatur. Den ordning som överlever kontakt med verkliga arkiv är: behåll RSA eller ECDSA som signaturen en validator kommer bedöma, lägg till tilläggsdeklarationen och en andra ML-DSA-signatur där en policy kräver kvantresistent bevisning, och flytta den primära signaturen först när de konsumerande systemen har hunnit ifatt
Det HotPDF ger dig idag är förmågan att skriva och verifiera båda, från samma kod, med algoritmen ärligt registrerad i filen och i verifieringsresultatet. HotPDF är en inbyggd VCL PDF-komponent för Delphi och C++Builder utan extern PDF-runtime, så signerings- och verifieringsvägarna skeppas inuti din exekverbara fil i stället för bredvid den — se HotPDF Delphi PDF-komponentsida för hela funktionslistan och testnedladdning