Teknik Makale

HotPDF ile Kuantum-Sonrası ve EdDSA PDF İmzalama

ML-DSA-65 imzası taşıyan bir PDF, onu üreten toolchain'in kontrolünden geçebilir ve dosyanın algoritmasını hangi extension'ın tanımladığını bildirmekte ısrar eden bir doğrulayıcıdan yine de desteklenmeyen bir algoritma olarak geri gelebilir. Açık anahtar 1.952 bayttır — o parametre kümesi için tam olarak FIPS 204 boyutu. İmza baytları sağlamdır ve byte aralıkları tüm dosyayı kapsar. Böyle bir doğrulayıcının aradığı şey, belge Catalog'unda o extension'ı adlandıran tek bir girdidir; çünkü ISO 32000-2, ML-DSA'yı hiç listelemez

İmzalama tarafında farklı bir duvar var. Bir donanım token'ı, bir bulut HSM ya da ulusal bir eID kartı, talep üzerine imza hesaplar ama hiçbiri özel anahtarı uygulamanıza teslim etmez; bu yüzden bir PFX dosyası yükleyip süreç içi imzalama bekleyen her imzalama kodunun yükleyecek hiçbir şeyi yoktur. İki sorun da bir kararın nerede yaşadığıyladır: algoritma bildirimi dosyada yaşar ve anahtar, Delphi kodunuzun ulaşamadığı bir yerde yaşar

Geçerli bir ML-DSA imzası neden desteklenmeyen olarak geri gelir?

HotPDF, ML-DSA-44, ML-DSA-65 ve ML-DSA-87 CMS imzalarını yalnızca yüklenen belge, Catalog'unda HotPDF geliştirici extension'ını bildirdiğinde doğrular. O bildirim olmadan doğrulayıcı, kriptografi ne kadar sağlam olursa olsun bir lattice imzası için svUnsupportedAlgorithm döndürür. Gerekçe tekrarlanabilirliktir: temel standardın adlandırmadığı bir algoritma kullanan ve hangi extension'a bel dayandığını söylemeyen bir dosya, başka bir uygulamanın tekrarlamak için temeli olmayan bir karar üretir

Üretme tarafında THotPDF.EnsureMLDSAExtensions, /Extensions << /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> >>'i Catalog'a yazar ve belge daha eski bir sürümü hedefliyorsa onu PDF 2.0'a yükseltir. Okuma tarafında LoadedDocumentDeclaresMLDSAExtension, yalnızca o sözlük, /BaseVersion'u 2.0'a eşit ve /ExtensionLevel'u en az 1 olacak şekilde mevcutken True döndürür. VerifyLoadedSignatureWithOptions ikisini birleştirir: çağıranın AllowMLDSA bayrağını alır ve herhangi bir doğrulama çalışmadan onu bildirim testiyle VE'ler; böylece bildirimsiz bir dosyada bayrağı ayarlamak hiçbir şeyi değiştirmez

HotPDF, ML-DSA PDF doğrulamasını, belge kaydedilmeden önce EnsureMLDSAExtensions'ın yazdığı açık bir Catalog extension bildiriminin arkasına kilitler ve AllowMLDSA ayarlanmış olsa bile bildirimsiz dosyalarda lattice algoritmalarını reddeder
HotPDF, ML-DSA'yı yalnızca Catalog hâlâ extension'ı bildirirken kabul eder; bu, kuantum-sonrası bir kararı tekrarlanabilir tutar ve EndDoc'tan önce EnsureMLDSAExtensions çağırmak, bildirimi imzalı byte aralığının içine, onun arkasına değil yerleştirir
var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'contract-pq.pdf';
    Pdf.StrictVersionLock := False;   // varsayılan; çağrının dosyayı PDF 2.0'a yükseltmesine izin verir
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 11);
    Pdf.CurrentPage.TextOut(50, 720, 0, 'Supply agreement 2026-114');
    Pdf.EnsureMLDSAExtensions;        // herhangi bir imza Catalog'u kapsamadan önce bildir
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

O parçadaki iki ayrıntı yük taşır. Çağrı, belge kaydedilip imzalanmadan önce gerçekleşmelidir, çünkü Catalog imzalı byte aralığının içinde durur; sonradan yamalamak ya imzalı bir dosyaya imzasız bir değişiklik ya da herhangi bir doğrulayıcının değişiklik olarak listeleyeceği yeni bir artımlı revizyon olur. İkinci ayrıntı StrictVersionLock'tur. Kilit açıkken ve belge PDF 2.0'ın altına sabitlenmişken EnsureMLDSAExtensions, hiçbir şey yazmadan döner; çünkü bir PDF 2.0 extension'ı 1.7 bir dosyada bildirilemez. Üreticiniz sürümleri arşiv nedenleriyle sabitliyorsa, bildirime bel bağlamadan önce Version = pdf20'yi kontrol edin

