계약 / 서명
한눈에 보기
섹션 제목: “한눈에 보기”서명 도메인에는 여섯 개의 계약이 포함됩니다. 이 계약들은 CMS 서명을 생성하고, RFC 3161 타임스탬프를 적용하며, 하드웨어 키로 서명하고, 장기 검증을 활성화하는 방법을 정의합니다. Core는 계약을 공개하고, Pro 및 Enterprise 에디션은 프로덕션 코드를 제공합니다.
composer require nextpdf/core:^3개념 개요
섹션 제목: “개념 개요”PDF 디지털 서명은 서명 딕셔너리에 저장되는 CMS SignedData 구조입니다. Contents 항목은 DER로 인코딩된 구조를 보관합니다. ByteRange 항목은 다이제스트에 포함되는 바이트 범위를 지정합니다. 다이제스트는 파일 전체를 포함하되 서명 값 자체는 제외합니다 — ISO 32000-2 §12.8.1 참조. CMS 구조는 RFC 5652 §5.1을 따르며, 버전, 다이제스트 알고리즘, 캡슐화된 콘텐츠, 서명자 정보로 이루어진 시퀀스입니다.
SignerInterface는 핵심 계약입니다. 이 계약은 바이트 범위에 대한 CMS SignedData를 생성하고, 서명 값에 타임스탬프를 적용하며, 장기 검증 지원 여부를 보고합니다. ETSI EN 319 142에 정의된 PAdES 베이스라인 레벨 B-B부터 B-LTA까지를 지원합니다. 상위 레벨일수록 추가 자료가 더해집니다. B-B는 기본 서명을 담습니다. B-T는 서명 타임스탬프를 추가합니다. B-LT는 폐기 데이터를 추가합니다. B-LTA는 보관용 타임스탬프를 추가합니다.
HsmSignerInterface는 하드웨어 보안 모듈에 보관된 키로 서명합니다. 개인 키는 하드웨어 경계를 벗어나지 않습니다. 이 계약은 CMS 계층이 구조를 빌드할 수 있도록 서명자 인증서와 인증서 체인을 DER 형식으로 반환합니다. DeferredSignerInterface는 비동기 서명을 위해 SignerInterface를 확장합니다. 호출자는 데이터를 제출하고, 작업 식별자를 받은 뒤, 완료 여부를 폴링하고, 결과를 가져옵니다. 원격 HSM 또는 클라우드 키 서비스가 결과를 즉시 반환하지 않을 때 사용합니다.
TimestampProviderInterface는 RFC 3161 타임스탬프 토큰을 요청합니다. 이 프로토콜은 시간 확인 기관(Time-Stamping Authority)과 주고받는 요청 및 응답입니다 — RFC 3161 §2.4 참조. 토큰 내의 messageImprint는 서명 값의 해시입니다 — RFC 3161 §2.4.2. LtvManagerInterface는 장기 검증을 활성화합니다. 이 계약은 인증서 체인을 수집하고, OCSP 및 CRL 응답을 가져오고, 문서 보안 저장소(Document Security Store)를 빌드하며, B-LTA를 위한 문서 타임스탬프를 추가합니다. CryptoPolicyInterface는 암호화 연산이 실행되기 전에 어떤 해시, 서명, 암호화 알고리즘과 키 강도를 허용할지 제어합니다.
API 표면
섹션 제목: “API 표면”| 유형 | 종류 | 주요 멤버 | 안정성 | 도입 버전 |
|---|---|---|---|---|
SignerInterface | interface | sign(string): SignatureResult, timestamp(string): string, supportsLtv(): bool | stable (안정) | 1.0.0 |
HsmSignerInterface | interface | sign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm() | stable (안정) | 1.0.0 |
DeferredSignerInterface | interface | submitForSigning(string): string, retrieveSignature(string), isComplete(string) (SignerInterface 확장) | experimental (실험적) | 3.0.0 |
TimestampProviderInterface | interface | getTimestamp(string): string | experimental (실험적) | 3.0.0 |
LtvManagerInterface | interface | enableLtv(...), addDocumentTimestamp(...) | stable (안정) | 1.10.0 |
CryptoPolicyInterface | interface | isHashAlgorithmAllowed(), isSignatureAlgorithmAllowed(), isEncryptionAlgorithmAllowed(), isKeyStrengthAllowed(), getPreferredHashAlgorithm(), getName() | stable (안정) | 1.9.0 |
SignerInterface::sign()는 NextPDF\Security\Signature\SignatureResult를 반환합니다. 공개 $cmsSignedData 속성은 DER로 인코딩된 CMS를 보관합니다. 공개 $digestHex 속성은 SHA-256 다이제스트를 보관합니다. 공개 $timestampToken 속성은 선택적 TSA 토큰을 보관합니다. 접근자는 toHex() / toHexPadded(int) / getSize() / hasTimestamp()입니다. HsmSignerInterface::sign()는 RSA의 경우 DER로 인코딩된 바이트를, ECDSA의 경우 원시 r‖s 바이트를 반환합니다. 알고리즘 인수는 OpenSSL 식별자를 사용합니다.
코드 예제 — 빠른 시작
섹션 제목: “코드 예제 — 빠른 시작”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\SignerInterface;
/** * Sign a byte range with any SignerInterface implementation. * * @param SignerInterface $signer A core or Premium signer. * @param string $byteRange The PDF byte range to sign. * * @return string Hex-encoded CMS SignedData for the PDF /Contents field. */function signByteRange(SignerInterface $signer, string $byteRange): string{ $result = $signer->sign($byteRange);
return $result->toHex();}SignerInterface::sign()는 SignatureResult를 반환합니다. toHex()는 작성기가 /Contents 필드에 넣을 16 진수 문자열을 생성합니다. 원시 DER 바이트는 공개 $cmsSignedData 속성에 있습니다. 이 함수는 구체적인 클래스가 아니라 계약에 의존합니다. Core 테스트 서명자와 Premium HSM 서명자 모두 이 계약을 충족합니다.
코드 예제 — 프로덕션
섹션 제목: “코드 예제 — 프로덕션”<?php
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use NextPDF\Contracts\CryptoPolicyInterface;use NextPDF\Contracts\SignerInterface;use NextPDF\Contracts\TimestampProviderInterface;use NextPDF\Exception\NextPdfException;use Psr\Log\LoggerInterface;
final readonly class TimestampedSigningService{ public function __construct( private SignerInterface $signer, private TimestampProviderInterface $timestamps, private CryptoPolicyInterface $policy, private LoggerInterface $logger, ) {}
/** * Sign a byte range, then timestamp the CMS structure. * * @param string $byteRange The PDF byte range to sign. * * @return array{cms: string, digest: string, tst: string} */ public function sign(string $byteRange): array { if (!$this->policy->isHashAlgorithmAllowed($this->policy->getPreferredHashAlgorithm())) { throw new \LogicException('Preferred hash rejected by crypto policy.'); }
try { $signature = $this->signer->sign($byteRange); $token = $this->timestamps->getTimestamp($signature->cmsSignedData);
return [ 'cms' => $signature->toHex(), 'digest' => $signature->digestHex, 'tst' => $token, ]; } catch (NextPdfException $e) { $this->logger->error('Signing failed', [ 'policy' => $this->policy->getName(), 'error' => $e->getMessage(), ]);
throw $e; } }}이 서비스는 세 개의 계약을 주입합니다. 암호화 정책은 서명 연산 전에 확인됩니다. SignatureResult는 공개 $cmsSignedData 속성에 CMS 바이트를, $digestHex에 SHA-256 다이제스트를, 그리고 toHex()(/Contents 16 진수 문자열용)를 노출합니다. 타임스탬프 제공자는 CMS 바이트를 받아 DER로 인코딩된 토큰을 반환합니다. catch 블록은 정책 이름을 기록한 뒤 다시 던집니다. 실패를 결코 묵살하지 않습니다.
엣지 케이스 및 주의 사항
섹션 제목: “엣지 케이스 및 주의 사항”- 바이트 범위 다이제스트는 서명 값을 제외해야 합니다.
Contents항목을 포함하는 다이제스트는 결코 검증될 수 없는 서명을 생성합니다 — ISO 32000-2 §12.8.1. SignerInterface::supportsLtv()는 상태가 아니라 기능을 보고합니다. 서명자는 장기 검증을 지원하면서도 타임스탬프 서비스가 구성되지 않은 경우 B-B 서명을 생성할 수 있습니다.DeferredSignerInterface::retrieveSignature()는 작업이 완료될 때까지null을 반환합니다. 확인할 때마다 페이로드를 전송하지 않으려면 먼저isComplete()를 폴링하세요. 결과가 존재하면 가져오기 동작은 멱등적입니다.LtvManagerInterface::addDocumentTimestamp()는 문서 보안 저장소가 작성된 후에 실행되어야 합니다. 이보다 먼저 호출하면 유효하지 않은 B-LTA 구조가 생성됩니다.CryptoPolicyInterface는 정책이 설정되지 않은 경우 모든 알고리즘에 대해true를 반환합니다. 규제 환경에서는 명시적 정책을 설정하고, 개방형 기본값에 의존하지 마세요.HsmSignerInterface::getCertificateChainDer()는 서명자 인증서를 제외합니다. 서명자 리프 인증서에는getCertificateDer()를, 중간 인증서에는 체인 메서드를 사용하세요.
서명 비용은 주로 계약이 아니라 암호화 연산과 네트워크 왕복에 의해 결정됩니다. 로컬 소프트웨어 서명은 한 자릿수 밀리초 수준입니다. HSM 서명은 장치 왕복을 추가합니다. 타임스탬프는 시간 확인 기관으로의 네트워크 왕복을 추가합니다. 장기 검증은 체인 내 인증서마다 하나의 OCSP 또는 CRL 가져오기를 추가합니다. 1500ms 실측 시간의 performance_budget는 웜 커넥션에서 원격 TSA를 사용한 단일 타임스탬프 서명을 포함합니다. 느린 폐기 엔드포인트에 대한 장기 검증은 이를 초과하므로 요청 경로 외부에서 실행해야 합니다. 재현성 프로필은 structural이며, bitwise가 아닙니다. 타임스탬프는 서명 시점을 포함하므로 두 번 실행해도 문서 구조는 동일하게 유지되지만 타임스탬프 바이트에는 차이가 납니다.
보안 참고 사항
섹션 제목: “보안 참고 사항”서명 계약은 엔진의 주요 암호화 경계이므로 위협 모델을 명시합니다. 첫 번째 고려 사항은 키 보관입니다. HsmSignerInterface는 개인 키를 하드웨어 경계 내부에 유지하며, 이 계약은 키 자료를 결코 노출하지 않습니다. 두 번째는 알고리즘 다운그레이드입니다. CryptoPolicyInterface는 연산 전에 약한 해시와 짧은 키를 차단하므로, 배포 환경은 엔진을 포크하지 않고도 FIPS 140-3 또는 eIDAS 프로필을 적용할 수 있습니다. 세 번째는 타임스탬프 신뢰입니다. RFC 3161 토큰은 시간 확인 기관만큼만 신뢰할 수 있으므로, 제공자 계약은 주입 가능하며 배포 환경은 자체 기관을 고정합니다. 네 번째는 장기 검증입니다. 폐기 자료는 서명 시점에 가져와 문서 보안 저장소에 저장되므로, 인증서 만료 후에도 검증이 유지됩니다. 모든 서명자 입력을 신뢰할 수 없는 것으로 취급하세요. 바이트 범위는 엔진이 계산하며, 호출자로부터 결코 받아들이지 않습니다. 이 계약은 암호화 서명을 관장하므로 이 페이지는 export_control_class: legal-review-required로 표시되어 있습니다. 본문은 인용 위생 원칙에 따라 모든 규범적 출처를 의역하며 어느 것도 직접 인용하지 않습니다.
적합성
섹션 제목: “적합성”| 주장 | 표준 | 조항 | 증거 |
|---|---|---|---|
서명 값은 서명 딕셔너리의 Contents 항목에 DER로 인코딩되어 CMS SignedData 또는 TimeStampToken으로 저장됩니다. | ISO 32000-2 | §12.8.1 | |
다이제스트는 ByteRange 배열로 정의된 바이트 범위에 대해 계산되며 서명 값을 제외합니다. | ISO 32000-2 | §12.8.1 | , |
| 장기 검증 자료는 VRI, OCSP, CRL, 인증서 항목과 함께 문서 보안 저장소에 보관됩니다. | ISO 32000-2 | §12.8.4.3 | , |
| PAdES 장기 검증은 검증 데이터를 DSS 및 VRI 딕셔너리에 배치합니다. | ETSI EN 319 142-2 | §6.3 | , |
RFC 3161 타임스탬프 토큰은 messageImprint를 통해 서명 값의 해시를 바인딩하며, TSA 요청과 응답을 통해 교환됩니다. | RFC 3161 | §2.4.2, §2.4 | , |
| CMS SignedData 시퀀스는 버전, 다이제스트 알고리즘, 캡슐화된 콘텐츠, 서명자 정보를 담습니다. | RFC 5652 | §5.1 |
모든 조항은 의역되었습니다. NextPDF는 규범적 텍스트를 재현하지 않습니다. 권위 있는 문구는 발행된 표준을 참조하세요.
상업적 컨텍스트
섹션 제목: “상업적 컨텍스트”Core는 서명 계약을 공개하고 동결합니다. HsmSignerInterface, LtvManagerInterface, 그리고 지연 서명자용 프로덕션 구현은 PKCS#11 하드웨어 통합과 PAdES B-LT 및 B-LTA를 포함하여 Pro 및 Enterprise 에디션에 제공됩니다. Core는 런타임에 class_exists()로 이를 확인하고 계약으로 캐스팅하므로, 오픈 소스 엔진은 상업적 종속성을 가지지 않으며 업그레이드 시 API가 변경되지 않습니다.
참고 자료
섹션 제목: “참고 자료”- 계약: 41개의 공개 인터페이스 (SPI) — SPI 개요 및 안정성 등급.
- 계약 / 보안 정책 —
CryptoPolicyInterface알고리즘과 키 게이팅. - 계약 / 추출 — 서명된 보관과 짝을 이루는 PDF/A 적용.
- 보안 — 암호화 및 서명 구현 표면.
- 감사 — 정책 이름과 서명 이벤트 감사 로깅.
- 예외 — 서명자가 던지는
NextPdfException계층 구조.