บทความเทคนิค

การลงลายเซ็น PDF แบบหลังควอนตัมและ EdDSA ด้วย HotPDF ใน Delphi

HotPDF ตรวจสอบลายเซ็น CMS ของ ML-DSA-44, ML-DSA-65, ML-DSA-87, Ed25519 และ Ed448 ในเอกสาร PDF ที่โหลด และลงลายเซ็นผ่าน provider ที่เสียบเปลี่ยนได้ เพื่อให้ private key ไม่ต้องอาศัยอยู่ในโพรเซส Delphi ของคุณ ส่วนที่สองนี้คือส่วนที่ทีมส่วนใหญ่ต้องการก่อน ฮาร์ดแวร์โทเคน บริการลงลายเซ็นระยะไกล และบัตร eID ของรัฐ ล้วนปฏิเสธที่จะส่งมอบคีย์ และจนกว่าไปป์ไลน์การลงลายเซ็นจะถูกแยกออกจากคีย์สโตร์ สิ่งเหล่านั้นจะใช้งานไม่ได้ทั้งหมด

การแยกนี้คือประเด็นของ THPDFSignatureProvider HotPDF รักษาส่วนที่มันควรเป็นเจ้าของ — parse CMS, สร้าง SignedData, จัดวาง /ByteRange — แล้วมอบหมายการดำเนินการเดียวที่มันเป็นเจ้าของไม่ได้ คือการเปลี่ยน digest ให้เป็นลายเซ็นด้วยคีย์ที่มันไม่ได้รับอนุญาตให้เห็น ทุกสิ่งข้างล่างนี้เป็นผลมาจากการแบ่งดังกล่าว

ทำไมลายเซ็น ML-DSA ที่ถูกต้องจึงตกการตรวจสอบ

เพราะ HotPDF ปฏิเสธ ML-DSA บนเอกสารที่โหลดมาซึ่งไม่ประกาศ extension สำหรับมัน ML-DSA — รูปแบบลายเซ็นแบบแลตทิซที่กำหนดเป็นมาตรฐาน FIPS 204 และเป็นเหตุผลที่คนพูดถึง "PDF หลังควอนตัม" — ยังไม่มีการจดทะเบียนใน ISO 32000-2 PDF ที่พกมันอยู่ใช้อัลกอริทึมที่มาตรฐานฐานไม่ได้ตั้งชื่อ และไฟล์ที่ใช้อัลกอริทึมที่ไม่มีชื่ออย่างเงียบ ๆ คือไฟล์ที่คำตัดสินของมันทำซ้ำไม่ได้โดยใครก็ตาม

ดังนั้น HotPDF จึงทำให้การอ้างนั้นชัดเจน EnsureMLDSAExtensions ยกระดับเอกสารขึ้นสู่ PDF 2.0 เมื่อได้รับอนุญาต และเขียน /Extensions /HotPDF << /BaseVersion /2.0 /ExtensionLevel 1 >> เข้าไปใน Catalog ฝั่งการอ่าน LoadedDocumentDeclaresMLDSAExtension รายงานว่าการประกาศนั้นรอดมาได้หรือไม่ และ VerifyLoadedSignatureWithOptions ทดสอบเดียวกันก่อนที่มันจะเคารพ Options.AllowMLDSA ตั้งค่าสถานะบนเอกสารที่ไม่ได้ประกาศแล้วมันจะยังคงปิดอยู่ — ตัวเลือกสามารถผ่อนนโยบายได้ ไม่ใช่ข้อกำหนดเชิงโครงสร้าง

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;

เรียกก่อนบันทึก ไม่ใช่หลังบันทึก การประกาศเป็นส่วนหนึ่งของ byte range ที่ลงลายเซ็น และ Catalog ที่แก้ภายหลังเป็นไปได้ทั้งการเปลี่ยนแปลงที่ไม่ได้ลงลายเซ็นในไฟล์ที่ลงลายเซ็นแล้ว หรือ revision ที่สองที่ตัวตรวจสอบจะรายงานว่าเป็นการแก้ไข

สามตระกูลอัลกอริทึม หนึ่งจุดเข้าการตรวจสอบ

ทั้งสามตระกูลมาผ่าน VerifyLoadedSignatureWithOptions ซึ่งรับ signature index, source stream, record THPDFCMSVerifyOptions และ out parameter สำหรับรายละเอียดลายเซ็น record มีเพียงสามฟิลด์ แต่ละฟิลด์ตอบคำถามที่ครั้งหนึ่งต้อง rebuild

