Articol tehnic

Semnarea PDF post-cuantică și EdDSA cu HotPDF în Delphi

HotPDF verifică semnături CMS ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 și Ed448 în documentele PDF încărcate și semnează prin furnizori conectabili, astfel încât cheia privată nu trebuie niciodată să locuiască în interiorul procesului Delphi. A doua jumătate este partea de care majoritatea echipelor au nevoie mai întâi. Un token hardware, un serviciu de semnare la distanță și o cartelă eID națională refuză toate să predea o cheie, iar până când fluxul de semnare este separat de magazia de chei, nu poate fi folosit niciunul

Separarea este scopul lui THPDFSignatureProvider. HotPDF păstrează părțile pe care ar trebui să le dețină — analiza CMS, construirea SignedData, așezarea /ByteRange — și deleagă singura operație pe care nu o poate deține, anume transformarea unui rezumat într-o semnătură cu o cheie pe care nu are voie să o vadă. Tot ce urmează decurge din acea împărțire

De ce eșuează la verificare o semnătură ML-DSA validă?

Pentru că HotPDF refuză ML-DSA pe un document încărcat care nu declară extensia pentru ea. ML-DSA — schema de semnătură pe structuri de zăbrele standardizată ca FIPS 204, și motivul pentru care se spune „PDF post-cuantice” — nu are încă o înregistrare ISO 32000-2. Un PDF care poartă una folosește un algoritm pe care standardul de bază nu îl numește, iar un fișier care folosește tăcut un algoritm nenumat este un fișier al cărui verdict nu poate fi reprodus de nimeni altcineva

Așadar HotPDF face revendicarea explicită. EnsureMLDSAExtensions ridică documentul la PDF 2.0 unde este permis și scrie /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> în Catalog. Pe partea de citire, LoadedDocumentDeclaresMLDSAExtension raportează dacă acea declarație a supraviețuit, iar VerifyLoadedSignatureWithOptions aplică același test înainte de a onora Options.AllowMLDSA. Setați fanionul pe un document nedeclarat și rămâne inactiv — opțiunea poate slăbi politica, niciodată cerința structurală

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;

Apelați-o înainte de salvare, nu după. Declarația face parte din intervalul de octeți semnat, iar un Catalog peticit după aceea este fie o schimbare nesemnată a unui fișier semnat, fie o a doua revizie pe care un validator o va raporta ca modificare

Trei familii de algoritmi, un singur punct de intrare pentru verificare

Toate cele trei familii sosesc prin VerifyLoadedSignatureWithOptions, care ia un index de semnătură, fluxul sursă, o înregistrare THPDFCMSVerifyOptions și un parametru de ieșire pentru detaliile semnăturii. Înregistrarea are exact trei câmpuri, iar fiecare răspunde la o întrebare care cerea înainte o reconstrucție

SignatureProvider substituie propriul furnizor în locul celui implicit al platformei. OpenSSLLibraryPath selectează o bibliotecă OpenSSL 3, cea care livrează verificarea Ed25519 și Ed448 în mod pur pe care Windows CNG nu o oferă peste tot. AllowMLDSA optează pentru algoritmii pe structuri de zăbrele, subiect al verificării extensiei de mai sus. OID-ul exact al algoritmului recunoscut revine în THPDFSignatureInfo.SignatureAlgorithmOID, astfel încât un jurnal de audit poate înregistra ce s-a verificat, nu ce s-a cerut

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 și Ed448 nu au nevoie de declarație de extensie, deoarece ISO 32000-2 le admite deja. Au însă nevoie de un furnizor care să le implementeze, ceea ce pe majoritatea implementațiilor Windows înseamnă să pointați OpenSSLLibraryPath către o bibliotecă pe care o livrați și o controlați, nu către orice se întâmplă să fie pe mașină

Ce promite de fapt un furnizor de semnare?

Un furnizor promite un singur lucru: dată o cerere, returnează o stare și, la semnare, octeți. THPDFSignatureProviderRequest poartă algoritmul și OID-ul său, OID-ul rezumatului, lungimea saltului PSS, dacă intrarea este un mesaj sau un rezumat deja calculat, intrarea în sine, cheia publică sau certificatul, un identificator de cheie și un identificator de operație. Nimic din acea înregistrare nu este specific HotPDF — este vocabularul pe care un driver de token sau un serviciu de semnare îl vorbește deja

