타협 없이, 대규모로 서명하기
Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8Spec: ETSI EN 319 142-1ETSI EN 319 142-1Spec: RFC 5652, §5.1RFC 5652 §5.1
한눈에 보기
섹션 제목: “한눈에 보기”문서 하나를 서명하는 것은 암호화 연산입니다. 마감을 앞두고 십만 건을 서명하는 것은 같은 연산의 반복이며, 거기서 위험한 실패는 더 이상 “느렸다”가 아니라 “그중 하나가 미서명된 채로 나갔고 아무도 알아채지 못했다”입니다. 이 페이지는 첫 번째 것을 포기하지 않은 채 두 번째 것을 해내는 일에 관한 것입니다. 모든 서명이 여전히 정확하고, 실행이 서명할 수 없는 파일을 내보내기를 거부하며, 대규모 작업이 처음부터 시작하지 않고 이어지는, 대량의 동시 서명입니다.
이것이 중요한 이유
섹션 제목: “이것이 중요한 이유”서명은 문서마다 따로 성립하는 사실입니다. 그 다이제스트는 서명 값 자체를 제외한 선언된 바이트 범위에 대해 계산되므로(Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8), 천 건의 문서를 한 번에 “배치로” 서명할 정직한 방법은 없습니다 — 각 문서는 자기 자신의 바이트에 대한 고유한 CMS SignedData를 지닙니다(Spec: RFC 5652, §5.1RFC 5652 §5.1). 따라서 규모는 정확히 한 가지가 조용히 잘못될 가능성을 곱절로 늘립니다. 잠깐 실패한 키 핸들, 시간이 초과된 타임스탬프 기관, 절반만 쓰인 파일을 쥔 채 죽어 버린 워커 같은 것들입니다.
비싼 결과는 크래시가 아닙니다. 크래시는 시끄럽고 여러분은 그것을 재시도합니다. 비싼 결과는 조용한 것입니다 — 완성된 것처럼 보이는 미서명 PDF가 보관소에 놓여 있다가, 몇 달 뒤 감사인의 검증기에 발견되는 것입니다. 대량에서는, 정작 중요한 그 하나가 확인되기 직전까지 “대부분 서명됨”은 “서명됨”과 구별되지 않습니다. 대규모 서명의 핵심은 바로 그 결과를 통계적으로 드물게 만드는 것이 아니라 구조적으로 불가능하게 만드는 것입니다.
짧게 요약하면
섹션 제목: “짧게 요약하면”- 모든 문서는 자기 자신의 바이트 범위에 대해 개별적으로 서명됩니다. 배치는 암호화 용어가 아니라 스케줄링 용어입니다. 공유되는 서명은 없습니다.
- 레벨은 힌트가 아니라 계약입니다. 여러분이 PAdES 베이스라인 레벨을 지정하면 엔진은 모든 문서에 대해 정확히 그 레벨을 생산하거나, 그 문서를 큰 소리로 실패시킵니다(Spec: ETSI EN 319 142-1ETSI EN 319 142-1).
- 파이프라인은 페일 클로즈드입니다. 올바르게 서명될 수 없는 문서는 평범한 바이트로 통과하지 못합니다. 그것은 건네지는 것이 아니라 붙들립니다.
- 동시성은 문서 단위이며, 설계상 안전합니다. 서명 단위는 가변 상태를 공유하지 않으므로, 두 워커가 서로의 출력을 손상시킬 수 없습니다.
- 대규모 실행은 내구성이 있습니다. 커밋된 출력은 재개 시 다시 내보내지지 않으며, 크래시된 실행은 모든 것을 다시 서명하는 대신 마지막 체크포인트에서 이어집니다.
NextPDF가 이를 다루는 방식
섹션 제목: “NextPDF가 이를 다루는 방식”이 설계는 하나의 분리에 기댑니다. 서명을 생산하는 것은 작고, 결정론적이며, 문서 단위의 단계이고, 그것을 수천 번 안전하게 실행하는 것은 오케스트레이션 단계입니다. 그 둘을 떼어 놓는 것이 각각을 단순하게 유지하는 비결입니다.
서명 단계는 결코 타협해서는 안 되는 단계입니다. 여러분은 레벨을 요청하며 —
엔진이 해석해야 하는 문자열이 아니라 SignatureLevel enum 케이스로 — 그 레벨은
그 문서에 대한 계약으로 취급됩니다. 엔진은 요청된 레벨을 생산하거나 실행 가능한 오류와 함께
멈춥니다. 그것은 조용히 더 낮은 레벨로 서명하고 기록이 더 높은 레벨을 주장하게 두지 않습니다.
이 문서 뒤에 더 많은 문서가 있다고 해서 정확성이 느슨해지지 않습니다. 십만 번째
서명은 첫 번째 것만큼이나 정확히 신중하게 계산됩니다.
페일 클로즈드 규칙이 그것을 대량에서 신뢰할 수 있게 만드는 요소입니다. NextPDF의 서명
경로는 여러분이 요청한 것 대신 그럴듯해 보이지만 미서명된 산출물을 내보내기를 거부합니다.
지원되는 애플리케이션 경로는 고수준 Document API입니다. 여러분은 Document::setSignature()로
서명을 구성한 뒤 Document::getPdfData()(또는 save() / output())로 바이트를 요청하며,
그 단일 쓰기 패스는 올바르게 서명된 PDF를 내보내거나 바이트를 돌려주기 전에 예외를
던집니다 — 호출자가 서명되었다고 믿는 미서명 파일을 결코 내보내지 않습니다. 이것을 배치 전반에
적용하면, “하나가 미서명된 채 빠져나갔다”를 조용한 잠재 결함에서 하나의 실패한, 재시도 가능한
작업으로 바꾸는 규칙이 됩니다.
- 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.
- Enqueue the documentsA queue holds the per-document jobs. The queue is the throughput dial — signing workers scale horizontally behind it.
- 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.
- 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.
- 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.
Core는 여러분에게 암호화적 정확성을 제공합니다. 즉, 각 문서가 개별적으로 그리고 페일 클로즈드 방식으로 서명되는 소프트웨어 CMS 서명과 PAdES B-B(타임스탬프 클라이언트를 통한 B-T)입니다. 대규모 실행을 내구성 있고, 동시적이며, 정확히 한 번 처리되게 만드는 오케스트레이션 — 부작용 없는 렌더 엔진과 더불어 커미터, 체크포인트, 멱등성, 데드레터 저장소 — 은 상위 에디션의 Stream 모듈입니다. HSM이나 클라우드 KMS를 통한 하드웨어 기반 서명도 마찬가지로 상위 에디션의 이음매입니다. Core는 각 서명이 옳음을 증명하고, 상위 에디션은 그것 백만 건을 견딜 수 있게 만듭니다.
실제 예시
섹션 제목: “실제 예시”아래 형태는 배치 루프 안의 문서 단위 서명 단위입니다. 각 반복은 지정된 레벨에서 한 문서를 서명하고 올바르게 서명된 결과를 산출하거나 그 하나의 작업을 실패시킵니다 — 그것은 미서명된 바이트를 결과인 척 꾸며 반환하는 일이 결코 없습니다.
<?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가 하중을 지탱하는 줄입니다. 그것은 서명할 수 없었던 문서를 붙들어 두는 실행과
그것을 어쨌든 내보내는 실행 사이의 차이입니다. continue는 실패를 덮어 가리지 않습니다 —
작업은 기록되어 재시도를 위해 남겨지므로, 배치는 무엇이 서명되었고 무엇이 서명되지 않았는지에
대한 알려진, 완전한 목록을 가지고 끝나며, 결코 조용한 누락을 남기지 않습니다.
흔한 오해
섹션 제목: “흔한 오해”첫 번째 오해는 “배치 서명”이 여러 파일에 적용된 하나의 서명을 뜻한다는 것입니다. 그렇지 않으며, 그렇다고 주장하는 어떤 시스템도 유효한 PAdES 서명을 생산하고 있지 않습니다 — 각 문서의 다이제스트는 자기 자신의 바이트에 결속됩니다 (Spec: ISO 32000-2, §12.8ISO 32000-2 §12.8). 배치는 순전히 얼마나 많이와 얼마나 빨리에 관한 것이지, 결코 암호화 단위를 공유하는 것이 아닙니다.
두 번째는 동시성이 속도를 위해 정확성을 느슨하게 하는 것 — 빠른 서명자가 신중한 서명자는 하지 않는 절차를 빠뜨려야 한다는 것 — 을 뜻한다는 것입니다. 그렇지 않습니다. 서명 단위는 가변 상태를 공유하지 않으므로, 그것들을 병렬로 실행하면 바이트가 아니라 스케줄이 바뀝니다. 각 병렬 서명은 단일 서명과 동일한 엄밀함으로 계산되며, 병렬성은 그것들을 둘러싼 오케스트레이션 안에 있습니다.
세 번째는 내구성이 첫 야간 실행이 실패한 뒤에 덧붙이는 무언가라는 것입니다. 그때쯤이면 여러분은 이미 그 실행을 잃었습니다. 재개 가능한 파이프라인은 크래시 전에 문서별로 무엇이 커밋되었고 무엇이 커밋되지 않았는지 알아야 합니다 — 그것이 바로 체크포인트와 멱등성 저장소가 기록하기 위해 존재하는 그것입니다.
한계와 경계
섹션 제목: “한계와 경계”- 각 서명은 문서 단위이며 표준에 결속됩니다. 배치 지름길은 없습니다. 대량은 암호화 단위가 아니라 스케줄링을 바꿉니다. NextPDF는 모든 문서를 자기 자신의 바이트 범위에 대해 서명합니다.
- Core는 소프트웨어 CMS 서명과 PAdES B-B(타임스탬프 클라이언트를 통한 B-T)를 합니다. 내구성 있고, 동시적이며, 정확히 한 번 처리하는 렌더링-및-서명 엔진은 상위 에디션의 Stream 모듈입니다. HSM/KMS 기반 키 보관은 상위 에디션의 이음매입니다. 이 페이지는 그 오케스트레이션을 Core라고 주장하지 않습니다.
- 페일 클로즈드는 엔진의 동작이지, 여러분의 배선에 대한 보장이 아닙니다.
NextPDF는 미서명되었지만 서명된 것으로 믿어지는 파일을 내보내기를 거부하고
지원되는 서명 경로를 노출합니다. 그로 인한 오류를 잡아서 어쨌든 커밋하는
파이프라인은 그 보장을 무력화하기로 선택한 것입니다 — 예시의
catch/continue가 방지하기 위해 존재하는 바로 그 틀입니다. - PAdES 레벨은 문서별로 강제되며, 실행 전체에 대해 인증되는 것이 아닙니다. 엔진은 요청된 베이스라인 레벨을 생산하거나 실패합니다. 그것은 생산된 파일에 대한 제3자 적합성 판정이 아니라 구조적 강제입니다. 레벨 진행 자체는 PAdES 베이스라인 프로파일에서 다룹니다.
- 큐, 키 보관, 타임스탬프 기관, 그리고 객체 저장소는 여러분의 것입니다. NextPDF는 문서별 서명 정확성을, 그리고 상위 에디션에서는 내구성 있는 오케스트레이션 프리미티브를 제공합니다. 그것은 여러분의 인프라를 운영하거나 여러분의 TSA를 보증하지 않습니다.
| Edition | Availability |
|---|---|
| Core | 문서별 소프트웨어 CMS 서명, PAdES B-B(타임스탬프 클라이언트를 통한 B-T)이며, 각 문서의 고유한 바이트 범위에 대해 개별적으로 서명되고, 조용히 미서명되는 출력에 대해 페일 클로즈드입니다. 평범한 문서별 서명에는 어떤 상업 티어도 필요하지 않습니다. |
| Pro | Stream 모듈을 더합니다. 부작용 없는 렌더 엔진과 더불어 내구성 있는 커미터, 체크포인트, 멱등성, 데드레터 저장소 — 처음부터 다시 시작하는 대신 이어지는, 동시적이고 크래시 안전하며 정확히 한 번 처리되는 배치 실행입니다. |
| Enterprise | 하드웨어 기반 키 보관(PKCS#11을 통한 HSM, 또는 클라우드 KMS)을 더해 개인 키가 장치를 결코 떠나지 않게 하며, 대규모 보관소를 수십 년간 검증 가능하게 유지하는 장기 PAdES 레벨(B-LT, B-LTA)을 더합니다. |
관련 문서
섹션 제목: “관련 문서”- 대량 문서 생성 — 이 페이지가 그 위에 서명을 얹는, 메모리가 제한된 큐 기반 배치 모델입니다. 처리량과 측정 규율에 대해서는 이것을 먼저 읽으세요.
- PAdES 베이스라인 프로파일 — 각 레벨 (B-B부터 B-LTA까지)이 무엇을 더하는지, 그래서 의무가 요구하는 레벨로 서명할 수 있도록.
- PDF 안에서 서명이 자리 잡는 방식 — 서명을 문서 단위로 만드는 바이트 범위 및 딕셔너리 토대.
- HSM 기반 서명 — 서명 자재가 하드웨어에 있을 때 개인 키 경계가 어디에 자리하는지.
- Stream (Pro) — 단일 서명 단위를 재개 가능한 실행으로 바꾸는, 내구성 있고 동시적이며 정확히 한 번 처리되는 렌더링 엔진.
용어집
섹션 제목: “용어집”- 배치 서명 — 스케줄에 따라 여러 문서를 서명하는 것. 스케줄링 개념이며, 각 문서는 여전히 자기 자신의 바이트에 대해 개별적으로 서명됩니다.
- 페일 클로즈드 — 그렇지 않았다면 미서명되거나 잘못된 출력을 생산했을 실패에서, 파이프라인은 그것을 평범한 바이트로 건네는 대신 문서를 붙들고 보고합니다.
- 정확히 한 번 커밋(exactly-once commit) — 올바르게 서명된 출력이 한 번 게시되고 크래시된 실행이 재개될 때 다시 내보내지지 않는, 내구성 파이프라인의 속성.
- 체크포인트 — 무엇이 커밋되었는지에 대한 문서별 내구성 기록으로, 실행이 모든 것을 다시 서명하는 대신 멈춘 지점부터 이어질 수 있게 합니다.
- CMS SignedData — 콘텐츠에 대한 서명을 담는 암호화 컨테이너 (여러 서명자를 담을 수 있음). 이 파이프라인은 문서마다 한 서명자의 PDF 서명을, 즉 배치가 생산하는 문서별 단위를 생산합니다.
- PAdES — PDF 고급 전자 서명(PDF Advanced Electronic Signatures), PDF 서명을 위한 ETSI EN 319 142 프로파일 계열입니다. 그 베이스라인 레벨은 B-B부터 B-LTA까지입니다.