EdDSA ve ML-DSA için tek doğrulama çağrısı

VerifyLoadedSignatureWithOptions, bir imza indeksi, kaynak akışı, bir THPDFCMSVerifyOptions kaydı ve bir out THPDFSignatureInfo alır. Kaydı THPDFCMSVerifyOptions.Default'tan başlatın; o, AllowMLDSA'yı kapalı bırakır, bir CMS kabını 256 imzalayıcıyla sınırlar ve countersignature yuvalamasını 8'e indirger. Üç alan, daha yeni algoritmalar için önem taşır. SignatureProvider, kendi doğrulayıcınızın önce cevap vermesini sağlar. OpenSSLLibraryPath, pure mod Ed25519 ve Ed448 doğrulamasını yapan OpenSSL 3 libcrypto'sunu adlandırır. AllowMLDSA, yukarıdaki Catalog testine tabi olarak lattice algoritmalarına opt-in eder. Kayıt ayrıca imza-politiği sabitleme alanları taşır (RequireSignaturePolicy, ExpectedSignaturePolicyOID, ExpectedSignaturePolicyHash); bunlar her algoritma ailesine uygulanır

Yerleşik yol algoritmaya göre bölünür. Ed25519 ve Ed448 OpenSSL 3'ten geçer; bir yol verilmediğinde platform provider'ı, Win64'teki normal DLL arama sırasıyla libcrypto-3-x64.dll, libcrypto-3.dll ve libcrypto.dll'yi dener. ML-DSA, Windows CNG ML-DSA algoritma provider'ından geçer ve onu sunmayan bir Windows derlemesinde sonuç desteklenmezdir. OpenSSLLibraryPath'i, çalıştırılabilir dosyanın yanına dağıttığınız bir kopyaya ayarlayın. Onu boş bırakmak, kararın arama yoluna başka bir installer'in bıraktığı hangi libcrypto ise ona bağımlı olması demektir ve bu, bir denetimde açıklaması zor şeydir

Kendi SignatureProvider'ınıza önce sorulur. spsUnsupported döndürürse yerleşik platform provider aynı isteği alır; böylece yalnızca ML-DSA bilen bir provider, varsayılan EdDSA işlemeyi yeniden uygulamadan onun önüne oturabilir

var
  Pdf: THotPDF;
  PQ: THPDFCallbackSignatureProvider;
  Opts: THPDFCMSVerifyOptions;
  Info: THPDFSignatureInfo;
  Status: THPDFSignatureVerifyStatus;
  Src: TFileStream;
begin
  PQ := THPDFCallbackSignatureProvider.Create(
    function(const Req: THPDFSignatureProviderRequest): THPDFSignatureProviderStatus
    begin
      if not (Req.Algorithm in [hsaMLDSA44, hsaMLDSA65, hsaMLDSA87]) then
        Exit(spsUnsupported);            // EdDSA'yı platform provider'ına devret
      // MyFips204Verify, bir ML-DSA kütüphanesi çevresindeki kendi sarmalayıcınızdır;
      // Req.Input, DER kodlu imzalı öznitelikleri taşır (pure mod)
      Result := MyFips204Verify(Req.AlgorithmOID, Req.PublicKey,
        Req.Input, Req.Signature);
    end);
  Pdf := THotPDF.Create(nil);
  Src := TFileStream.Create('contract-pq.pdf', fmOpenRead or fmShareDenyNone);
  try
    Pdf.LoadFromFile('contract-pq.pdf');
    Opts := THPDFCMSVerifyOptions.Default;
    Opts.SignatureProvider := PQ;
    Opts.OpenSSLLibraryPath := ExtractFilePath(ParamStr(0)) + 'libcrypto-3-x64.dll';
    Opts.AllowMLDSA := True;             // hâlâ Catalog bildirimiyle VE'lenir
    Status := Pdf.VerifyLoadedSignatureWithOptions(0, Src, Opts, Info);
    if Status = svValid then
      Memo1.Lines.Add(string(Info.SignatureAlgorithm) + ' / ' +
        string(Info.SignatureAlgorithmOID));
  finally
    Src.Free;
    Pdf.Free;
    PQ.Free;                             // options kaydı ona sahip değildir
  end;
end;