SignatureProvider แทนที่ provider ของคุณเองสำหรับ provider แบบแพลตฟอร์มที่มีมาให้ OpenSSLLibraryPath เลือกไลบรารี OpenSSL 3 ซึ่งเป็นสิ่งที่จัดหาการตรวจสอบ Ed25519 และ Ed448 แบบ pure-mode ที่ Windows CNG ไม่ได้เสนอในทุกที่ AllowMLDSA เลือกเข้าใช้อัลกอริทึมแลตทิซ ภายใต้การตรวจสอบ extension ข้างต้น OID ของอัลกอริทึมที่รับรู้ได้รับการส่งกลับใน THPDFSignatureInfo.SignatureAlgorithmOID ทำให้บันทึกการตรวจสอบสามารถบันทึกสิ่งที่ถูกตรวจสอบแทนสิ่งที่ถูกร้องขอ

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 และ Ed448 ไม่ต้องประกาศ extension เพราะ ISO 32000-2 รับรองทั้งคู่อยู่แล้ว แต่ต้องการ provider ที่ทำงานกับมัน ซึ่งบนการปรับใช้ Windows ส่วนใหญ่หมายถึงการชี้ OpenSSLLibraryPath ไปยังไลบรารีที่คุณจัดส่งและควบคุมเอง ไม่ใช่สิ่งที่บังเอิญอยู่บนเครื่อง

provider การลงลายเซ็นสัญญาว่าอะไรจริง ๆ

provider สัญญาสิ่งเดียว คือเมื่อได้รับคำขอ ให้คืนสถานะ และเมื่อลงลายเซ็นให้คืนไบต์ THPDFSignatureProviderRequest พกอัลกอริทึมและ OID ของมัน, digest OID, ความยาวเกลือ PSS, ว่าอินพุตเป็นข้อความหรือ digest ที่คำนวณแล้ว, อินพุตเอง, public key หรือใบรับรอง, key identifier และ operation identifier ไม่มีสิ่งใดใน record นั้นเป็นของเฉพาะ HotPDF — มันคือคำศัพท์ที่โทเคนไดรเวอร์หรือบริการลงลายเซ็นใช้อยู่แล้ว

สามการทำงานส่งมาพร้อมไลบรารี THPDFCallbackSignatureProvider หุ้ม anonymous method ซึ่งเป็นเส้นทางที่สั้นที่สุดจากรูทีนการลงลายเซ็นภายในที่มีอยู่สู่ลายเซ็น PDF ที่ใช้งานได้ THPDFRemoteSignatureProvider หุ้ม transport callback พร้อมขีดจำกัดการลองใหม่, รีจิสตรีการยกเลิก และขอบเขตของขนาดอินพุตและลายเซ็น เพื่อให้ HSM ที่ค้างไม่สามารถกลายเป็นแอปพลิเคชันที่ค้างได้ THPDFPKCS11SignatureProvider ทำให้การดำเนินการ RSA เป็นอนุกรมเทียบกับเซสชัน PKCS#11 ที่ผู้เรียกเป็นเจ้าของและผ่านการรับรองความถูกต้องแล้ว พร้อม private-key handle — HotPDF ไม่เคยล็อกอิน ไม่เคยเห็น PIN และไม่เคยปิดเซสชันที่มันไม่ได้เปิด

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;

ทำไม enum สถานะจึงมีหกค่าแทนที่จะเป็น boolean

THPDFSignatureProviderStatus แยกแยะ spsValid, spsInvalid, spsUnsupported, spsMalformed, spsProviderError และ spsCancelled และการยุบมันรวมกันทำให้คุณสูญเสียความสามารถในการตอบสนองอย่างถูกต้อง ลายเซ็นที่ผิดพลาดทางการเข้ารหัส (spsInvalid) คือเหตุการณ์ความปลอดภัย อัลกอริทึมที่ provider ไม่ได้ทำงานด้วย (spsUnsupported) คือช่องว่างในการปรับใช้ ความล้มเหลวของการส่งข้อมูล (spsProviderError) คุ้มค่าที่จะลองใหม่ และพร้อมต์โทเคนที่ผู้ใช้ยกเลิก (spsCancelled) ไม่คุ้มที่จะลองใหม่เลย

