XML 서명·암호화·WS-Security로 메시지 레벨 보안 구현하기
XML Signature·XML Encryption·WS-Security 표준을 Python·Java 코드로 검증하고, XXE·Signature Wrapping 방어와 파서 하드닝 실무를 정리한다.
2026-08-13 · 최초 발행 2025-12-03
XML 문서 하나가 공격 벡터가 될 때
웹서비스, 전자문서, SAML 기반 인증처럼 XML이 경로에 끼어드는 곳이라면 어디든 기밀성·무결성·인증·부인방지를 확보해야 할 필요가 생긴다. XML Security는 이 요구를 메시지 레벨에서 해결하는 표준군과 안전 파싱 기법의 조합이다. TLS와는 독립적으로 XML 요소 단위 서명·암호화를 적용할 수 있어서, 중간 경유지(브로커·게이트웨이)를 거치거나 문서가 보관되는 동안에도 정책 기반 보호가 유지된다. 핵심 표준은 XML Signature, XML Encryption, WS-Security, XKMS이고, 실무에서는 XXE·Signature Wrapping·XPath 인젝션 같은 XML 특유의 공격면 대응이 필수다.
XML 보안 체계의 뼈대는 정규화(C14N), 네임스페이스 처리, 참조 URI/ID, 서명·암호화 알고리즘, 키·신뢰 관리, 안전 파서 설정으로 구성된다.
XML Signature: 서명이 지키는 것과 뚫리는 지점
XML Signature는 Enveloped, Enveloping, Detached 세 가지 서명 유형을 지원하고, 다중 참조와 변환(Transforms)·정규화(C14N)를 포함한다. 검증할 때는 참조 URI의 ID 정확성, ID 중복 금지, Transforms 검증, 타임스탬프·서명 시각 검증을 반드시 확인해야 한다. Signature Wrapping 공격은 고유 ID 인덱싱과 명시적 서명 대상 검증으로 막고, 알고리즘 약화 공격은 알고리즘 화이트리스트로 차단한다.
XML Encryption: 문서 전체가 아니라 필요한 부분만
암호화 범위는 주민번호·토큰 같은 민감 필드를 요소 단위로 선택 암호화하거나 문서 전체를 암호화하는 두 방식이 있다. 키 처리는 AES-GCM 같은 대칭키로 본문을 암호화하고 수신자 공개키로 대칭키를 래핑(Key Transport)하는 방식이 일반적이다. KeyInfo·X.509를 포함해 다중 수신자를 처리할 수 있고, 스트리밍 복호화 구현도 가능하다.
WS-Security: SOAP 메시지에 보안 헤더를 얹는다
Timestamp, BinarySecurityToken, Signature, Encryption 헤더로 정책 기반 보안을 적용하고, 서명→암호화 또는 암호화→서명 순서를 정책화하며 재생공격 방지를 위한 Nonce·TTL을 적용한다. WS-SecurityPolicy로 알고리즘·토큰·보호 대상 파트를 규정화하면 엔터프라이즈 통합에서 정책을 일괄 적용할 수 있다.
키·신뢰 관리와 파서 하드닝
X.509/PKI 신뢰 루트는 CA 체인 검증, CRL/OCSP를 통한 폐지 상태 확인, 키 롤오버 절차가 필요하다. XKMS는 키 조회·등록 API와 파트너 온보딩 자동화, 키 만료 알림 운영을 지원하고, 알고리즘 애질리티(RSA-PSS/ECDSA, AES-GCM, SHA-256 이상) 관리도 이 계층의 일이다.
안전 파싱·검증에서는 XXE 방지가 최우선이다. 외부 엔티티·DTD를 비활성화하고 안전 모드를 활성화하며 스키마 화이트리스트를 적용해야 한다. XSD로 구조·타입·제약을 검증하고, XPath 인젝션 방지를 위해 입력 이스케이프·바인딩을 적용한다. 메모리·노드 수·엔티티 확장을 제한해 DoS를 방지하는 것도 파서 하드닝의 일부다.
검증부터 응답까지의 처리 흐름
Python으로 안전 파싱과 서명 검증을
전제조건은 Python 3.10 이상, defusedxml>=0.7, lxml>=4.9, signxml>=3.2, 시스템에 OpenSSL이다. pip install defusedxml lxml signxml로 설치한다.
# 파일: verify_xmlsig.py
from defusedxml.lxml import fromstring
from signxml import XMLVerifier, methods
from lxml import etree
# 안전 파싱(XXE/DTD 차단)
parser = etree.XMLParser(resolve_entities=False, no_network=True, dtd_validation=False)
with open("signed.xml", "rb") as f:
xml_data = f.read()
root = etree.fromstring(xml_data, parser=parser)
# ID 속성 고유성 검증
ids = set()
for el in root.iter():
idv = el.get("ID") or el.get("Id") or el.get("id")
if idv:
if idv in ids:
raise ValueError("중복 ID 탐지")
ids.add(idv)
# 서명 검증(특정 요소만 대상으로 제한)
# 참고: cert.pem에는 파트너 공개키 인증서 포함
verified_data = XMLVerifier().verify(
root,
x509_cert=open("cert.pem", "rb").read(),
expect_references=1, # 참조 개수 기대값 제한
validate_schema=True # 표준 서명 스키마 검증
).signed_xml
print("서명 검증 성공")
실무 포인트는 expect_references와 ID 중복 검사로 Signature Wrapping을 저감하고, methods.SignatureMethod.RSA_SHA256 같은 알고리즘 화이트리스트를 구성하는 것이다.
Java에서 XXE 차단과 Apache Santuario 서명 검증
전제조건은 JDK 11 이상, org.apache.santuario:xmlsec:2.3.0, 최신 Xerces다. Maven 의존성은 다음과 같다.
<dependency>
<groupId>org.apache.santuario</groupId>
<artifactId>xmlsec</artifactId>
<version>2.3.0</version>
</dependency>
// 파일: VerifyXmlSig.java
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.XMLConstants;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.Init;
import java.io.File;
import java.security.PublicKey;
public class VerifyXmlSig {
public static void main(String[] args) throws Exception {
Init.init();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new File("signed.xml"));
// Signature 노드 선택(명시적 경로, ID 검증 정교화 권장)
NodeList sigs = doc.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature");
if (sigs.getLength() != 1) throw new RuntimeException("서명 개수 오류");
XMLSignature sig = new XMLSignature((org.w3c.dom.Element) sigs.item(0), "");
KeyInfo ki = sig.getKeyInfo();
if (ki == null) throw new RuntimeException("KeyInfo 누락");
PublicKey pk = ki.getPublicKey();
if (pk == null) throw new RuntimeException("공개키 누락");
boolean ok = sig.checkSignatureValue(pk);
if (!ok) throw new RuntimeException("서명 검증 실패");
System.out.println("서명 검증 성공");
}
}
DOCTYPE·외부 엔티티 차단으로 XXE를 방어하고, Signature 노드 선택은 애플리케이션 스키마·ID 바인딩과 함께 범위를 제한하는 것이 실무 포인트다.
xmlsec1 CLI로 서명·검증·암호화
전제조건은 xmlsec1 1.2.x, OpenSSL, 그리고 준비된 키·인증서다.
- 서명:
xmlsec1 --sign --privkey-pem privkey.pem,cert.pem --output signed.xml input.xml - 검증:
xmlsec1 --verify --pubkey-cert-pem cert.pem signed.xml - 암호화:
xmlsec1 --encrypt --pubkey-cert-pem cert.pem --session-key aes128 --output enc.xml input.xml
운영에서 지켜야 할 것들
파서 하드닝은 DTD·외부 엔티티 비활성화, 네트워크 접근 차단, 리소스 제한 설정에 스키마 검증과 허용 도메인·네임스페이스 화이트리스트를 얹는 것이다. 서명·암호화 정책은 알고리즘 화이트리스트(RSA-PSS/ECDSA, SHA-256 이상, AES-GCM)를 쓰고 RSA-SHA1·3DES 같은 약한 알고리즘은 금지하며, 서명 대상을 고정 ID·경로로 한정하고 참조 개수를 제한하며 중복 ID를 거부한다. 키·신뢰 수명주기는 키 롤오버 절차와 CRL/OCSP 점검, XKMS·메타데이터 기반 파트너 온보딩 자동화, 인증서 핀닝·정책 버전 관리, 테스트 환경 분리로 관리한다. 가시성·감사 측면에서는 감사 로그에 서명 결과·키 식별자(KID)·타임스탬프·정책 버전을 기록하고 민감 데이터는 마스킹하며 실패 사유를 과다 노출하지 않는다.
성능·운영 트레이드오프도 분명하다. DOM 대비 StAX 기반 스트리밍 처리는 메모리·지연을 개선하고, 메시지 레벨 보안은 TLS 대비 설정 복잡성이 늘어나는 대신 브로커 경유·보관 시에도 보안 경계를 유지하는 이점이 있다.
| 메커니즘 | 성능 | 확장성 | 일관성 | 안정성 | 운영 편의 |
|---|---|---|---|---|---|
| XML Signature | 중간(해시/서명 오버헤드) | 높음(요소 단위 선택) | 높음(무결성 담보) | 높음(표준 성숙) | 보통(키·정규화 관리 필요) |
| XML Encryption | 중간~높음(대칭암호 중심) | 높음(선택 암호화) | 중간(파트 선택에 의존) | 높음 | 보통(키 교환 필수) |
| WS-Security | 중간(헤더 처리 비용) | 높음(정책 기반) | 높음(정책 표준화) | 높음 | 보통~낮음(정책/상호운용 난도) |
| TLS(전송 보안) | 높음(하드웨어 가속 용이) | 높음 | 중간(중간 저장 시 한계) | 높음 | 높음(운용 단순) |
실무에서는 어디에 적용하나
B2B SOAP 연동에서는 주문·정산 문서에 서명·타임스탬프를 적용하고 민감 필드를 요소 단위로 암호화하며 파트너별 키 롤오버를 자동화한다. 전자정부 문서 교환에서는 문서 원본성(부인방지)을 보장하고 장기서명(LTV)·타임스탬프를 연계한다. 연합 인증(SAML)에서는 Assertion 서명을 검증하고 NameID·Attribute를 부분 암호화하며 리플레이를 방지한다. 금융·보험 ESB에서는 게이트웨이에서 WS-SecurityPolicy 준수를 검증하고 실패 트래픽을 샘플링·포렌식 저장한다. 임베디드·IoT에서는 설정 파일 요소를 암호화하고 경량 서명(ECDSA)을 적용하며 스트리밍 파서를 쓴다.
처리 절차는 파트너 인증서 메타데이터 동기화와 안전 파서 설정 로드를 입력으로 받아, 스키마 검증 → C14N → 서명 검증 → 복호화 → 정책 평가 순서로 처리한다. 오류는 파싱·스키마·서명·복호화·정책 실패로 코드별 분류해 감사 로깅과 경보를 트리거하고, 검증·복호화가 성공했을 때만 비즈니스 트랜잭션을 시작해 실패 시 롤백 일관성을 유지한다. 출력은 응답에 필요한 최소 범위만 서명·암호화하고 타임스탬프·만료(예: 5분)를 포함한다.
오버헤드는 얼마나 되는가
위·변조와 재생 공격을 줄이고 원본성 검증을 가능하게 하는 것이 보안 측면의 핵심 효과다. 필요한 필드만 최소 암호화하면 규제 준수를 강화하면서도 성능 부담을 줄일 수 있고, 파트너 간 표준 기반 상호운용성과 장애 분석 용이성이 함께 올라간다. 키 롤오버 자동화는 운영 리스크를 줄인다. 실제 성능 수치는 환경에 따라 다르지만 참고할 만하다 — RSA-2048 서명 검증 오버헤드는 1vCPU 기준 메시지당 약 315ms, AES-GCM 요소 암호화·복호화는 수 KB 페이로드에서 2ms 미만 수준이며, DOM 대신 StAX 스트리밍으로 전환하면 메모리 사용량이 5080% 절감된다.
표준을 쌓는 것과 파서를 하드닝하는 것은 별개다
XML Security의 핵심은 안전 파싱·정규화·서명/암호화·정책·키 수명주기를 체계적으로 결합하는 데 있다. 전송 보안과 병행해 메시지 레벨 보안을 도입하면 중간 저장이나 브로커 경유 시에도 보호 경계를 유지할 수 있다. 표준 기반 도입과 파서 하드닝, 알고리즘 화이트리스트, 키 롤오버 자동화를 중심으로 단계적으로 적용하는 편이 실무에서는 안전하다.