튜토리얼: Delphi에서 XAdES XML 서명

일반 XML 송장을 가져와 XAdES-B-T 문서를 생성하는 실용적인 워크스루예요 — FatturaPA, FACTUR-X 및 유럽 전역의 대부분의 전자 청구서 포털에서 요구하는 수준이에요. 엔벨로프드, 엔벨로핑 및 디태치드 서명 패키징 중에서 선택하고, RFC 3161 타임스탬프를 첨부하고, 결과를 검증하여 거래 상대방이 정확히 무엇을 볼지 알 수 있도록 해요.

XAdES-B-B / B-T / B-LT / B-LTA
엔벨로프드 / 엔벨로핑 / 디태치드
Delphi 7 – RAD Studio 13

세 가지 패키징 모드, 하나의 엔진

XAdES는 타임스탬핑 및 장기 검증 데이터로 W3C XML-DSig 사양을 확장해요. 서명하기 전에 ds:Signature 요소가 어디에 위치할지 결정하세요.

엔벨로프드

ds:Signature 요소가 문서 루트 내부에 추가돼요. 이것은 FatturaPA, FACTUR-X-XAdES 및 대부분의 전자 청구서 흐름에서 사용되는 형식이에요. 서명된 XML은 여전히 원본 문서의 유효한 사본이에요 — 리더는 이해하지 못하는 서명 요소를 무시해요.

엔벨로핑

원본 XML이 ds:Signature 내부ds:Object 자식이 돼요. 단일 자체 포함 엔벨로프가 필요하고 기존 소비자가 문서를 계속 파싱할 수 있는지 신경 쓰지 않을 때 유용해요.

디태치드

서명이 별도의 파일에 있고 URI 참조를 통해 원본 문서를 가리켜요. SAML, ebXML 및 원본 문서가 바이트 단위로 손대지 않은 상태로 유지되어야 하는 모든 흐름에서 일반적이에요.

유닛 추가하기

XAdES에는 XML 서명자, XAdES 프로파일 및 키 제공자가 필요해요. 이 튜토리얼에서는 PFX를 사용해요; 스마트카드 또는 클라우드 HSM이 있다면 10개 제공자 중 어느 것으로든 교체하세요.

Delphi uses

sgcSign_XML은 XML 서명 코어예요. sgcSign_Profile_XAdES는 수준, 변환 및 타임스탬프 구성을 노출해요. sgcSign_KeyProvider_PFX는 디스크에서 PKCS#12 파일을 로드해요 — 가장 일반적인 시작점이에요.

장기 수준(B-LT, B-LTA)의 경우 폐기 데이터를 가져와 임베드할 수 있도록 sgcSign_OCSPsgcSign_CRL도 필요해요.

uXAdESSigning.pas
uses
  Classes, SysUtils,
  // sgc
  sgcSign_KeyProvider_PFX,
  sgcSign_XML,
  sgcSign_Profile_XAdES,
  sgcSign_TSA,
  sgcSign_OCSP,
  sgcSign_CRL,
  sgcSign_Verifier;

XML과 키 로드하기

두 가지 입력 — 서명할 XML과 서명에 사용할 PFX 인증서.

PFX 키 제공자

TsgcPFXKeyProvider는 Windows CNG를 통해 PKCS#12 파일을 가져오므로, 인증서가 원래 어떤 CSP용으로 발급되었든 관계없이 최신 서명 핸들을 얻을 수 있어요. 동일한 제공자가 Windows 7 이상, 32비트 및 64비트에서 작동해요.

전체 서명 작업 동안 제공자를 활성 상태로 유지하세요 — XML 서명자는 SignXML이 반환될 때까지 기본 키 핸들을 참조해요.

step1-load.pas
var
  vXML: string;
  vKeyProvider: TsgcPFXKeyProvider;
begin
  vXML := TFile.ReadAllText('invoice.xml', TEncoding.UTF8);

  vKeyProvider := TsgcPFXKeyProvider.Create(nil);
  vKeyProvider.FileName := 'certificate.pfx';
  vKeyProvider.Password := 'secret';
  vKeyProvider.LoadFromFile;
