튜토리얼: Delphi에서 PAdES PDF 서명
서명되지 않은 송장 PDF를 가져와 RFC 3161 타임스탬프가 임베드된 PAdES-B-T 문서를 생성하는 완전한 워크스루예요 — eIDAS, AdES 검증기 및 대부분의 공공 부문 포털에서 요구하는 수준이에요. 키 제공자(PKCS#11 스마트카드 또는 Windows 인증서 저장소)를 구성하고, 올바른 PAdES 프로파일을 선택하며, 서명된 파일을 처음부터 끝까지 검증해요.
서명되지 않은 송장 PDF를 가져와 RFC 3161 타임스탬프가 임베드된 PAdES-B-T 문서를 생성하는 완전한 워크스루예요 — eIDAS, AdES 검증기 및 대부분의 공공 부문 포털에서 요구하는 수준이에요. 키 제공자(PKCS#11 스마트카드 또는 Windows 인증서 저장소)를 구성하고, 올바른 PAdES 프로파일을 선택하며, 서명된 파일을 처음부터 끝까지 검증해요.
PDF를 로드하고, 스마트카드 또는 PFX로 서명하며, 신뢰할 수 있는 타임스탬프를 임베드하고, 검증 가능한 PAdES 파일을 디스크에 기록하는 VCL Forms 애플리케이션이에요.
PAdES-B-B(기본), PAdES-B-T(타임스탬프 포함), PAdES-B-LT(폐기 데이터를 포함한 장기 보관) 또는 PAdES-B-LTA(아카이브 타임스탬프 포함 장기 보관). PAdES-B-T가 송장, 계약 및 대부분의 공공 부문 워크플로에 적합한 기본값이에요.
sgcSign은 10가지 키 제공자를 지원해요. 이 튜토리얼에서는 하드웨어 토큰용 TsgcPKCS11KeyProvider와 Windows에 이미 설치된 인증서용 TsgcWindowsStoreKeyProvider를 다뤄요. 동일한 코드 경로가 PFX, PEM, Azure Trusted Signing, AWS KMS 및 Google KMS에도 적용돼요.
TsgcSignPDF가 서명 파이프라인을 구동하고, TsgcSignProfile_PAdES가 프로파일을 구성하며, TsgcSignatureVerifier가 최종 문서를 검증해요. 각 단계는 이벤트를 노출하므로 모든 단계에서 무슨 일이 일어나는지 점검할 수 있어요.
각 키 제공자는 자체 유닛에 있어요. 사용하지 않을 유닛이 연결되지 않도록 필요한 제공자만 참조하세요.
uses 절이 튜토리얼에서는 두 가지 키 제공자(PKCS#11 및 Windows Store)와 PAdES 서명 유닛을 가져와요. sgcSign_TSA는 RFC 3161 타임스탬프 클라이언트로, TSA URL을 설정하면 PAdES 프로파일이 자동으로 사용해요.
PFX 파일만 있다면 sgcSign_KeyProvider_PKCS11을 sgcSign_KeyProvider_PFX로 바꾸고 5분 빠른 시작에 나온 대로 TsgcPFXKeyProvider를 사용하세요.
uses Classes, SysUtils, // sgc sgcSign_KeyProvider_PKCS11, sgcSign_KeyProvider_WindowsStore, sgcSign_PDF, sgcSign_Profile_PAdES, sgcSign_TSA, sgcSign_Verifier;
두 가지 일반적인 선택지 — PKCS#11 하드웨어 토큰(스마트카드, USB HSM) 또는 Windows 인증서 저장소에 이미 있는 인증서.
ModulePath를 공급업체 PKCS#11 DLL을 가리키도록 설정하세요(예: SafeNet의 경우 C:\Windows\System32\eTPKCS11.dll). Slot 속성은 토큰 슬롯을 선택해요 — 사용 가능한 첫 번째 토큰의 경우 0을 사용하세요. PIN은 OnPinPrompt 이벤트를 통해 전달되므로 소스에 저장되지 않아요.
LoadFromToken을 호출하면 세션이 열리고, 레이블 또는 지문으로 서명 인증서를 찾고, 개인 키 핸들을 준비해요. 토큰에서 바이트가 나가지 않아요 — 실제 RSA-PSS 또는 ECDSA 서명은 장치에서 수행돼요.
var vKeyProvider: TsgcPKCS11KeyProvider; begin vKeyProvider := TsgcPKCS11KeyProvider.Create(nil); try vKeyProvider.ModulePath := 'C:\Windows\System32\eTPKCS11.dll'; vKeyProvider.Slot := 0; vKeyProvider.CertificateLabel := 'My Signing Cert'; vKeyProvider.OnPinPrompt := procedure(Sender: TObject; var aPIN: string) begin aPIN := InputBox('Smart card', 'PIN:', ''); end; vKeyProvider.LoadFromToken; // vKeyProvider is now ready to sign finally // keep alive until signing finishes, then Free end; end;
인증서가 이미 Windows 저장소에 설치되어 있다면 — 예를 들어 공급업체 미니 드라이버가 있는 qualified eID 카드를 사용하기 때문에 — 대신 TsgcWindowsStoreKeyProvider를 사용하세요. 이 제공자는 CNG와 통신하므로 원본 CSP에 관계없이 SHA-256/SHA-384가 작동해요.
주체, 지문 또는 일련번호로 매칭하세요. StoreLocation을 slCurrentUser로 설정하면 개인 저장소를 사용해요; slLocalMachine은 컴퓨터 저장소를 사용하고 일반적으로 관리자 권한이 필요해요.
var vKeyProvider: TsgcWindowsStoreKeyProvider; begin vKeyProvider := TsgcWindowsStoreKeyProvider.Create(nil); vKeyProvider.StoreLocation := slCurrentUser; vKeyProvider.StoreName := 'MY'; vKeyProvider.Thumbprint := '1234ABCD5678EF901234567890ABCDEF12345678'; vKeyProvider.LoadFromStore; end;
PAdES-B-T는 서명에 RFC 3161 타임스탬프를 추가하므로 서명 인증서가 나중에 만료되거나 폐기되더라도 검증자는 문서가 알려진 시점 이전에 서명되었음을 증명할 수 있어요.
Level은 AdES 수준을 선택해요 — palB, palT, palLT 또는 palLTA. PAdES-B-T의 경우 palT로 설정하세요; 그러면 프로파일이 비어 있지 않은 TSA.URL을 기대해요.
HashAlgorithm의 기본값은 shaSHA256으로, 모든 최신 검증기가 기대하는 것이에요. SHA-384 및 SHA-512도 사용 가능해요; SHA-1은 eIDAS에서 더 이상 허용하지 않으므로 의도적으로 제공되지 않아요.
SignatureField 하위 객체는 PDF 내에서 가시적인 서명 위젯이 배치되는 위치를 제어해요 — 페이지 번호, 사각형 좌표 및 선택적 사유 / 위치 / 연락처 텍스트 필드. 보이지 않는 서명(기본값)의 경우 비워두세요.
var vProfile: TsgcSignProfile_PAdES; begin vProfile := TsgcSignProfile_PAdES.Create(nil); vProfile.Level := palT; vProfile.HashAlgorithm := shaSHA256; // RFC 3161 timestamp authority vProfile.TSA.URL := 'http://timestamp.digicert.com'; vProfile.TSA.HashAlgorithm := shaSHA256; // Optional visible signature widget vProfile.SignatureField.Visible := True; vProfile.SignatureField.Page := 1; vProfile.SignatureField.Rect := Rect(50, 50, 250, 120); vProfile.SignatureField.Reason := 'Approved'; vProfile.SignatureField.Location := 'Madrid'; end;
키 제공자와 프로파일을 TsgcSignPDF에 연결한 다음 입력 및 출력 파일 이름을 사용하여 SignFile을 호출하세요.
SignFile은 입력 PDF를 읽고, 문서 다이제스트를 계산하고, 키 제공자를 호출하여 서명을 생성하고, TSA에서 타임스탬프를 가져와 결과를 작성해요. 입력에 이미 서명이 있으면 sgcSign은 증분 업데이트를 추가해요 — 원본 서명은 유효한 상태로 유지돼요.
OnProgress 이벤트는 각 단계(읽기, 해싱, 서명, 타임스탬핑, 쓰기)를 보고하며, 수 MB 파일의 진행률 표시줄에 유용해요.
var vSigner: TsgcSignPDF; begin vSigner := TsgcSignPDF.Create(nil); try vSigner.KeyProvider := vKeyProvider; vSigner.Profile := vProfile; vSigner.OnProgress := procedure(Sender: TObject; const aPhase: string; aPct: Integer) begin Memo1.Lines.Add(Format('%s — %d%%', [aPhase, aPct])); end; vSigner.SignFile('invoice.pdf', 'invoice-signed.pdf'); finally vSigner.Free; end; end;
서명된 문서는 검증되어야 비로소 유용해요. TsgcSignatureVerifier는 한 번의 호출로 암호화 서명, 인증서 체인 및 임베드된 타임스탬프를 검증해요.
VerifyFile 메서드는 서명별 분석이 있는 TsgcSignatureReport를 반환해요. 각 항목에 대해 Status(svValid, svInvalid, svUnknown), 서명자 주체, 실제로 감지된 AdES 수준, 타임스탬프 값 및 모든 체인 또는 폐기 문제를 얻을 수 있어요.
Report.Level을 예상 수준과 비교하세요 — palT를 원했는데 palB를 보고하는 검증은 타임스탬프 요청이 자동으로 실패했음을 의미해요. TSA URL이 일반적인 원인이에요.
var vVerifier: TsgcSignatureVerifier; vReport: TsgcSignatureReport; i: Integer; begin vVerifier := TsgcSignatureVerifier.Create(nil); try vReport := vVerifier.VerifyFile('invoice-signed.pdf'); for i := 0 to vReport.SignatureCount - 1 do begin Memo1.Lines.Add('Signer: ' + vReport.Signatures[i].Subject); Memo1.Lines.Add('Level: ' + vReport.Signatures[i].LevelAsString); Memo1.Lines.Add('Status: ' + vReport.Signatures[i].StatusAsString); Memo1.Lines.Add('TSA time: ' + DateTimeToStr(vReport.Signatures[i].TimestampUTC)); end; finally vVerifier.Free; end; end;
개발자가 첫 PAdES 문서에 서명할 때 가장 자주 보는 문제의 짧은 목록이에요.
일부 TSA는 HTTPS 전용이며 일반 http:// URL을 소프트 오류로 거부해요. palT를 요청했는데 Report.Level이 palB로 반환되면 TSA URL을 https:// 등가물로 전환하세요. 대부분의 공용 TSA는 두 가지 스키마를 모두 게시해요.
테스트 인증서에는 종종 id-kp-documentSigning EKU가 없어요. 암호화 서명이 유효하더라도 Adobe Reader는 경고를 표시해요. 운영 환경에서는 CA로부터 명시적인 Document Signing EKU가 있는 인증서를 요청하세요.
PDF/A-2 및 PDF/A-3 아카이브는 모든 폐기 정보가 문서 자체에 임베드되어야 해요. palLT 또는 palLTA를 사용하세요 — sgcSign은 서명 시점에 OCSP 응답 및 CRL을 수집하여 DSS 사전에 임베드해요.
일부 PKCS#11 드라이버는 몇 초 동안 비활동 후 세션을 닫아요. 전체 서명 작업 동안 TsgcPKCS11KeyProvider 인스턴스를 활성 상태로 유지하고 SignFile이 반환된 후에만 Free를 호출하세요.
장기 아카이빙, 국가별 프로파일 또는 데스크톱에서 중앙 집중식 서버로 서명을 이동하세요.