v2.259.0 버전에서 처음 도입된 HotPDF는 세 가지 THotPDF 메서드인 GetLoadedSignatureInfo, VerifyLoadedSignature, VerifyLoadedSignatureEx를 통해 로드된 PDF 문서의 전자 서명을 검증할 수 있습니다. 이 컴포넌트는 원본 파일의 /ByteRange 세그먼트 영역을 재해싱하고, CMS의 messageDigest 속성을 분석하며, 내장된 서명자 인증서를 대상으로 RSA PKCS#1 v1.5 검증을 수행하여 문서 바이트가 훼손되지 않은 경우 svValid 결과를 반환합니다
이 작업이 필요한 비즈니스 환경은 일상적이지만 그 중요성은 결코 가볍지 않습니다. 협력업체가 서명 완료된 계약서를 반송해 왔을 때 워드 프로세서 내에 저장하기 전에 던지게 되는 유일하게 중요한 질문이 있습니다. 즉, '이 파일이 우리가 보낸 원래 문서와 단 1바이트의 오차도 없는 원본 상태인가, 지명된 인증서로 서명되었는가?'입니다. 코드를 통해 이에 대한 답을 구하는 것이 서명 검증 알고리즘 영역입니다. 반대로 처음부터 PAdES 서명을 생성하고 삽입하는 서명자 측 메커니즘은 HotPDF를 사용한 PAdES 전자 서명 생성 안내서에서 다루고 있습니다. 본 문서는 그와 반대로, 이미 서명된 PDF 문서가 입수되었을 때 어도비 아크로뱃의 초록색 체크표시 스크린샷 대신 프로그램 수치 계산 Verdict를 확보하는 방법을 다룹니다
서명된 PDF는 위변조되지 않았음을 어떻게 증명하나요?
PDF 서명은 '추상적인 계약 문서 내용'을 보호하는 것이 아니라, 파일의 구체적인 물리적 바이트 범위(byte range)를 보호합니다. ISO 32000-1 §12.8은 이 메커니즘을 정의합니다. 서명 양식 필드는 /Contents 항목 내에 CMS SignedData 컨테이너(RFC 5652)를 포함하는 사전을 가지며, /ByteRange 배열에는 서명이 감싸 보호하는 정확한 파일 영역 범위를 지정해 둡니다(§12.8.1 규정). 이 배열은 오프셋과 길이 쌍의 목록이며 실제로는 두 개의 세그먼트로 나뉩니다. 즉, /Contents 16진수 문자열 이전의 모든 영역과 그 이후의 모든 영역입니다. 서명 값 자체는 자기 자신 영역을 해싱할 수 없으므로, 이 구멍을 제외한 나머지 파일 전체 영역을 대상으로 해싱이 수행됩니다
이러한 설계 방식은 API의 전반적인 구조를 결정짓는 핵심 제약 조건을 수반합니다. 바로 검증을 수행할 때 디스크 상에 기록되어 있는 원본 바이트 배열을 대상으로 해시를 직접 계산해야 한다는 점입니다. 파싱 완료된 메모리 상의 객체 데이터는 쓸모가 없는데, 설령 문서 내용에 아무런 수정을 가하지 않더라도 재직렬화(re-serializing)를 하면 바이트 배열이 미세하게 바뀔 수 있기 때문입니다. 따라서 HotPDF는 메모리 상의 임시 데이터를 쓰지 않고, 로드 시에 기준 삼았던 원본 파일 자체나 사용자가 직접 매개변수로 전달한 원시 바이트 TStream 데이터를 대상으로 검증을 가동합니다
검증 수행 전에 서명 메타데이터 읽기
GetLoadedSignatureInfo는 문서의 실제 바이트 데이터 해싱 단계를 건너뛰고 오직 서명 사전과 CMS 컨테이너만을 고속 구문 분석합니다. 단순히 서명 완료 시각이나 서명자가 누구인지 화면에 표출하려는 용도라면 이 메서드를 호출하는 것이 최적입니다. 서명 필드들은 양식 필드 순서대로 0부터 인덱싱되며 GetLoadedSignatureFieldCount를 통해 총 개수를 파악할 수 있습니다. 리턴되는 THPDFSignatureInfo 레코드에는 필드 이름, /SubFilter, 서명자 인증서의 커먼 네임(Common Name), 발급자 및 소유자 식별 이름(DN), 시리얼 번호, 유효 기간, 서명 시각(속성에 기록된 시각을 기본으로 하되 누락 시 사전의 /M 값 기준), 해시 알고리즘 이름 및 /Reason, /Location, /ContactInfo 문자열이 수록되어 전달됩니다. 이때 Status 필드는 수학적 검증을 거치지 않은 구문 분석 상태임을 알리는 svNotVerified 값으로 유지됩니다
var
Pdf: THotPDF;
Info: THPDFSignatureInfo;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('signed-contract.pdf');
for I := 0 to Pdf.GetLoadedSignatureFieldCount - 1 do
begin
Info := Pdf.GetLoadedSignatureInfo(I);
Writeln('Field: ', Info.FieldName);
Writeln('Signer: ', Info.SignerName);
Writeln('Issuer: ', Info.IssuerDN);
Writeln('Algorithm: ', Info.HashAlgorithm);
Writeln('SubFilter: ', Info.SubFilter);
end;
finally
Pdf.Free;
end;
end;
암호학적 서명 검증 가동하기
VerifyLoadedSignatureEx는 파일 기반 문서에 대해 모든 수학적 검증을 단번에 실행하고 그 정보가 채워진 결과 레코드를 함께 반환합니다. 원본 파일을 열고, SignerInfo의 다이제스트 알고리즘을 사용해 /ByteRange 영역을 해싱하고, 그 결과를 messageDigest 서명된 속성값(RFC 5652 §5.4 규정)과 비교 대조한 뒤, 서명 속성들의 DER SET 재인코딩 형태를 대상으로 RSA 서명 검증을 진행합니다. 만일 서명 정보 내에 별도의 서명 속성이 수록되지 않은 경우에는 문서 해시 데이터 자체를 대상으로 직접 RSA 검증을 처리합니다. 지원되는 검증 규격은 SHA-1, SHA-256, SHA-384, SHA-512 다이제스트를 조합한 RSA PKCS#1 v1.5 서명 방식으로, 시중의 범용 서명 도구들이 생성해 내는 adbe.pkcs7.detached 및 ETSI.CAdES.detached 하위 필터 규격을 완벽히 포함합니다
var
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
Status := Pdf.VerifyLoadedSignatureEx(0, Info);
case Status of
svValid:
if Info.CoversWholeDocument then
Writeln('Valid; signature covers the whole file')
else
Writeln('Valid; file was extended after signing');
svDigestMismatch:
Writeln('Document bytes changed after signing');
svSignatureInvalid:
Writeln('RSA check failed over signed attributes');
svUnsupportedAlgorithm:
Writeln('Non-RSA key or unknown digest algorithm');
svMalformed:
Writeln('CMS container could not be parsed');
svSourceUnavailable:
Writeln('No source bytes; use the TStream overload');
end;
end;
외부에서는 쉽게 이해하기 어려운 검증 실패의 원인을 설명해 주는 두 가지 세부 구현 사양이 있습니다. 첫째, 서명 속성 검증 시 인코딩 형식을 매우 민감하게 따집니다. 파일 내부적으로는 속성들이 [0] IMPLICIT 태그로 수록되어 있는 반면, 서명 값 자체는 이들의 DER SET OF 형식을 기반으로 연산되기 때문에, 검증기는 RFC 5652 §5.4 규격 요구에 맞춰 해싱 전에 태그를 다시 변환해 매칭해야 합니다. 이 전처리 과정 없이 스트림 데이터 바이트 그대로 해싱을 수행하는 파서는 정상 서명된 모든 문서들을 실패로 기각하게 됩니다. 둘째, 서명 값인 /Contents는 공간 예약을 위해 뒤편에 0을 채워 넣는(zero-padded) 경우가 일반적이므로, 검증기는 구문 분석 전에 DER 블롭의 최외각 SEQUENCE에 정의된 실제 물리적 길이만큼만 잘라내어 분석해야 합니다. 뒤편의 의미 없는 0 바이트들은 정상 규격 범위 내에 포함되는 사항입니다. 인증서 가공 단계에서 이와 비슷한 ASN.1 구문 분석 보안 사항들은 HotPDF에서의 PKCS#12 및 ASN.1 보안 강화 기사에서 전문적으로 상세히 안내하고 있습니다
올바른 서명이 보증해 주는 유효 범위는 무엇인가요?
svValid는 정확히 다음 조건이 보장되었음을 지칭합니다. /ByteRange가 지정한 물리 파일 바이트들을 해싱한 값이 서명자가 서명할 때 보존한 값과 일치하고, CMS 컨테이너에 내장된 인증서의 공개 키를 대상으로 서명 유효성이 수학적으로 증명되었다는 점입니다. 이는 바이트 단위 위변조 부재와 키 연동 관계만을 증명할 뿐 그 외의 신뢰 관계는 보증해 주지 않습니다. 인증서 체인 유효성 검사 및 신뢰 계층 검증은 HotPDF 검증 알고리즘 범위를 명백히 벗어납니다. 즉, 루트 인증기관까지의 경로 추적, 폐기 여부 조회(CRL/OCSP), 외부 신뢰 스토어 대조는 수행하지 않습니다. 임의 변조한 파일에 공격자가 자체 서명 인증서(self-signed)를 넣어 서명한 문서도 검증기 수학 연산 결과는 svValid로 확인되는데, 이는 내부 수식 체계가 모순 없이 올바르기 때문입니다. 따라서 해당 서명자가 사칭된 인물인지 여부나 실질적인 인증서 신뢰성 판별은 사내 인증서 화이트리스트 대조, Windows 인증서 스토어 매칭 등 비즈니스 애플리케이션의 상위 정책 단계에서 별도로 정의하여 판별해야 합니다
결과 멤버인 CoversWholeDocument 플래그는 보다 미묘한 격차를 검증해 줍니다. 서명은 오직 /ByteRange가 가리키는 고유 영역만을 서명 보호하므로, PDF의 증분 업데이트 설계를 활용하면 기존 서명을 훼손하지 않고 파일 끝부분 뒤에 새로운 개정 데이터를 추가하는 것이 구조적으로 허용되며, 다중 서명 워크플로우 역시 이 원리로 작동합니다. 이 플래그는 검증 과정에서 자동 계산되며 해시 영역과 /Contents 구멍의 결합이 파일 전체 크기와 한 바이트의 편차도 없이 일치할 때만 참(true)이 됩니다. 만일 서명은 svValid 상태이나 이 플래그가 거짓(false)이라면, 서명된 당시의 데이터 부분은 안전하게 보존되어 있으나 서명 이후에 새로운 내용이 뒤편에 추가되었다는 경고이므로, 해당 사후 추가 내용을 허용할지 여부를 검증 비즈니스 단계에서 판단해야 합니다
스트림 기반 로드 문서 및 암호화 파일의 검증 시 유의할 점
매개변수가 없는 기본형 VerifyLoadedSignature 및 VerifyLoadedSignatureEx 함수는 컴포넌트가 로드 시의 소스 파일 경로를 메모리에 유지하고 있는 상태에 의존합니다. 만일 문서를 스트림 형태로 로드했다면 다시 열기 위한 디스크상의 파일명이 부재하며, 이는 암호를 입력받아 해독하는 암호화 PDF 문서 로드 파이프라인(HotPDF를 사용한 AES-256 PDF 암호화 및 복호화 안내서 문서 참고)의 경우에도 유사합니다. 이 경우 파일 기준 함수들은 예측 검증을 실행할 수 없어 svSourceUnavailable를 반환하고 즉시 기각합니다. 해결책은 TStream 전용 오버로드 함수를 사용하는 것으로, 사용자가 데이터베이스 블롭이나 별도 복사본 스트림 메모리 등 보관 중인 원시 바이트 스트림 데이터를 함수 인수로 직접 제공해 검증을 완수할 수 있습니다
var
Src: TFileStream;
Status: THPDFSignatureVerifyStatus;
Info: THPDFSignatureInfo;
begin
// Stream-loaded document: the component holds no source
// file name, so supply the original bytes yourself.
Src := TFileStream.Create('signed-contract.pdf',
fmOpenRead or fmShareDenyWrite);
try
Status := Pdf.VerifyLoadedSignature(0, Src, Info);
if Status <> svValid then
Writeln('Verification failed: ', Ord(Status));
finally
Src.Free;
end;
end;
검증 불가 사유의 상세 구분 리포팅
서명 검증기가 단순히 '참/거짓' 결과만을 리턴하면, 단지 디코드 기술 한계로 분석하지 못하는 경우까지 위변조된 손상 문서로 오판하게 만드므로 상세 에러 열거형 상태 정보를 지원합니다. svDigestMismatch는 서명 후 파일 내용이 변조된 위변조 상태를 알리는 대표적인 신호입니다. svSignatureInvalid는 바이트 해싱 결과는 정확하나 RSA 공개 키 암호 검증이 실패한 경우로, 잘못 연산되어 삽입되었거나 손상된 서명 구조를 지칭합니다. svUnsupportedAlgorithm은 ECDSA 타원곡선 서명이나 알려지지 않은 독특한 다이제스트 방식에 대해 안내해 주는 합리적인 오류로, 서명 자체는 지극히 정상이나 HotPDF가 해독을 지원하지 못함을 의미하며 이를 위변조 에러로 표시해 정상 파일을 파손 문서로 오판하지 않도록 예방합니다. svMalformed는 서명 CMS 데이터 컨테이너 자체가 파손되어 구문 분석이 불가능함을 의미합니다. 아카이브 수집 게이트 웨이 등 일괄 판별이 필요할 때는, 문서 내에 최소한 한 개 이상의 서명이 존재하고 수록된 모든 서명이 svValid 상태일 때만 참(true)을 리턴하는 VerifyAllLoadedSignatures 단일 함수를 사용해 대처하십시오
서명 검증, PAdES 서명 생성, AES-256 보안 암호화 및 로드된 문서의 속성 가공 API들은 모두 외부 DLL 종속성 없이 Object Pascal 소스 기반으로 컴파일되는 Delphi 및 C++Builder용 동일 패키지로 배포됩니다. 기능 사양과 호환 IDE 사양은 HotPDF Component의 공식 제품 설명 페이지에서 안내해 드립니다