Bỏ qua để đến nội dung
getnextpdf.com

Ký ở quy mô lớn, không thỏa hiệp

Spec: ISO 32000-2, §12.8Spec: ETSI EN 319 142-1Spec: RFC 5652, §5.1

Ký một tài liệu là một phép toán mật mã. Ký một trăm nghìn tài liệu trong hạn chót là cùng phép toán đó, lặp lại, nơi sự cố nguy hiểm không còn là “nó chậm” mà là “một trong số đó đã ra ngoài chưa ký và không ai nhận ra.” Trang này nói về việc làm điều thứ hai mà không từ bỏ điều thứ nhất: ký hàng loạt và đồng thời nơi mọi chữ ký vẫn đúng, lần chạy từ chối phát ra một tệp mà nó không thể ký, và một công việc lớn tiếp tục thay vì bắt đầu lại từ đầu.

Một chữ ký là một sự thật riêng cho từng tài liệu. Digest của nó được tính trên một dải byte đã khai báo loại trừ chính giá trị chữ ký (Spec: ISO 32000-2, §12.8), nên không có cách thẳng thắn nào để ký một nghìn tài liệu “theo lô” trong một động tác — mỗi tài liệu mang theo CMS SignedData của riêng nó trên các byte của riêng nó (Spec: RFC 5652, §5.1). Quy mô vì thế nhân lên các cơ hội cho đúng một thứ lặng lẽ trục trặc: một key handle thoáng thất bại, một thẩm quyền cấp dấu thời gian quá hạn, một worker chết khi đang giữ một tệp mới ghi một nửa.

Kết cục đắt giá không phải là một cú sập. Một cú sập thì ồn ào và bạn thử lại. Kết cục đắt giá là một kết cục lặng lẽ — một PDF chưa ký trông như đã hoàn tất nằm trong một kho lưu trữ, được phát hiện nhiều tháng sau bởi trình kiểm định của một kiểm toán viên. Ở quy mô lớn, “ký được phần lớn” không phân biệt được với “đã ký” cho tới đúng lúc cái quan trọng được kiểm tra. Toàn bộ điểm mấu chốt của ký ở quy mô lớn là khiến kết cục đó bất khả thi về mặt cấu trúc, không phải hiếm về mặt thống kê.

  • Mỗi tài liệu được ký riêng lẻ, trên dải byte của riêng nó. Lô là một từ về lập lịch, không phải về mật mã. Không có chữ ký dùng chung.
  • Mức là một hợp đồng, không phải một gợi ý. Bạn nêu tên một mức baseline PAdES và engine tạo ra đúng mức đó cho mọi tài liệu, hoặc nó làm thất bại tài liệu đó một cách to tiếng (Spec: ETSI EN 319 142-1).
  • Pipeline là fail-closed. Một tài liệu không thể được ký đúng cách thì không đi qua dưới dạng byte thuần. Nó bị giữ lại, không được chuyển tiếp.
  • Tính đồng thời là theo từng tài liệu, và an toàn theo cách dựng. Các đơn vị ký không chia sẻ trạng thái khả biến, nên hai worker không thể làm hỏng đầu ra của nhau.
  • Các lần chạy lớn là bền vững. Đầu ra đã commit không được phát lại khi tiếp tục; một lần chạy bị sập tiếp tục từ checkpoint cuối cùng thay vì ký lại mọi thứ.

Thiết kế dựa trên một sự tách biệt: tạo ra chữ ký là một bước nhỏ, tất định, riêng cho từng tài liệu; chạy hàng nghìn bước đó một cách an toàn là một bước điều phối. Giữ chúng tách biệt là điều cho phép mỗi bên giữ được sự đơn giản.

Bước ký là bước không bao giờ được thỏa hiệp. Bạn yêu cầu một mức — một case enum SignatureLevel, không bao giờ là một chuỗi mà engine phải diễn giải — và mức đó được coi là một hợp đồng cho tài liệu đó. Engine tạo ra mức được yêu cầu hoặc dừng lại với một lỗi có thể hành động; nó không lặng lẽ ký ở mức thấp hơn rồi để một bản ghi tuyên bố một mức cao hơn. Tính đúng đắn không nới lỏng vì có nhiều tài liệu hơn phía sau tài liệu này. Chữ ký thứ một trăm nghìn được tính toán cẩn thận đúng như chữ ký đầu tiên.

