PDFium 컴포넌트는 SignPades 메서드를 통해 PDF 문서에 PAdES B-B 규격의 디지털 서명을 날인합니다: 문서를 로드하고, 서명할 바이트 범위를 해싱하며, CAdES CMS 암호 외포 구조를 구축하고, 서명 결과물을 증분 업데이트 방식으로 파일 후미에 덧붙여 기입합니다. 암호 처리 엔진은 Windows 전용으로 구동되므로, 서명 호출 전 PadesCryptoAvailable 검사 플래그를 통한 사전 이중 체크는 필수적입니다
흔히 마주하는 실무적 애로사항이 있습니다. 계약서 PDF가 접수되어 법무 지침에 따라 배포 전에 전자 서명을 날인해야 하는 과제를 안았을 때, 개발자는 이미 렌더링 및 텍스트 추출용으로 쓰고 있던 PDFium 라이브러리를 사용해 이를 해결하려 시도합니다. 그러나 PDFium 자체 API에는 서명 기입 기능이 전혀 없고 오직 읽기 전용으로만 서명 정보 파싱을 제공함을 발견하게 됩니다. PDFium 컴포넌트는 해시 함수 계산부터 최종 저수준 스트림 바이트 주입에 이르기까지 서명 파이프라인 전체를 순수 Pascal 코드로 구현하여 이 기술적 공백을 완벽하게 메워 줍니다. 이 가이드는 해당 처리 흐름을 처음부터 끝까지 정밀하게 다룹니다
왜 PDFium은 디지털 서명을 직접 기입하지 못하나요?
PDFium 라이브러리 엔진은 원래 서명을 읽기 전용 개체로만 다루며, 신규 서명을 직접 창조하는 어떠한 진입점도 제공하지 않습니다. FPDFSignatureObj_* 계열 API는 문서에 새겨진 서명을 순회하고 /Contents 및 /ByteRange를 읽을 수 있게 돕지만, 이에 상응하여 서명 딕셔너리를 생성하고 /Contents를 위한 바이너리 슬롯 공간을 파일 스트림 상에 예약 확보해 쓰는 빌드 계열은 전무합니다; 증분 저장 API(FPDF_INCREMENTAL 매개변수를 수반하는 FPDF_SaveAsCopy)가 존재하긴 하나 서명 기입을 위한 별도 훅(Hook)을 제공하지는 않습니다. 결과적으로 PDFium에 기초하여 서명을 기입하고자 하는 컴포넌트는 모든 서명 바이트 데이터를 독자 조립해야 하며, 이것이 PDFium 컴포넌트가 세 개의 순수 Pascal 유닛으로 구성된 독자 암호 연산 장치를 구축한 이유입니다. FPC 3.2.2는 md5 및 sha1만 탑재하고 최신 규격인 SHA-2 해시 함수는 부재하며, Delphi의 System.Hash SHA-256 API는 FPC 환경과 소스 수준에서 교차 컴파일되지 않으므로, FPdfSha256 유닛이 컴파일러 분기 처리 없이 공통으로 돌릴 수 있는 FIPS 180-4 표준의 TSHA256Digest 연산 모듈을 순수 Pascal로 완벽히 제공합니다. 또한 FPdfAsn1은 CMS 아키텍처에 필수적인 ASN.1 DER 인코더 및 디코더를 처리하고, FPdfCms가 이 둘을 기반으로 최종 CAdES SignedData 구조를 직렬화해 조립해 냅니다
Delphi에서 PDF 디지털 서명을 어떻게 날인하나요?
문서를 메모리에 올리고 서명 인증서의 지문(Thumbprint)을 인자로 넘겨 SignPades를 호출하십시오. PDFium 컴포넌트는 해당 지문 값을 토대로 현재 로그인한 Windows 사용자의 개인 인증서 보관소(Current User "MY")를 스캔하여 매칭되는 인증서와 개인 키(Private key) 핸들을 획득하고 지정한 타겟 경로에 서명이 새겨진 최종 사본 파일을 완벽하게 저장해 냅니다
uses
PDFium, FPdfCrypto;
procedure SignContract(const AThumbprint: string);
var
Pdf: TPdf;
begin
if not PadesCryptoAvailable then
raise Exception.Create('PAdES 서명은 Windows CNG 백엔드가 필수적입니다');
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf'; // 서명할 원본 PDF 문서
Pdf.Active := True;
// 두 번째 인자: 현재 로그인한 Windows 사용자의 "MY" 인증서 보관소에
// 등록된 서명 인증서의 SHA-1 지문 값. 첫 번째 인자: 서명본 사본 저장 경로.
if not Pdf.SignPades('contract-signed.pdf', AThumbprint) then
raise Exception.Create('서명 날인 실패');
finally
Pdf.Free;
end;
end;
PadesCryptoAvailable은 매 구동 시 최우선 확인해야 하는 사전 식별 프로브입니다. Windows 환경에서는 참(True)이 반환되며 내부 crypt32/ncrypt 저수준 암호 엔진이 활성화됩니다; 기타 macOS나 Linux 환경에서는 거짓(False)을 반환하므로 무단 호출 시 EPadesCrypto 암호 연산 예외를 던집니다. 이 이중 안전장치를 강제해 둠으로써 서명 기능 작동이 불가능한 Linux나 macOS 서버 런타임 환경에서 프로그램이 크래시로 급사하는 문제를 사전에 철저히 단속할 수 있습니다. 입력 인자인 인증서 지문은 Windows 인증서 인증서 관리자 상세 탭에 기입된 SHA-1 해시 값 자체이며, 소스 코드 단에 개인 키 데이터나 비밀번호 유실 위험이 있는 원시 리소스를 직접 노출하지 않고도 특정 인증서 서명자를 안전하게 저격 식별해 주는 역할을 담당합니다
CMS 내부 구조: 필수 서명 속성과 RFC 5652 규격 준수
PAdES 표준 디지털 서명은 단지 파일 바이너리를 RSA 알고리즘으로 단순 서명한 결과물이 아닙니다; 이는 규범 지침에 입각한 정형 속성 세트(Signed attributes)를 반드시 함유하는 CAdES CMS SignedData 구조를 요구하며, FPdfCms.BuildSignedData는 이 지침에 준하여 content-type, message-digest 및 서명 인증서 자체를 해시 값으로 묶는 ESS 바인딩 속성인 signing-certificate-v2를 완벽하게 기입해 냅니다. 여기서 일반적인 간이 CMS 구현체들이 흔히 규격 에러를 유발하는 중요한 수치 구조 제약이 있습니다. RFC 5652 §5.4는 서명 속성들의 해시 계산을 수행할 때 반드시 0x31 태그로 식별되는 DER SET OF 인코딩 방식을 따를 것을 규정하지만, 이 동일한 데이터가 SignerInfo 내부로 운반될 때는 IMPLICIT [0] 태그인 0xA0 바이트 형태로 변환되어 포장되어야 합니다. PDFium 컴포넌트는 이를 해결하기 위해 속성 집합을 최초 1회 인코딩하고 0x31 형태로 우선 해시를 연산한 뒤, 스트림 기입 마감 단계에서 선두 태그 1바이트만 0xA0 바이트로 직렬화하여 패치 기입함으로써 불필요한 이중 연산 없이 아주 고효율로 표준 규격을 엄격히 충족해 냅니다
var
Pdf: TPdf;
Opts: TPadesSignOptions;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := 'a1b2c3d4e5f6...'; // MY 인증서 보관소 내의 서명 인증서 지문
Opts.Reason := 'I approve this agreement';
Opts.Location := 'Berlin, DE';
Opts.ContentsSize := 16384; // /Contents의 16진수 할당 크기
if not Pdf.SignPades('contract-signed.pdf', Opts) then
raise Exception.Create('서명 날인 실패');
finally
Pdf.Free;
end;
end;
ByteRange 범위와 /Contents 슬롯의 정밀 배치 연동
디지털 서명은 서명 데이터 자체가 담길 자리를 정확하게 빼고 나머지 전체 파일 영역을 해싱 보증해야 하며, PAdES는 SignPadesBytes 루틴이 정밀 관리하는 고정 크기 플레이스홀더를 통해 이 모순(자기 참조 순환)을 극복합니다. 통상적인 CMS SignedData 크기보다 훨씬 넓은 ContentsSize 분량(기본값 16,384바이트)의 /Contents 16진수 임시 공간을 먼저 확보하고 증분 직렬화를 수행하여 슬롯의 정확한 파일 내 오프셋 위치를 측정한 뒤, 그 구멍의 앞 구역과 뒤 구역 두 구간으로 감싸지는 형태로 /ByteRange 범위를 도출합니다. 그런 다음 오직 이 두 구간 데이터에 대해서만 SHA-256 해시 연산을 돌립니다. 최종 계산된 CMS 바이너리는 16진수 인코딩되어 예약된 구멍 속에 정밀 안착하고 남는 여유 바이트는 0으로 안전하게 패딩(Zero-padded) 처리되며, 마지막으로 상위 크로스 레퍼런스(Xref) 구조가 덧붙여 기입됩니다. 구멍의 크기를 선제 고정해 연산하므로 데이터 기입 시 하단에 배치된 다른 오프셋 주소들이 1바이트도 밀려나지 않고 그대로 고정되어, ByteRange의 서명 유효성이 영구 유지됩니다. 이 증분 방식 덕분에 원본 바이트가 전혀 훼손되지 않으므로, 동일 파일 내의 이전 결재 서명들도 ISO 32000-1 §12.8.1 명세 지침대로 깨지지 않고 그대로 공존할 수 있게 됩니다
Windows CNG 백엔드 탑재 및 실무 제한 사항
PDFium 컴포넌트는 Windows 환경에서만 서명 날인을 생성하며, 이는 타겟 플랫폼을 제한한 의도된 설계입니다. FPdfCryptoWin 유닛은 crypt32.dll 및 ncrypt.dll을 런타임에 동적으로 기동하므로 컴파일 타임에 복잡한 정적 DLL 외부 의존성을 배제하고, 표준 Windows CNG 파이프라인(사용자 MY 보관소를 열어 해시 값으로 인증서를 확보한 뒤, CryptAcquireCertificatePrivateKey를 통해 인증서에 내장된 개인 키 핸들을 취득하고 최종 NCryptSignHash를 호출하는 시퀀스)을 충실히 추종합니다. RSA PKCS#1 v1.5, RSA-PSS 및 최신 ECDSA 알고리즘 서명 규격이 모두 지원됩니다. 이 중 ECDSA는 한 가지 필수 변환 처리가 수반됩니다: Windows NCryptSignHash는 날것 형태의 IEEE P1363 r/s 값 쌍을 반환하는 반면 CMS 외포 규격은 ASN.1 DER 형태의 ECDSA-Sig-Value SEQUENCE를 요구하므로, 엔진이 백엔드 단에서 이를 RFC 5480 표준에 준하여 ASN.1 구조로 자동 재인코딩해 줍니다
var
Pdf: TPdf;
Opts: TPadesSignOptions;
Output: TFileStream;
begin
if not PadesCryptoAvailable then
Exit; // 이 플랫폼에서는 서명 날인 엔진 기동이 불가능합니다
Opts := TPadesSignOptions.Default;
Opts.CertificateThumbprint := ReadThumbprintFromConfig;
Pdf := TPdf.Create(nil);
Output := TFileStream.Create('contract-signed.pdf', fmCreate);
try
Pdf.FileName := 'contract.pdf';
Pdf.Active := True;
Pdf.SignPadesToStream(Output, Opts);
finally
Output.Free;
Pdf.Free;
end;
end;
PAdES B-B 규격 레벨의 한계점
PAdES B-B는 4가지 PAdES 계층의 기본 주춧돌이며, 오직 서명자 신원과 문서 위변조 여부만을 입증할 뿐 그 외의 부가적 보증은 제공하지 못합니다. B-B 서명에는 공인된 시간 기록이 누락되어 있어 날인 시점의 법적 증빙이 곤란하며, 인증서 검증용 해지 자료를 포함하지 않아 수년 뒤 인증서가 파기된 환경에서 유효성 판단을 어렵게 만듭니다. 바로 이 틈새를 고해상도 상위 레벨 서명들이 해결합니다. 감사관이 인정하는 공인 서명 시간을 보증해야 한다면 RFC 3161 시각 스탬프 및 LTV DSS 보강 가이드를 참고하여 B-T 이상 등급으로 서명을 빌드업해 갈 수 있으며, 서명 상태를 판독하고 분석할 목적이라면 PDF 서명 구조 및 PAdES 레벨 검사 가이드를 함께 참고해 조율할 수 있습니다. 또한 서명을 날인하기 전 문서 자체에 잠재된 악성 위협 요소를 제거하려면 PDF 보안 리스크 정밀 감사 가이드가 유용한 지침을 제공해 줍니다
여기에 소개된 SignPades 메서드들은 PDFium 라이브러리가 기본 탑재하는 읽기 전용 서명 분석 스펙과 함께 Delphi 및 C++Builder용 PDFium 컴포넌트 최신 버전에 포함되어 함께 배포됩니다