튜토리얼: Delphi에서 PAdES PDF 서명

서명되지 않은 송장 PDF를 가져와 RFC 3161 타임스탬프가 임베드된 PAdES-B-T 문서를 생성하는 완전한 워크스루예요 — eIDAS, AdES 검증기 및 대부분의 공공 부문 포털에서 요구하는 수준이에요. 키 제공자(PKCS#11 스마트카드 또는 Windows 인증서 저장소)를 구성하고, 올바른 PAdES 프로파일을 선택하며, 서명된 파일을 처음부터 끝까지 검증해요.

PAdES-B-B / B-T / B-LT / B-LTA
PKCS#11 / Windows 저장소 / PFX
Delphi 7 – RAD Studio 13

무엇을 만들까요

PDF를 로드하고, 스마트카드 또는 PFX로 서명하며, 신뢰할 수 있는 타임스탬프를 임베드하고, 검증 가능한 PAdES 파일을 디스크에 기록하는 VCL Forms 애플리케이션이에요.

PAdES 수준 선택

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가 최종 문서를 검증해요. 각 단계는 이벤트를 노출하므로 모든 단계에서 무슨 일이 일어나는지 점검할 수 있어요.

유닛 추가하기

각 키 제공자는 자체 유닛에 있어요. 사용하지 않을 유닛이 연결되지 않도록 필요한 제공자만 참조하세요.

Delphi uses

이 튜토리얼에서는 두 가지 키 제공자(PKCS#11 및 Windows Store)와 PAdES 서명 유닛을 가져와요. sgcSign_TSA는 RFC 3161 타임스탬프 클라이언트로, TSA URL을 설정하면 PAdES 프로파일이 자동으로 사용해요.

PFX 파일만 있다면 sgcSign_KeyProvider_PKCS11sgcSign_KeyProvider_PFX로 바꾸고 5분 빠른 시작에 나온 대로 TsgcPFXKeyProvider를 사용하세요.

uPDFSigning.pas
uses
  Classes, SysUtils,
  // sgc
  sgcSign_KeyProvider_PKCS11,
  sgcSign_KeyProvider_WindowsStore,
  sgcSign_PDF,
  sgcSign_Profile_PAdES,
  sgcSign_TSA,
  sgcSign_Verifier;

키 제공자 구성하기

두 가지 일반적인 선택지 — PKCS#11 하드웨어 토큰(스마트카드, USB HSM) 또는 Windows 인증서 저장소에 이미 있는 인증서.

PKCS#11 스마트카드

ModulePath를 공급업체 PKCS#11 DLL을 가리키도록 설정하세요(예: SafeNet의 경우 C:\Windows\System32\eTPKCS11.dll). Slot 속성은 토큰 슬롯을 선택해요 — 사용 가능한 첫 번째 토큰의 경우 0을 사용하세요. PIN은 OnPinPrompt 이벤트를 통해 전달되므로 소스에 저장되지 않아요.

LoadFromToken을 호출하면 세션이 열리고, 레이블 또는 지문으로 서명 인증서를 찾고, 개인 키 핸들을 준비해요. 토큰에서 바이트가 나가지 않아요 — 실제 RSA-PSS 또는 ECDSA 서명은 장치에서 수행돼요.

step1-pkcs11.pas
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 인증서 저장소

인증서가 이미 Windows 저장소에 설치되어 있다면 — 예를 들어 공급업체 미니 드라이버가 있는 qualified eID 카드를 사용하기 때문에 — 대신 TsgcWindowsStoreKeyProvider를 사용하세요. 이 제공자는 CNG와 통신하므로 원본 CSP에 관계없이 SHA-256/SHA-384가 작동해요.

주체, 지문 또는 일련번호로 매칭하세요. StoreLocationslCurrentUser로 설정하면 개인 저장소를 사용해요; slLocalMachine은 컴퓨터 저장소를 사용하고 일반적으로 관리자 권한이 필요해요.

step1-winstore.pas
var
  vKeyProvider: TsgcWindowsStoreKeyProvider;
begin
  vKeyProvider := TsgcWindowsStoreKeyProvider.Create(nil);
  vKeyProvider.StoreLocation := slCurrentUser;
  vKeyProvider.StoreName := 'MY';
  vKeyProvider.Thumbprint :=
    '1234ABCD5678EF901234567890ABCDEF12345678';
  vKeyProvider.LoadFromStore;
end;

PAdES-B-T 프로파일 구성하기

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 내에서 가시적인 서명 위젯이 배치되는 위치를 제어해요 — 페이지 번호, 사각형 좌표 및 선택적 사유 / 위치 / 연락처 텍스트 필드. 보이지 않는 서명(기본값)의 경우 비워두세요.

step2-profile.pas
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;

PDF 서명하기

키 제공자와 프로파일을 TsgcSignPDF에 연결한 다음 입력 및 출력 파일 이름을 사용하여 SignFile을 호출하세요.

전체 서명 호출

SignFile은 입력 PDF를 읽고, 문서 다이제스트를 계산하고, 키 제공자를 호출하여 서명을 생성하고, TSA에서 타임스탬프를 가져와 결과를 작성해요. 입력에 이미 서명이 있으면 sgcSign은 증분 업데이트를 추가해요 — 원본 서명은 유효한 상태로 유지돼요.

OnProgress 이벤트는 각 단계(읽기, 해싱, 서명, 타임스탬핑, 쓰기)를 보고하며, 수 MB 파일의 진행률 표시줄에 유용해요.

step3-sign.pas
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;

서명된 PDF 검증하기

서명된 문서는 검증되어야 비로소 유용해요. TsgcSignatureVerifier는 한 번의 호출로 암호화 서명, 인증서 체인 및 임베드된 타임스탬프를 검증해요.

결과 읽기

VerifyFile 메서드는 서명별 분석이 있는 TsgcSignatureReport를 반환해요. 각 항목에 대해 Status(svValid, svInvalid, svUnknown), 서명자 주체, 실제로 감지된 AdES 수준, 타임스탬프 값 및 모든 체인 또는 폐기 문제를 얻을 수 있어요.

Report.Level을 예상 수준과 비교하세요 — palT를 원했는데 palB를 보고하는 검증은 타임스탬프 요청이 자동으로 실패했음을 의미해요. TSA URL이 일반적인 원인이에요.

step4-verify.pas
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가 HTTP 200을 반환하지만 형식이 잘못된 응답

일부 TSA는 HTTPS 전용이며 일반 http:// URL을 소프트 오류로 거부해요. palT를 요청했는데 Report.LevelpalB로 반환되면 TSA URL을 https:// 등가물로 전환하세요. 대부분의 공용 TSA는 두 가지 스키마를 모두 게시해요.

인증서에 확장 키 사용이 없음

테스트 인증서에는 종종 id-kp-documentSigning EKU가 없어요. 암호화 서명이 유효하더라도 Adobe Reader는 경고를 표시해요. 운영 환경에서는 CA로부터 명시적인 Document Signing EKU가 있는 인증서를 요청하세요.

PDF/A 문서에는 PAdES-B-LT가 필요해요

PDF/A-2 및 PDF/A-3 아카이브는 모든 폐기 정보가 문서 자체에 임베드되어야 해요. palLT 또는 palLTA를 사용하세요 — sgcSign은 서명 시점에 OCSP 응답 및 CRL을 수집하여 DSS 사전에 임베드해요.

스마트카드 세션 타임아웃

일부 PKCS#11 드라이버는 몇 초 동안 비활동 후 세션을 닫아요. 전체 서명 작업 동안 TsgcPKCS11KeyProvider 인스턴스를 활성 상태로 유지하고 SignFile이 반환된 후에만 Free를 호출하세요.

여기서 어디로 갈까요

장기 아카이빙, 국가별 프로파일 또는 데스크톱에서 중앙 집중식 서버로 서명을 이동하세요.

21개 국가 프로파일

VeriFactu, FatturaPA, KSeF, FACTUR-X 및 EU 고용 계약 프로파일을 위한 한 줄짜리 호출 — 모두 동일한 PAdES / XAdES 엔진 위에 구축되어 있어요.

더 읽기 →

sgcSign Server

서명을 자체 호스팅 데몬으로 이동하세요 — REST API, GitHub Actions 통합, PAdES 위의 Authenticode 및 ClickOnce.

더 읽기 →

10개 키 제공자

PFX 및 Windows 저장소를 넘어 — Azure Trusted Signing, AWS KMS, Google KMS, HashiCorp Vault, Certum 및 CSC v2 클라우드 제공자.

더 읽기 →

첫 PDF에 서명할 준비가 되셨나요?

체험판을 다운로드하고, 자신의 송장에 대해 이 튜토리얼을 실행하고, 출시할 때 sgcSign 라이선스를 받으세요.