Quy tắc fail-closed là điều khiến cho điều đó đáng tin ở quy mô lớn. Đường ký của NextPDF từ chối phát ra một sản phẩm trông-có-vẻ-hợp-lệ-nhưng-chưa-ký thay cho cái bạn yêu cầu. Lộ trình ứng dụng được hỗ trợ là Document API cấp cao: bạn cấu hình chữ ký với Document::setSignature() rồi yêu cầu các byte với Document::getPdfData() (hoặc save() / output()), và lần ghi duy nhất đó hoặc phát ra một PDF được ký đúng cách hoặc ném lỗi trước khi trả lại các byte — không bao giờ là một tệp chưa ký mà người gọi tin là đã ký. Áp dụng trên một lô, đây là quy tắc biến “một cái lọt qua chưa ký” từ một khiếm khuyết tiềm ẩn lặng lẽ thành một công việc đơn lẻ thất bại có thể thử lại.

  1. Warm the signing material onceOn worker boot, open the key/certificate source and the timestamp client. This cost is paid once per worker, not once per document.
  2. Enqueue the documentsA queue holds the per-document jobs. The queue is the throughput dial — signing workers scale horizontally behind it.
  3. Render and sign one documentA disposable unit renders the document, then signs it over its own byte range at the requested PAdES level. Nothing is shared with the next document.
  4. Commit on success, hold on failureA correctly-signed file commits once. A document that could not be signed is failed and retried — never emitted as unsigned bytes.
  5. Checkpoint, and resume on crashA durable run records what has committed. After a crash it continues from the last checkpoint instead of re-signing the whole batch.
A high-volume signing run end to end: shared signing material is warmed once; each document is rendered and signed individually on a disposable unit; a correctly-signed result commits exactly once, while any failure is held for retry, never passed on as unsigned bytes; a crashed run resumes from its checkpoint.

Core cho bạn tính đúng đắn về mật mã: ký CMS bằng phần mềm và PAdES B-B (với B-T qua timestamp client) nơi mỗi tài liệu được ký riêng lẻ và fail-closed. Phần điều phối khiến một lần chạy lớn bền vững, đồng thời, và đúng-một-lần — engine kết xuất không có hiệu ứng phụ cộng với committer, checkpoint, idempotency, và dead-letter store — là module Stream trong các phiên bản nâng cao; ký dựa trên phần cứng qua một HSM hay một cloud KMS cũng tương tự là một mối nối của phiên bản nâng cao. Core chứng minh mỗi chữ ký đúng; các phiên bản nâng cao khiến một triệu chữ ký có thể sống sót.

Hình dạng bên dưới là đơn vị ký theo từng tài liệu bên trong một vòng lặp lô. Mỗi vòng lặp ký một tài liệu ở một mức được nêu tên và hoặc cho ra một kết quả được ký đúng cách hoặc làm thất bại đúng công việc đó — nó không bao giờ trả về các byte chưa ký được khoác lên như một kết quả.

