Bài viết kỹ thuật

Ký PDF hậu lượng tử và EdDSA với HotPDF trong Delphi

HotPDF xác minh chữ ký CMS ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 và Ed448 trong các tài liệu PDF đã tải, đồng thời ký thông qua các provider có thể cắm rời để private key không bao giờ phải nằm trong tiến trình Delphi của bạn. Nửa thứ hai là phần mà hầu hết các đội ngũ cần trước tiên. Một token phần cứng, một dịch vụ ký từ xa và một thẻ eID quốc gia đều từ chối giao ra key, và cho đến khi pipeline ký được tách khỏi kho key thì không cái nào trong số đó dùng được cả

Sự tách biệt đó chính là điểm của THPDFSignatureProvider. HotPDF giữ lại những phần đáng lẽ phải sở hữu — phân tích CMS, dựng SignedData, bố trí /ByteRange — và ủy thác thao tác duy nhất mà nó không thể sở hữu, đó là biến một digest thành chữ ký bằng một key mà nó không được phép thấy. Mọi thứ bên dưới đều bắt nguồn từ sự phân chia đó

Vì sao một chữ ký ML-DSA hợp lệ lại thất bại khi xác minh?

Vì HotPDF từ chối ML-DSA trên một tài liệu đã tải mà không khai báo extension cho nó. ML-DSA — lược đồ chữ ký lattic được chuẩn hóa thành FIPS 204, và là lý do người ta nói "PDF hậu lượng tử" — chưa có đăng ký ISO 32000-2. Một PDF mang theo nó đang dùng một thuật toán mà chuẩn cơ sở không hề nhắc đến, và một file âm thầm dùng một thuật toán không tên là một file mà phán quyết của nó không thể được tái lập bởi bất kỳ ai khác

Nên HotPDF bắt buộc phải khai báo rõ ràng. EnsureMLDSAExtensions nâng tài liệu lên PDF 2.0 khi được phép và ghi /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> vào Catalog. Bên phía đọc, LoadedDocumentDeclaresMLDSAExtension báo cáo xem khai báo đó có tồn tại hay không, còn VerifyLoadedSignatureWithOptions áp dụng cùng phép kiểm tra trước khi nó tôn trọng Options.AllowMLDSA. Đặt cờ trên một tài liệu không khai báo thì nó vẫn tắt — tùy chọn có thể nới lỏng chính sách, không bao giờ nới lỏng yêu cầu cấu trúc

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;

Hãy gọi nó trước khi lưu, không phải sau đó. Khai báo là một phần của dải byte đã ký, và một Catalog được vá sau đó hoặc là một thay đổi không ký trên một file đã ký, hoặc là một revision thứ hai mà trình xác thực sẽ báo cáo như một sửa đổi

Ba họ thuật toán, một điểm vào xác minh

Cả ba họ đều đi qua VerifyLoadedSignatureWithOptions, hàm này nhận một chỉ số chữ ký, stream nguồn, một bản ghi THPDFCMSVerifyOptions và một tham số out cho chi tiết chữ ký. Bản ghi có đúng ba trường, mỗi trường trả lời một câu hỏi mà trước đây cần phải dựng lại mã

SignatureProvider thay provider của riêng bạn vào provider nền tảng mặc định. OpenSSLLibraryPath chọn một thư viện OpenSSL 3, thứ cung cấp xác minh Ed25519 và Ed448 chế độ thuần mà Windows CNG không có ở mọi nơi. AllowMLDSA chọn tham gia các thuật toán lattic, tuân theo phép kiểm tra extension bên trên. OID thuật toán chính xác được nhận diện quay về trong THPDFSignatureInfo.SignatureAlgorithmOID, nên một nhật ký kiểm toán có thể ghi lại cái đã được xác minh thay vì cái đã yêu cầu

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 và Ed448 không cần khai báo extension, vì ISO 32000-2 đã chấp nhận chúng. Chúng chỉ cần một provider hiện thực chúng, và trên hầu hết các triển khai Windows nghĩa là trỏ OpenSSLLibraryPath tới một thư viện bạn tự phân phối và kiểm soát thay vì vào thứ tình cờ có trên máy

Một provider ký thực sự hứa hẹn điều gì?

Một provider hứa một điều duy nhất: nhận một yêu cầu, trả về một trạng thái và, khi ký, các byte chữ ký. THPDFSignatureProviderRequest mang theo thuật toán và OID của nó, OID của digest, độ dài salt PSS, việc đầu vào là một thông điệp hay một digest đã tính, bản thân đầu vào, public key hoặc chứng chỉ, một định danh key và một định danh thao tác. Không có gì trong bản ghi đó là đặc thù HotPDF — đó là từ vựng mà một driver token hay một dịch vụ ký đã nói sẵn