Yapılandırmanızın beklediği hangi algoritma ise onu değil, Info.SignatureAlgorithmOID'u günlüğe kaydedin. ML-DSA-65 için o, 2.16.840.1.101.3.4.3.18'dir ve dosyada bulunan OID'i kaydeden bir denetim izi, daha sonra bir yapılandırma değerinin cevaplayamayacağı soruları cevaplar. Hiç özel provider'a ihtiyacınız yoksa daha kısa olan VerifyLoadedSignature(SigIndex, Source, Info) overload'u, varsayılan option'ları kurar ve AllowMLDSA'yı Catalog bildiriminden sizin için ayarlar

Dışarıdan üretilmiş ML-DSA ve EdDSA CMS'yi düşüren kodlama kuralları

Her iki algoritma ailesi de eksiksiz DER kodlu imzalı öznitelikler üzerinden pure modda doğrulanır ve imzalı öznitelikleri olmayan bir SignerInfo, desteklenmeyen olarak raporlanır. EdDSA için AlgorithmIdentifier parametreleri mevcut olmamalıdır. Elle yazılmış CMS'de yaygın bir hata, imzalı öznitelikleri önce SHA-512 ya da SHAKE256 ile hash'leyip o digest'i imzalamaktır; bu, PureEdDSA'yı sessizce, CMS'nin hiçbir şey bildirmediği bir prehash varyantına çevirir. ML-DSA için doğrulayıcı ayrıca parametre kümesi ile mesaj digest'i arasındaki eşleşmeyi kontrol eder: ML-DSA-44 SHA-256, SHA-384, SHA-512 ya da SHAKE256 kabul eder, ML-DSA-65 SHA-384 ya da daha güçlüsünü ister ve ML-DSA-87 SHA-512 ya da SHAKE256 ister. Eşleşmeyen bir çift, Info.DigestConsistencyIssue'da görünür; başka bir toolchain'den gelen bir dosya başarısız olduğunda okunacak ilk alan budur

EdDSA'nın kendi kayıt hikayesi vardır. ISO/TS 32002, Ed25519 ve Ed448'ü PDF 2.0 imzaları için kaydeden extension'dır ve EnsureISO32002Extensions, eşleşen ISO_ girdisini ExtensionLevel 32002 ile Catalog'a ekler. Doğrulama, EdDSA'yı o girdi olmadan da kabul eder; ama sizin ürettiğiniz bir dosya yine de onu taşımalıdır — ML-DSA bildirimiyle aynı tekrarlanabilirlik gerekçesiyle

Bir imzalama provider'ından ne istenir?

Bir imzalama provider'ı tek bir THPDFSignatureProviderRequest alır ve bir THPDFSignatureProviderStatus artı, başarı hâlinde imza baytlarını döndürür. İstek, algoritmayı ve OID'ini, digest algoritması OID'ini, PSS salt uzunluğunu, Input'un bir mesaj mı (sikMessage) yoksa önceden hesaplanmış bir digest mi (sikDigest) olduğunu, girdi baytlarını, imzalayıcı sertifikasını, bir anahtar tanımlayıcısını ve çağrıya özgü bir işlem tanımlayıcısını taşır. Bir token sürücüsünün ya da imzalama servisinin zaten konuştuğu kelime dağarcığı budur ve hiçbiri anahtarın sürecinize geçmesini gerektirmez

Her provider ayrıca spcSign, spcCancel, spcHardwareKey ve spcRemoteKey gibi bir yetenek kümesi bildirir. Kütüphanede birkaç uygulama gelir:

  • THPDFCallbackSignatureProvider, verify, sign ve cancel için anonim metotları sarar; var olan bir şirket içi imzalama rutininden bir PDF imzasına en kısa yol budur
  • THPDFRemoteSignatureProvider, bir transport fonksiyonunu bir yeniden deneme sınırıyla, bir iptal kaydıyla ve girdi ile imza boyutu sınırlarıyla sarar; bozuk davranan bir uzak servis, CMS kodlayıcıya aşırı büyük bir yanıt itemez
  • THPDFPKCS11SignatureProvider, kodunuzun çoktan açıp oturum açtığı bir session'ı ve özel anahtar handle'ını ödünç alır. HotPDF PIN'i hiç görmez ve token session'ını yönetmez; C_Sign çağrılarını serileştirir, RSA PKCS#1 v1.5 için DigestInfo'yu öne ekler, gerçek salt uzunluğunu mekanizma parametreleriyle RSA-PSS'e geçirir ve ham bir ECDSA r||s sonucunu CMS'nin beklediği DER biçimine çevirir
  • THPDFWindowsKeyStorageSignatureProvider, Windows CNG anahtar deposu üzerinden imzalar — akıllı kartları ve TPM destekli anahtarları kapsar — ve donanım destekli bir anahtar isteyebilir