<?php
declare(strict_types=1);
use NextPDF\Contracts\DocumentFactoryInterface;
use NextPDF\Security\Signature\CertificateInfo;
use NextPDF\Security\Signature\SignatureLevel;
use NextPDF\Exception\SignatureException;
use Psr\Log\LoggerInterface;
/**
* One signing-batch iteration: render, sign at a named level, commit or fail.
*
* The factory and the certificate source ($certInfo, the warmed signing
* material) are process-lifetime singletons; the document is disposable. A
* document that cannot be signed at the requested level fails this job loudly —
* it is never committed unsigned.
*
* @param iterable<int, callable(\NextPDF\Core\Document): \NextPDF\Core\Document> $jobs
*/
function signBatch(
DocumentFactoryInterface $factory,
CertificateInfo $certInfo,
LoggerInterface $logger,
iterable $jobs,
): void {
// The level is an explicit, ordered contract — not a flag we hope is honoured.
$level = SignatureLevel::PAdES_B_T;
foreach ($jobs as $jobId => $build) {
// Fresh, disposable unit — shares the warmed signing material only.
$doc = $factory->create();
$doc = $build($doc);
try {
// Sign over this document's own byte range, at exactly $level,
// or throw. There is no "signed lower, reported higher" path.
$doc->setSignature(certInfo: $certInfo, level: $level);
$signed = $doc->getPdfData();
} catch (SignatureException $e) {
// Fail-closed: this document does NOT continue as unsigned bytes.
// The job is failed and left for retry / dead-letter handling.
$logger->error('pdf.sign.failed', ['job_id' => $jobId, 'reason' => $e->getMessage()]);
continue;
}
// Only a correctly-signed result reaches the commit step.
commitSignedOutput($jobId, $signed);
unset($doc, $signed); // release per-document state before the next iteration
$logger->info('pdf.sign.committed', ['job_id' => $jobId, 'level' => $level->value]);
}
}

catch là dòng gánh trọng lượng. Nó là khác biệt giữa một lần chạy giữ lại các tài liệu mà nó không thể ký và một lần chạy vẫn xuất chúng đi. continue không che đậy thất bại — công việc được ghi lại và để dành cho việc thử lại, nên lô kết thúc với một danh sách đã biết, đầy đủ về những gì đã ký và những gì chưa, không bao giờ với một lỗ hổng lặng lẽ.

Hiểu lầm thứ nhất là rằng “ký theo lô” nghĩa là một chữ ký áp dụng cho nhiều tệp. Không phải vậy, và bất kỳ hệ thống nào tuyên bố điều đó thì không đang tạo ra các chữ ký PAdES hợp lệ — digest của mỗi tài liệu được ràng buộc vào các byte của riêng nó (Spec: ISO 32000-2, §12.8). Lô hoàn toàn là về bao nhiêunhanh đến đâu, không bao giờ về việc chia sẻ đơn vị mật mã.

Hiểu lầm thứ hai là rằng tính đồng thời nghĩa là nới lỏng tính đúng đắn để đổi lấy tốc độ — rằng một bộ ký nhanh phải cắt một góc mà bộ ký cẩn thận thì không. Không phải vậy. Vì các đơn vị ký không chia sẻ trạng thái khả biến, chạy chúng song song thay đổi lịch trình, không phải các byte. Mỗi chữ ký song song được tính với cùng sự nghiêm cẩn như một chữ ký đơn lẻ; tính song song nằm ở phần điều phối quanh chúng.

Hiểu lầm thứ ba là rằng tính bền vững là thứ bạn gắn thêm vào sau lần chạy qua đêm thất bại đầu tiên. Tới lúc đó bạn đã mất lần chạy rồi. Một pipeline có thể tiếp tục phải biết, theo từng tài liệu, cái gì đã commit và cái gì chưa trước khi cú sập — vốn chính là điều mà các store checkpoint và idempotency tồn tại để ghi lại.

  • Mỗi chữ ký là riêng theo từng tài liệu và bị ràng buộc bởi chuẩn; không có lối tắt theo lô. Khối lượng thay đổi việc lập lịch, không phải đơn vị mật mã. NextPDF ký mọi tài liệu trên dải byte của riêng nó.
  • Core làm ký CMS bằng phần mềm và PAdES B-B (B-T qua một timestamp client). Engine kết xuất-và-ký bền vững, đồng thời, đúng-một-lần là module Stream trong các phiên bản nâng cao; việc giữ khóa dựa trên HSM/KMS là một mối nối của phiên bản nâng cao. Trang này không tuyên bố phần điều phối đó là của Core.
  • Fail-closed là hành vi của engine, không phải một bảo đảm về cách đấu nối của bạn. NextPDF từ chối phát ra một tệp chưa-ký-nhưng-bị-tin-là-đã-ký và đưa ra lộ trình ký được hỗ trợ. Một pipeline bắt lấy lỗi phát sinh rồi vẫn commit là đã chọn đánh bại bảo đảm đó — chính là cách diễn giải mà catch/continue của ví dụ tồn tại để ngăn ngừa.
  • Mức PAdES được thực thi theo từng tài liệu, không được chứng nhận cho cả lần chạy. Engine tạo ra mức baseline được yêu cầu hoặc thất bại; đó là một sự thực thi mang tính cấu trúc, không phải một phán quyết tuân thủ của bên thứ ba cho các tệp được tạo ra. Bản thân trình tự tăng dần của mức được trình bày trong Hồ sơ baseline PAdES.
  • Hàng đợi, việc giữ khóa, thẩm quyền cấp dấu thời gian, và object store là của bạn. NextPDF cung cấp tính đúng đắn của việc ký theo từng tài liệu và, trong các phiên bản nâng cao, các nguyên thủy điều phối bền vững. Nó không vận hành hạ tầng của bạn hay bảo lãnh cho TSA của bạn.