Trei implementări sunt livrate cu biblioteca. THPDFCallbackSignatureProvider împachetează metode anonime, cea mai scurtă cale de la o rutină de semnare internă existentă la o semnătură PDF funcțională. THPDFRemoteSignatureProvider împachetează un apel de transport cu o limită de reîncercare, o registru de anulare și limite pe dimensiunea intrării și a semnăturii, astfel încât un HSM blocat nu poate deveni o aplicație blocată. THPDFPKCS11SignatureProvider serializează operații RSA împotriva o sesiune PKCS#11 deținută și deja autentificată de apelant, cu un identificator de cheie privată — HotPDF nu se conectează niciodată, nu vede niciun PIN și nu închide niciodată o sesiune pe care nu a deschis-o

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;

De ce are enumerarea de stare șase valori în loc de un boolean

THPDFSignatureProviderStatus distinge spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError și spsCancelled, iar colapsarea lor vă costă capacitatea de a reacționa corect. O semnătură care este criptografic greșită (spsInvalid) este un eveniment de securitate. Un algoritm pe care furnizorul nu îl implementează (spsUnsupported) este o lacună de implementare. Un eșec de transport (spsProviderError) merită reîncercat, iar o promptare de token anulată de utilizator (spsCancelled) nu merită reîncercată deloc

Regula pentru semnare este îngustă: un furnizor de semnare returnează spsValid doar cu o semnătură nevidă. Furnizorii de verificare returnează spsValid sau spsInvalid, iar celelalte patru rămân distincte pe ambele căi. Dacă scrieți un furnizor, rezistați tentației de a mapa tot ce nu recunoașteți pe spsInvalid — asta transformă un DLL lipsă într-un raport că semnătura clientului este falsificată

Unde aterizează semnătura în fișier

Două funcții conectează furnizorii la octeți PDF reali. HPDFCMSBuildSignedDataWithProvider construiește CMS detașat dintr-un rezumat SHA-256 al documentului, punctul de intrare corect când fluxul dumneavoastră de lucru calculează rezumatul în altă parte. HPDFCMSSignPDFStreamWithProvider semnează un substituent de semnătură existent într-un flux PDF și păstrează conducta standard /ByteRange, punctul de intrare corect când HotPDF a așezat singur substituentul

Păstrarea acelei conducte contează mai mult decât sună. Convenția /ByteRange — două intervale care sar peste fereastra hex a semnăturii — este ce verifică mai întâi orice validator, iar o cale bazată pe furnizor care ar rescrie-o ar sparge conformanța PAdES indiferent cât de solidă era criptografia. HotPDF păstrează așezarea identică cu calea de semnare încorporată, astfel încât un document semnat printr-un token PKCS#11 se verifică cu același cod de verificare a semnăturilor ca unul semnat dintr-un fișier PFX. Pentru regulile de profil care stau deasupra alegerii algoritmului, vedeți prezentarea detaliată despre semnăturile PAdES baseline în Delphi, iar pentru capcanele de codare specifice ECDSA care precedau acest model de furnizor, notele despre verificarea CMS ECDSA și formatele de semnătură P1363

O ordine de migrare care nu vă lasă documentele în balon

Pregătirea post-cuantică este o problemă de planificare, nu un comutator. Aproape niciun vizualizator PDF implementat nu validează ML-DSA astăzi, astfel încât un document semnat doar cu ea este, din punctul de vedere al cititorului, un document cu o semnătură neverificabilă. Ordinea care supraviețuiește contactului cu arhive reale este: păstrați RSA sau ECDSA ca semnătura pe care o va judeca un validator, adăugați declarația de extensie și o a doua semnătură ML-DSA acolo unde o politică cere dovezi rezistente la cuantic, și mutați semnătura primară doar când sistemele consumatoare au ajuns din urmă

Ceea ce vă oferă HotPDF astăzi este capacitatea de a scrie și verifica ambele, din același cod, cu algoritmul înregistrat onest în fișier și în rezultatul verificării. HotPDF este o componentă VCL PDF nativă pentru Delphi și C++Builder fără runtime PDF extern, astfel încât căile de semnare și verificare sunt livrate în interiorul executabilului dumneavoastră, nu alături de el — vedeți pagina componentei HotPDF Delphi PDF pentru lista completă de funcții și descărcarea de probă