Ba hiện thực đi kèm thư viện. THPDFCallbackSignatureProvider bọc các anonymous method, đó là đường ngắn nhất từ một quy trình ký nội bộ sẵn có tới một chữ ký PDF hoạt động. THPDFRemoteSignatureProvider bọc một callback vận chuyển với giới hạn retry, một cơ chế hủy và các giới hạn trên kích thước đầu vào và chữ ký, để một HSM treo không thể trở thành một ứng dụng treo. THPDFPKCS11SignatureProvider tuần tự hóa các thao tác RSA trên một phiên PKCS#11 do bên gọi sở hữu và đã xác thực cùng một handle private-key — HotPDF không bao giờ đăng nhập, không bao giờ thấy PIN, và không bao giờ đóng một phiên mà nó không mở

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;

Vì sao enum trạng thái có sáu giá trị thay vì một boolean

THPDFSignatureProviderStatus phân biệt spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderErrorspsCancelled, và gộp chúng lại sẽ tước khả năng hành động đúng. Một chữ ký sai về mặt mật mã (spsInvalid) là một sự kiện bảo mật. Một thuật toán mà provider không hiện thực (spsUnsupported) là một khoảng trống triển khai. Một thất bại vận chuyển (spsProviderError) đáng retry, còn một lời nhắc token bị người dùng hủy (spsCancelled) không đáng retry chút nào

Quy tắc cho việc ký là hẹp: một provider ký chỉ trả về spsValid kèm một chữ ký không rỗng. Provider xác minh trả về spsValid hoặc spsInvalid, còn bốn giá trị kia vẫn tách biệt trên cả hai đường. Nếu bạn viết một provider, hãy cưỡng lại cám dỗ ánh xạ mọi thứ bạn không nhận diện vào spsInvalid — điều đó biến một DLL thiếu thành một báo cáo rằng chữ ký của khách hàng bị giả mạo

Nơi chữ ký thực sự đáp xuống trong file

Hai hàm kết nối provider với byte PDF thực. HPDFCMSBuildSignedDataWithProvider dựng detached CMS từ một digest SHA-256 của tài liệu, đó là điểm vào đúng khi quy trình của bạn tính digest ở nơi khác. HPDFCMSSignPDFStreamWithProvider ký một chỗ giữ chữ ký sẵn có trong một stream PDF và bảo toàn pipeline /ByteRange chuẩn, đó là điểm vào đúng khi chính HotPDF đã bố trí chỗ giữ đó

Việc bảo toàn pipeline đó quan trọng hơn vẻ bề ngoài. Quy ước /ByteRange — hai dải bỏ qua cửa sổ chữ ký hex — là thứ mọi trình xác thực kiểm tra đầu tiên, và một đường dẫn dựa trên provider mà viết lại nó sẽ phá vỡ tính tuân thủ PAdES bất kể mật mã có vững đến đâu. HotPDF giữ bố cục đồng nhất với đường dẫn ký mặc định, nên một tài liệu ký qua một token PKCS#11 được xác minh bằng cùng mã xác minh chữ ký như một tài liệu ký từ file PFX. Về các quy tắc profile nằm trên lựa chọn thuật toán, hãy xem bài viết về chữ ký PAdES baseline trong Delphi, còn về các bẫy mã hóa riêng ECDSA trước khi mô hình provider này ra đời, hãy xem ghi chú về xác minh ECDSA CMS và định dạng chữ ký P1363

Một thứ tự di chuyển không bỏ rơi tài liệu của bạn

Sự sẵn sàng hậu lượng tử là một vấn đề lịch trình, không phải một công tắc. Gần như không có trình xem PDF triển khai nào xác thực ML-DSA ngày hôm nay, nên một tài liệu chỉ ký bằng nó, từ góc nhìn trình đọc, là một tài liệu với chữ ký không thể xác minh. Thứ tự sống sót qua các kho lưu trữ thực tế là: giữ RSA hoặc ECDSA làm chữ ký mà trình xác thực sẽ đánh giá, thêm khai báo extension và một chữ ký ML-DSA thứ hai khi chính sách đòi hỏi bằng chứng kháng lượng tử, và chỉ di chuyển chữ ký chính khi các hệ thống tiêu thụ đã theo kịp

Điều HotPDF mang lại cho bạn hôm nay là khả năng ghi và xác minh cả hai, từ cùng mã, với thuật toán được ghi trung thực trong file và trong kết quả xác minh. HotPDF là thành phần PDF VCL bản địa cho Delphi và C++Builder không cần runtime PDF bên ngoài, nên đường dẫn ký và xác minh xuất xưởng bên trong thực thi của bạn thay vì đi kèm nó — xem trang thành phần PDF Delphi HotPDF để biết danh sách tính năng đầy đủ và bản dùng thử