end;

XAdES-B-T 또는 XAdES-B-LT 선택

B-T는 서명 타임스탬프를 추가해요. B-LT는 추가로 CA 체인과 폐기 데이터를 임베드하므로 서명이 수년 동안 검증 가능한 상태로 유지돼요.

프로파일 구성

Level은 AdES 수준을 선택해요: xalB, xalT, xalLT, xalLTA. PackagingxpkEnveloped(기본값), xpkEnveloping 또는 xpkDetached를 선택해요.

Transforms 목록은 기본적으로 xtEnvelopedSignature + xtC14NExclusive로 설정되며, 이것은 대부분의 전자 청구서 스키마에서 요구하는 것이에요. 특정 국가 프로파일이 포함 C14N 또는 사용자 정의 XSLT 변환을 요청할 때만 재정의하세요.

B-LT의 경우 OCSP.AutoFetch := True는 서명자에게 체인의 모든 인증서에 대해 OCSP 응답을 검색하여 RevocationValues 요소 내부에 임베드하도록 지시해요. 그러면 서명된 XML은 검증자가 필요로 하는 모든 것을 포함해요 — 검증 시점에 네트워크 호출이 필요하지 않아요.

step2-profile.pas
var
  vProfile: TsgcSignProfile_XAdES;
begin
  vProfile := TsgcSignProfile_XAdES.Create(nil);
  vProfile.Level := xalT;            // or xalLT, xalLTA
  vProfile.Packaging := xpkEnveloped;
  vProfile.HashAlgorithm := shaSHA256;

  // Timestamp authority for B-T and above
  vProfile.TSA.URL := 'https://freetsa.org/tsr';
  vProfile.TSA.HashAlgorithm := shaSHA256;

  // For B-LT: embed full chain and revocation data
  vProfile.OCSP.AutoFetch := True;
  vProfile.CRL.AutoFetch := True;
end;

서명 — 엔벨로프드, 엔벨로핑 및 디태치드

동일한 TsgcSignXML 컴포넌트가 세 가지 패키징 모드를 모두 처리해요 — Packaging 속성만 다를 뿐이에요.

엔벨로프드 서명

송장의 기본값이에요. 서명은 문서 루트 내부에 추가되며 엔벨로프드 서명 변환을 사용하여 다이제스트에서 자신을 제외해요.

스키마 인식 페이로드(FatturaPA, TicketBAI, KSeF)의 경우 RootNamespace를 설정하여 서명이 올바른 XML 네임스페이스를 상속하도록 하세요 — 국가 프로파일이 자동으로 이를 설정해요.

step3-enveloped.pas
var
  vSigner: TsgcSignXML;
  vSigned: string;
begin
  vSigner := TsgcSignXML.Create(nil);
  try
    vSigner.KeyProvider := vKeyProvider;
    vSigner.Profile := vProfile;          // Packaging = xpkEnveloped
    vSigned := vSigner.SignXML(vXML);
    TFile.WriteAllText('invoice-signed.xml', vSigned, TEncoding.UTF8);
  finally
    vSigner.Free;
  end;
end;

엔벨로핑 및 디태치드

프로파일의 패키징을 전환하고 다른 모든 것은 동일하게 유지하세요. 디태치드 서명의 경우 DetachedURI를 서명되는 문서의 경로 또는 URL로 설정하세요; 검증자는 데이터를 가져오기 위해 그 참조가 필요해요.

디태치드 서명이 다시 돌아오면 서명 XML과 원본 문서 바이트를 모두 VerifyDetached에 전달하세요 — sgcSign이 다이제스트를 다시 계산하고 바인딩을 확인해요.

step3-other.pas
// Enveloping: original XML wrapped in a ds:Object
vProfile.Packaging := xpkEnveloping;
vSigned := vSigner.SignXML(vXML);

// Detached: signature file points at invoice.xml
vProfile.Packaging := xpkDetached;
vProfile.DetachedURI := 'invoice.xml';
vSigned := vSigner.SignXML(vXML);
TFile.WriteAllText('invoice.sig.xml', vSigned, TEncoding.UTF8);