กฎสำหรับการลงลายเซ็นคือแคบ provider การลงลายเซ็นคืน spsValid เฉพาะเมื่อมีลายเซ็นที่ไม่ว่าง provider การตรวจสอบคืน spsValid หรือ spsInvalid และอีกสี่ค่ายังคงแยกแยะบนทั้งสองเส้นทาง หากคุณเขียน provider ให้ต้านการล่อใจที่จะแมปทุกสิ่งที่คุณไม่รู้จักไปเป็น spsInvalid — นั่นเปลี่ยน DLL ที่ขาดหายไปให้กลายเป็นรายงานว่าลายเซ็นของลูกค้าถูกปลอมแปลง

จุดที่ลายเซ็นลงจอดจริงในไฟล์

สองฟังก์ชันเชื่อมต่อ provider ไปยังไบต์ PDF จริง HPDFCMSBuildSignedDataWithProvider สร้าง detached CMS จาก digest SHA-256 ของเอกสาร ซึ่งเป็นจุดเข้าที่ถูกต้องเมื่อเวิร์กโฟลว์ของคุณคำนวณ digest ที่อื่น HPDFCMSSignPDFStreamWithProvider ลงลายเซ็น placeholder ลายเซ็นที่มีอยู่ใน PDF stream และรักษาไปป์ไลน์ /ByteRange มาตรฐาน ซึ่งเป็นจุดเข้าที่ถูกต้องเมื่อ HotPDF จัดวาง placeholder เอง

การรักษาไปป์ไลน์นั้นสำคัญกว่าที่เสียงมันฟังดู รูปแบบ /ByteRange — สองช่วงที่ข้ามหน้าต่างลายเซ็นแบบ hex — คือสิ่งที่ตัวตรวจสอบทุกตัวตรวจก่อนเป็นอันดับแรก และเส้นทางที่ใช้ provider ที่เขียนมันใหม่จะทำลายการปฏิบัติตามมาตรฐาน PAdES ไม่ว่าการเข้ารหัสจะแข็งแกร่งเพียงใด HotPDF รักษาเลย์เอาต์ให้เหมือนกับเส้นทางการลงลายเซ็นแบบภายใน ดังนั้นเอกสารที่ลงลายเซ็นผ่านโทเคน PKCS#11 จึงตรวจสอบด้วยโค้ดการตรวจสอบลายเซ็นชุดเดียวกับที่ลงลายเซ็นจากไฟล์ PFX สำหรับกฎโปรไฟล์ที่อยู่เหนือการเลือกอัลกอริทึม ดูคู่มือ ลายเซ็น PAdES baseline ใน Delphi และสำหรับกับดักการเข้ารหัสเฉพาะ ECDSA ที่มีอยู่ก่อนโมเดล provider นี้ บันทึกของ การตรวจสอบ ECDSA CMS และรูปแบบลายเซ็น P1363

ลำดับการย้ายที่ไม่ทิ้งเอกสารของคุณไว้

ความพร้อมหลังควอนตัมเป็นปัญหาตารางเวลา ไม่ใช่สวิตช์ โปรแกรมดู PDF ที่ปรับใช้แล้วแทบไม่มีโปรแกรมใดตรวจสอบ ML-DSA วันนี้ ดังนั้นเอกสารที่ลงลายเซ็นด้วยมันเพียงอย่างเดียว ในมุมมองของผู้อ่าน คือเอกสารที่มีลายเซ็นที่ตรวจสอบไม่ได้ ลำดับที่รอดจากการกระทบกับคลังเก็บจริงคือ รักษา RSA หรือ ECDSA เป็นลายเซ็นที่ตัวตรวจสอบจะตัดสิน เพิ่มการประกาศ extension และลายเซ็น ML-DSA ที่สองเมื่อนโยบายเรียกร้องหลักฐานที่ทนทานต่อควอนตัม และย้ายลายเซ็นหลักเฉพาะเมื่อระบบที่บริโภคตามทัน

สิ่งที่ HotPDF มอบให้คุณวันนี้คือความสามารถในการเขียนและตรวจสอบทั้งสองแบบ จากโค้ดชุดเดียวกัน โดยมีอัลกอริทึมถูกบันทึกอย่างซื่อสัตย์ทั้งในไฟล์และในผลการตรวจสอบ HotPDF เป็นคอมโพเนนต์ PDF แบบ VCL พื้นเมืองสำหรับ Delphi และ C++Builder โดยไม่มีรันไทม์ PDF ภายนอก เส้นทางการลงลายเซ็นและการตรวจสอบจึงจัดส่งภายใน executable ของคุณแทนที่จะอยู่ข้างมัน — ดู หน้าคอมโพเนนต์ HotPDF Delphi PDF สำหรับรายการคุณสมบัติฉบับสมบูรณ์และดาวน์โหลดรุ่นทดลอง