High-volume and concurrent signing — edition availability
EditionAvailability
Core

Ký CMS bằng phần mềm theo từng tài liệu, PAdES B-B (B-T với một timestamp client), được ký riêng lẻ trên dải byte của riêng mỗi tài liệu, fail-closed chống lại đầu ra âm thầm chưa ký. Việc ký theo từng tài liệu thuần túy không cần bậc thương mại nào.

Pro

Bổ sung module Stream: một engine kết xuất không có hiệu ứng phụ cộng với committer bền vững, checkpoint, idempotency, và dead-letter store — các lần chạy lô đồng thời, an toàn khi sập, đúng-một-lần, tiếp tục thay vì khởi động lại.

Enterprise

Bổ sung việc giữ khóa dựa trên phần cứng (HSM qua PKCS#11, hoặc một cloud KMS) để khóa riêng không bao giờ rời khỏi thiết bị, và các mức PAdES dài hạn (B-LT, B-LTA) giữ cho một kho lưu trữ khối lượng lớn còn kiểm tra được qua nhiều thập kỷ.

  • Tạo tài liệu khối lượng lớn — mô hình lô có hàng đợi, bộ nhớ giới hạn mà trang này ký bên trên; hãy đọc nó trước để nắm kỷ luật về throughput và đo lường.
  • Hồ sơ baseline PAdES — mỗi mức (B-B tới B-LTA) bổ sung gì, để bạn ký ở mức mà nghĩa vụ cần.
  • Chữ ký nằm ở đâu trong một PDF — nền tảng byte-range và dictionary làm cho một chữ ký mang tính theo từng tài liệu.
  • Ký dựa trên HSM — ranh giới khóa-riêng nằm ở đâu khi tài liệu ký sống trong phần cứng.
  • Stream (Pro) — engine kết xuất bền vững, đồng thời, đúng-một-lần biến một đơn vị ký đơn lẻ thành một lần chạy có thể tiếp tục.
  • Ký theo lô (batch signing) — ký nhiều tài liệu theo một lịch. Một khái niệm về lập lịch; mỗi tài liệu vẫn được ký riêng lẻ trên các byte của riêng nó.
  • Fail-closed — khi một thất bại lẽ ra sẽ tạo ra một đầu ra chưa ký hoặc sai, pipeline giữ tài liệu lại và báo cáo, thay vì chuyển tiếp nó dưới dạng byte thuần.
  • Commit đúng-một-lần (exactly-once commit) — một tính chất của pipeline bền vững nơi một đầu ra được ký đúng cách được công bố một lần và không được phát lại khi một lần chạy bị sập tiếp tục.
  • Checkpoint — bản ghi bền vững theo từng tài liệu về những gì đã commit, để một lần chạy có thể tiếp tục từ chỗ nó dừng thay vì ký lại mọi thứ.
  • CMS SignedData — vùng chứa mật mã cho các chữ ký trên nội dung (nó có thể mang nhiều người ký); pipeline này tạo ra một chữ ký PDF của một người ký cho mỗi tài liệu, đơn vị theo từng tài liệu mà một lô tạo ra.
  • PAdES — PDF Advanced Electronic Signatures, họ profile ETSI EN 319 142 cho việc ký PDF; các mức baseline của nó chạy từ B-B tới B-LTA.