서명된 XML 검증하기

워크플로를 배포하기 전에 서명된 모든 문서를 검증해야 해요.

한 번의 호출, 전체 보고서

VerifyXML은 감지된 AdES 수준, 서명자 주체, 서명 타임스탬프 및 모든 체인 또는 폐기 문제를 포함한 TsgcSignatureReport를 반환해요. 엔벨로프드 서명의 경우 검증자가 ds:Signature 요소를 자동으로 찾아요; 디태치드 서명의 경우 VerifyDetached를 사용하고 원본 문서 바이트를 제공하세요.

StatussvValid이면 다이제스트가 일치하고, 인증서 체인이 신뢰할 수 있는 루트에 고정되며, 타임스탬프가 손상되지 않았어요. StatusDetail에 이유가 있는 svInvalid가 일반적인 실패 모드예요; svUnknown은 검증자가 OCSP 응답자 또는 CRL 배포 지점에 도달할 수 없음을 의미해요.

step4-verify.pas
var
  vVerifier: TsgcSignatureVerifier;
  vReport: TsgcSignatureReport;
begin
  vVerifier := TsgcSignatureVerifier.Create(nil);
  try
    vReport := vVerifier.VerifyXML(vSigned);
    Memo1.Lines.Add('Level:   ' + vReport.Signatures[0].LevelAsString);
    Memo1.Lines.Add('Status:  ' + vReport.Signatures[0].StatusAsString);
    Memo1.Lines.Add('Signer:  ' + vReport.Signatures[0].Subject);
    Memo1.Lines.Add('TSA:     ' +
      DateTimeToStr(vReport.Signatures[0].TimestampUTC));
  finally
    vVerifier.Free;
  end;
end;

일반적으로 잘못되는 것들

개발자가 첫 XAdES 엔벨로프에 서명할 때 가장 자주 보는 문제들이에요.

공백 및 정규화

서명된 XML을 텍스트 편집기, IDE에서 또는 XSLT를 통해 보기 좋게 출력하면 다이제스트가 깨져요. 독점 C14N은 모든 바이트에 민감해요. 서명된 XML을 바이트로 저장하고, 바이트로 전송하며, 절대로 다시 포맷하지 마세요.

입력 문서의 BOM

XML 시작 부분의 UTF-8 BOM은 다이제스트 불일치의 빈번한 원인이며, 특히 메모장으로 문서를 작성할 때 그래요. 서명하기 전에 BOM을 제거하세요 — sgcSign은 내부적으로 정규화하지만 다른 검증자는 그렇지 않을 수 있어요.

FatturaPA에는 xalLT가 필요해요

이탈리아 전자 청구서 포털은 아카이브 시간에 장기 폐기 조회를 수행할 수 없기 때문에 XAdES-B-T를 거부해요. OCSP.AutoFetch = True와 함께 xalLT를 사용하여 OCSP 응답이 문서와 함께 이동하도록 하세요.

디태치드 서명 및 URI 해석

DetachedURI가 상대 경로이면 검증자가 자체 작업 디렉터리에 대해 해석해요. 머신 간 흐름의 경우 절대 URL을 사용하거나 문서 바이트를 VerifyDetached에 직접 전달하세요.

여기서 어디로 갈까요

국가 프로파일이 XAdES 구성을 대신 처리해요. 서버는 빌드 팜 전체에 걸쳐 서명을 중앙 집중화해요.

국가 프로파일

VeriFactu, FatturaPA, KSeF, FACTUR-X, TicketBAI — 하나의 상수가 모든 변환, 알고리즘 및 필수 속성을 전환해요.

더 읽기 →

PAdES 튜토리얼

동일한 엔진이 XML 대신 PDF에 적용돼요. PAdES 워크스루를 읽고 두 형식을 나란히 비교하세요.

더 읽기 →

sgcSign Server

REST API, GitHub Actions 통합, Docker 및 Helm. 개별 개발자에서 제어되는 서비스로 서명을 오프로드하세요.

더 읽기 →

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

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