콘텐츠로 이동

계약 / 서명

서명 도메인에는 여섯 개의 계약이 포함됩니다. 이 계약들은 CMS 서명을 생성하고, RFC 3161 타임스탬프를 적용하며, 하드웨어 키로 서명하고, 장기 검증을 활성화하는 방법을 정의합니다. Core는 계약을 공개하고, Pro 및 Enterprise 에디션은 프로덕션 코드를 제공합니다.

Terminal window
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는 암호화 연산이 실행되기 전에 어떤 해시, 서명, 암호화 알고리즘과 키 강도를 허용할지 제어합니다.

유형종류주요 멤버안정성도입 버전
SignerInterfaceinterfacesign(string): SignatureResult, timestamp(string): string, supportsLtv(): boolstable (안정)1.0.0
HsmSignerInterfaceinterfacesign(string, string): string, getCertificateDer(), getCertificateChainDer(), getPublicKeyAlgorithm()stable (안정)1.0.0
DeferredSignerInterfaceinterfacesubmitForSigning(string): string, retrieveSignature(string), isComplete(string) (SignerInterface 확장)experimental (실험적)3.0.0
TimestampProviderInterfaceinterfacegetTimestamp(string): stringexperimental (실험적)3.0.0
LtvManagerInterfaceinterfaceenableLtv(...), addDocumentTimestamp(...)stable (안정)1.10.0
CryptoPolicyInterfaceinterfaceisHashAlgorithmAllowed(), 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 식별자를 사용합니다.

examples/contracts/signing-quickstart.php
<?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 서명자 모두 이 계약을 충족합니다.

examples/contracts/signing-production.php
<?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가 변경되지 않습니다.