PKCS#11 provider'ı digest girdisi alır; bu yüzden RSA PKCS#1 v1.5, RSA-PSS ve ECDSA'yı işler. Ed25519 ve Ed448, eksiksiz imzalı-öznitelik mesajını imzalar ve onlar, kodunuzun Req.Input'u token'a ya da servise bir mesaj olarak geçirdiği callback ya da uzak provider'dan geçer

HotPDF takılabilir imza provider'ları tek bir sade istek kaydını alır ve altı değerli bir durum döndürür; callback, uzak ve PKCS#11 uygulamaları için gösterilirken özel anahtar hiçbir zaman Delphi sürecine girmez
Callback, uzak ve PKCS#11 provider'ları aynı istek kaydını konuşur ve aynı altı değerli durumla cevap verir; bu, donanım token'larının, uzak HSM servislerinin ve ulusal eID kartlarının anahtarlarını dışa aktarmak yerine tutmasını sağlar
var
  Provider: THPDFRemoteSignatureProvider;
  Opts: THPDFCMSSignOptions;
  CertDER: TBytes;
  InStream, OutStream: TFileStream;
begin
  CertDER := TFile.ReadAllBytes('signer.cer');   // System.IOUtils; uzak anahtarın DER sertifikası
  Provider := THPDFRemoteSignatureProvider.Create(
    function(const Req: THPDFSignatureProviderRequest; Attempt: Integer;
      out Signature: TBytes): THPDFSignatureProviderStatus
    begin
      // PostToSigningService sizin HTTP istemcinizdir: timeout'ları ve 5xx'i
      // spsProviderError'a, reddedilen onayı spsCancelled'a, bilinmeyen anahtarı spsUnsupported'a eşle
      Result := PostToSigningService(Req.KeyIdentifier, Req.AlgorithmOID,
        Req.Input, Signature);
    end,
    3,          // RetryLimit: toplamda en fazla dört deneme
    1048576,    // MaxInputBytes
    65536);     // MaxSignatureBytes
  Opts := HPDFCMSDefaultOptions(palBaseline_B_B);
  Opts.SignatureScheme := cmsRSAPSS;
  Opts.DigestAlgorithm := cmsdaSHA256;
  Opts.PSSSaltLength := 32;
  // contract-unsigned.pdf, AddSignedSignatureField'dan bir yer tutucuyla kaydedildi
  InStream := TFileStream.Create('contract-unsigned.pdf', fmOpenRead or fmShareDenyWrite);
  OutStream := TFileStream.Create('contract-signed.pdf', fmCreate);
  try
    HPDFCMSSignPDFStreamWithProvider(InStream, OutStream, CertDER,
      Provider, 'contracts-2026', Opts);
  finally
    OutStream.Free;
    InStream.Free;
    Provider.Free;
  end;
end;

Altı durum ve her birinin neyi tetiklemesi gerektiği

THPDFSignatureProviderStatus'un altı değeri vardır çünkü doğru tepki her biri için farklıdır. spsInvalid kriptografik bir başarısızlıktır ve bir güvenlik günlüğüne aittir. spsUnsupported, provider'ın algoritmayı uygulamadığı anlamına gelir; bu bir dağıtım sorunudur. spsMalformed, isteğin ya da yanıtın yanlış biçimli olduğunu anlamına gelir. spsProviderError, yeniden denemede başarılı olabilecek bir transport ya da cihaz arızasıdır. spsCancelled, bir kişinin token istemini reddettiği anlamına gelir; onu yeniden denemek sadece rahatsız eder

Uzak provider o politikayı içine inşa eder. Yalnızca spsProviderError üzerinde yeniden dener, her denemeden önce iptal kaydını kontrol eder, transport'u çağırmadan önce boş ya da aşırı büyük girdiyi spsMalformed olarak reddeder ve boş ya da aşırı büyük imzalı bir "geçerli" yanıtı spsMalformed'a düşürür. Doğrulama tarafında bir provider'ın cevabı, verify durumuna eşlenir: spsInvalid svSignatureInvalid olur, spsMalformed svMalformed olur, spsProviderError ile spsCancelled ikisi de svCryptoProviderError olur ve spsUnsupported, platform provider da isteği reddettikten sonra svUnsupportedAlgorithm olur

Bir provider yazıyorsanız, her beklenmedik durumu spsInvalid'a eşleme isteğine direnin. Bu, eksik bir DLL'i ya da süresi dolmuş bir servis token'ını, müşterinin imzasının sahte olduğuna dair bir rapora çevirir ve bunu müşteriye açıklamak birinin işi olur. spsInvalid'i yalnızca matematik gerçekten başarısız olduğunda döndürün. İmzalama için spsValid'i yalnızca boş olmayan bir imzayla birlikte döndürün; iki giriş noktası da, bir provider baytsız başarı döndürdüğünde EHPDFCMSError yükseltir

Provider'ın imzası dosyada nereye düşer?

İki fonksiyon bir provider'ı gerçek PDF baytlarına bağlar. HPDFCMSBuildSignedDataWithProvider, başka bir yerde hesapladığınız bir belge digest'inden detached CMS kurar; digest uzunluğu Options.DigestAlgorithm ile eşleşmelidir, böylece SHA-384 bir yapılandırma 48 bayt bekler ve 32 baytlık bir SHA-256 digest'ini bir istisnayla reddeder. HPDFCMSSignPDFStreamWithProvider, AddSignedSignatureField'ın yerleştirdiği yer tutucuyu imzalar, her iki /ByteRange penceresini hash'ler, imzalı öznitelikler üzerinden imzayı provider'a ister ve hex sonucunu /Contents'e yamar. CMS yer tutucuyu aşarsa çağrı bir istisna yükseltir ve çıktı akışında hiçbir şeyi kısaltmak yerine imzasız yer tutucuyu geri koyar

Düzen, PFX tabanlı imzalama yolunun yazdığı düzenin aynısıdır: hex imza penceresini atlayan iki aralık. Doğrulayıcı her dosyayı o biçime tutar; aralıklar arasındaki boşluğun tam olarak bu imzanın /Contents dizgesini ve başka hiçbir şey içerdiğini kontrol eder ve aksi hâlde svInvalidByteRange raporlar. Bu yüzden bir PKCS#11 token'ı üzerinden imzalanan bir belge, bir PFX dosyasından imzalananla aynı imza doğrulama koduyla doğrulanır. Algoritma seçiminin üstünde duran profil kuralları Delphi'de PAdES baseline imzalarında kapsanır ve PKCS#11 dönüşümünün hallettiği ECDSA kodlama tuzakları ECDSA CMS doğrulaması ve P1363 imza biçimlerindedir

İki HotPDF provider imzalama giriş noktası — dışarıdan hesaplanan digest için HPDFCMSBuildSignedDataWithProvider ve yerleştirilmiş bir yer tutucu için HPDFCMSSignPDFStreamWithProvider — doğrulayıcıların önce kontrol ettiği /ByteRange aralıklarını koruyan tek bir CMS düzeninde buluşur
Her iki giriş noktası da hex penceresinin çevresinde iki kapsanan aralıklı aynı imzalı biçimi yazar; böylece provider'la imzalanan bir belge, bir PFX dosyasından imzalananla aynı byte düzenine sahiptir

Kuantum-sonrası imzaları okuyucuları yolda bırakmadan yaymak

ML-DSA uygulamayan bir PDF okuyucusu böyle bir imzayı doğrulanamaz rapor eder ve belgeye bakan kişi yeşil bir onay yerine bir uyarı görür. Bu, göçü bir zamanlama problemine dönüştürür. RSA ya da ECDSA'yı, bugünün doğrulayıcılarının yargılayacağı imza olarak tutun. Bir politika kuantum-dirençli kanıt istediğinde extension'ı EnsureMLDSAExtensions ile bildirin ve klasik olanın yanına, ML-DSA yetenekli bir imzalayıcının ürettiği ikinci bir imza ekleyin. O imza, kendi FIPS 204 imzalayıcınızdan gelir; çünkü HotPDF CMS kurucusu ML-DSA imzaları yaratmaz — kütüphanenin payı onu doğrulamaktır. Birincil imzayı yalnızca, belgelerinizi tüketen sistemler onu doğrulayabildiğinde taşıyın

O planda HotPDF, Delphi'den kontrol ettiğiniz iki ucu da kapsar. CMS kurucuları, yukarıdaki provider'ların herhangi biri üzerinden RSA PKCS#1 v1.5, RSA-PSS, ECDSA, Ed25519 ve Ed448 ile imzalar; doğrulayıcısı ML-DSA'yı, EdDSA'yı ve klasik algoritmaları tek bir çağrıdan kontrol eder, gerçekte bulunan OID de sonuca kaydedilmiş olarak. HotPDF, harici bir PDF çalışma zamanına ihtiyaç duymayan, Delphi ve C++Builder için native bir VCL PDF bileşenidir; imzalama ve doğrulama kodu çalıştırılabilir dosyanıza derlenir. Sürümler, imza özellik kümesi ve deneme indirmesi HotPDF Delphi PDF bileşeni sayfasındadır