기고문2012. 10. 8. 11:37

본 내용은 월간 마이크로소프트웨어 2012년 12월호에 기고된 내용입니다 :)


--


전자세금계산서 제도는 사업자가 전자적 방법에 의해 세금계산서를 교부하고, 교부한 전자세금계산서를 정해진 기한 내 국세청에 전송하는 것으로서 종이세금계산서 사용에 따른 납세협력 비용을 줄이고, 거짓 세금계산서 발급 차단 등 세무거래 투명성 확보를 위해 지난 2010년부터 시행되었다. 전자세금계산서 발급은 법인사업자의 경우 2011년부터, 매출 10억 이상 개인사업자는 올해부터 의무화되었으며, 전송지연 또는 미전송 시 공급가액 0.1%~0.3%의 가산세가 부과되므로 사업자들의 면밀한 대비가 요구된다.



전자세금계산서 표준인증 


전자세금계산서를 발생하는 사용자는 자체적으로 시스템을 보유하고 발행하는 경우와 ASP(Application Service Provider) 서비스를 사용하는 경우로 구분될 수 있다. 자체적으로 사내의 ERP 시스템과 연동하는 전자의 경우와 전자세금계산서 ASP 사업자로 허가를 받기 위해서는 전자세금계산서 표준인증 과정을 성공적으로 통과하고 인증번호를 부여받아야만 한다. 전자세금계산서 ASP 사업자와 달리 ASP 사용자는 표준인증을 받을 필요가 없다. 전자세금계산서 표준인증은 정보통신산업진흥원 전자세금계산서 인증 시스템(http://www.taxcerti.or.kr/etax/)에 회원가입 후 법인 공인인증서 등록을 완료한 후에 검증 받을 수 있다. 


표준인증은 전자세금계산서 검증, 암호화된 전자세금계산서 검증, 전자세금계산서 제출 검증, 전자세금계산서 처리결과 응답 검증, 전자세금계산서 처리결과 요청 검증, 상호운용성 전자세금계산서 제출 및 처리결과 전송 종합 검증 그리고 상호운용성 전자세금계산서 결과 요청 검증 등 총 7개의 단계로 이루어져 있다. 모든 단계는 연속적으로 이루어져야하며 각 단계를 모두 성공해야 검증을 통과하게 된다. 따라서 각 단계 진행 중 하나라도 실패할 경우 검증은 통과하지 못하게 되며, 해당 단계에서 검증이 종료된다. 검증 실패 후 재 검증을 시도할 경우 1단계부터 다시 시작해야 한다. 


본 포스팅에서는 표준인증의 1단계인 전자세금계산서 전자서명 검증 부분만을 다루고 있으며, 표준인증 7단계에 대한 보다 상세한 정보는 정보통신산업진흥원 전자세금계산서 인증 시스템 게시판의 “전자세금계산서 개발지침 v1.0”을 통해 확인할 수 있다.




준비사항


표준인증의 1단계 전자세금계산서 전자서명 검증을 위해서는 먼저 JCE_Policy 파일을 패치해야 한다. JCE란 Java Cryptography Extension의 약자로서 자바 암호 구조(JCA : Java Cryptography Architecture)에서 지원하지 않는 대칭키 알고리즘과 암호화 구조를 위해 제공되는 확장 패키지이다. JDK 1.4.x부터 JCE가 기본적으로 포함이 되어 있는데 미국 수출 통상법에 따라 사용할 수 있는 키 길이 등에 제한이 걸려있다는 문제가 있다. 아래 그림과 같이 Oracle의 JAVA 다운로드 페이지에서 키 길이 등에 제한이 없는 정책파일을 다운로드 받고 압축을 해제하면 local_policy.jar, US_export_policy.jar 라는 2개의 파일이 있는데 이 파일들을 %JAVA_HOME%/lib/security 아래에 덮어씀으로써 문제를 해결할 수 있다. 만약 인증서의 비밀키 파일을 로드하는데 “There are bigger problems than just policy files: java.security.InvalidKeyException: Illegal key size or default parameters“와 같은 예외가 발생한다면 JCE_Policy 파일 패치가 제대로 이루어지지 않는 것이다.




전자세금계산서 전자문서 


전자세금계산서 XML 문서 예제는 다음와 같다.

<?xml version="1.0" encoding="UTF-8"?>
<TaxInvoice xsi:schemaLocation="urn:kr:or:kec:standard:Tax:ReusableAggregateBusinessInformationEntitySchemaModule:1:0 http://www.kec.or.kr/standard/Tax/TaxInvoiceSchemaModule_1.0.xsd" xmlns="urn:kr:or:kec:standard:Tax:ReusableAggregateBusinessInformationEntitySchemaModule:1:0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <ExchangedDocument>
    <ID>201208064100000100000001</ID>
    <IssueDateTime>20120806174653</IssueDateTime>
    <ReferencedDocument>
      <ID>201208064100000100000001</ID>
    </ReferencedDocument>
  </ExchangedDocument>
  <TaxInvoiceDocument>
    <IssueID>201208064100000100000001</IssueID>
    <TypeCode>0101</TypeCode>
    <IssueDateTime>20091005</IssueDateTime>
    <PurposeCode>01</PurposeCode>
  </TaxInvoiceDocument>
  <TaxInvoiceTradeSettlement>
    <InvoicerParty>
      <ID>2158757426</ID>
      <TypeCode>서비스</TypeCode>
      <NameText>(주)헬로월드</NameText>
      <ClassificationCode>정보처리</ClassificationCode>
      <SpecifiedPerson>
        <NameText>대표자명</NameText>
      </SpecifiedPerson>
      <DefinedContact>
        <DepartmentNameText>담당부서</DepartmentNameText>
        <PersonNameText>담당자이름</PersonNameText>
        <TelephoneCommunication>전화번호</TelephoneCommunication>
        <URICommunication>전자우편</URICommunication>
      </DefinedContact>
      <SpecifiedAddress>
        <LineOneText>서울시 강남구 삼성동</LineOneText>
      </SpecifiedAddress>
    </InvoicerParty>
    <InvoiceeParty>
      <ID>2158757426</ID>
      <TypeCode>서비스</TypeCode>
      <NameText>(주)헬로월드</NameText>
      <ClassificationCode>정보처리</ClassificationCode>
      <SpecifiedOrganization>
        <BusinessTypeCode>01</BusinessTypeCode>
      </SpecifiedOrganization>
      <SpecifiedPerson>
        <NameText>대표자명</NameText>
      </SpecifiedPerson>
      <PrimaryDefinedContact>
        <DepartmentNameText>담당부서</DepartmentNameText>
        <PersonNameText>담당자이름</PersonNameText>
        <TelephoneCommunication>전화번호</TelephoneCommunication>
        <URICommunication>전자우편</URICommunication>
      </PrimaryDefinedContact>
      <SecondaryDefinedContact>
        <DepartmentNameText>담당부서</DepartmentNameText>
        <PersonNameText>담당자이름</PersonNameText>
        <TelephoneCommunication>전화번호</TelephoneCommunication>
        <URICommunication>전자우편</URICommunication>
      </SecondaryDefinedContact>
      <SpecifiedAddress>
        <LineOneText>서울시 강남구 삼성동</LineOneText>
      </SpecifiedAddress>
    </InvoiceeParty>
    <SpecifiedPaymentMeans>
      <TypeCode>10</TypeCode>
      <PaidAmount>150000</PaidAmount>
    </SpecifiedPaymentMeans>
    <SpecifiedMonetarySummation>
      <ChargeTotalAmount>136364</ChargeTotalAmount>
      <TaxTotalAmount>13636</TaxTotalAmount>
      <GrandTotalAmount>150000</GrandTotalAmount>
    </SpecifiedMonetarySummation>
  </TaxInvoiceTradeSettlement>
  <TaxInvoiceTradeLineItem>
    <SequenceNumeric>01</SequenceNumeric>
    <InvoiceAmount>136364</InvoiceAmount>
    <ChargeableUnitQuantity>1</ChargeableUnitQuantity>
    <NameText>물품명</NameText>
    <PurchaseExpiryDateTime>20090928</PurchaseExpiryDateTime>
    <TotalTax>
      <CalculatedAmount>13636</CalculatedAmount>
    </TotalTax>
    <UnitPrice>
      <UnitAmount>150000</UnitAmount>
    </UnitPrice>
  </TaxInvoiceTradeLineItem>
</TaxInvoice>
각 항목은 위에서부터 순서대로 관리정보(ExchangedDocument), 기본정보(TaxInvoiceDocument), 계산서/거래처/결제 정보(TaxInvoiceTradeSettlement), 상품정보(TaxInvoiceTradeLineItem)를 담고 있다. 전자세금계산서에는 상당히 많은 양의 정보가 구체적으로 담기다 보니 XML문서가 크고 복합해 읽기 어려운 경향이 있는데, 여기서 눈여겨 볼 노드는 <TaxInvoiceDocument/Issue. ID>의 이다. 이 노드는 세금계산서 작성연월일 8자리 yyyymmdd, 국세청 등록번호 8자리 NNNNNNNN, 알파벳 소문자와 숫자 조합 8자리 ssssssss를 결합한 yyyymmddNNNNNNNNssssssss 형태의 24자리 문자열로서 전자세금계산서의 승인번호로 사용되며, 전자세금계산서를 식별할 수 있는 기본 키 이다.
위 예제 전자세금계산서 문서는 전자서명 부분을 포함하고 있지 않은데, 사업자 공인인증서를 통해 전자서명을 수행하면 관리정보(ExchangedDocument)와 기본정보(TaxInvoiceDocument) 사이에 전자서명 정보를 나타내는 <Signature> 노드가 추가된다. 전자서명을 수행할 때 주의해야 할 점은 전자세금계산서에서 공급사업자의 사업자번호를 의미하는 <InvoicerParty/ID>와 전자서명에 사용되는 사업자 공인인증서의 사업자번호가 일치해야 한다는 것이다. (전자세금계산서의 모든 항목에 대한 상세한 정의는 전자세금계산서 개발지침 v1.0의 부록A에서 확인할 수 있다.)


전자서명 

전자세금계산서의 법적 효력 발생을 위해서는 전자서명을 수행해야 한다. 전자서명의 방법은 W3C에서 권고하는 “XML Signature Syntax and Processing" 규약을 따른다. 사업자 및 국세청은 전자서명 시 법적 효력을 위해 공인인증기관에서 발급한 전자서명용 인증서만을 사용해야 하는 것에 주의한다. 전자서명의 전체 프로세스는 다음과 같다.


전자세금계산서 XML 문서는 가장먼저 정규화 과정을 거쳐야 한다. 왜냐하면 XML 문서가 논리적으로 동일하더라도 물리적으로는 띄어쓰기 방식이나 XML 주석, 문서 인코딩 방식 등의 차이로 인해 물리적으로는 다양하게 표현 가능하기 때문이다. 전자세금계산서 XML 문서의 전자서명은 XPath 필터링을 통해 전자서명에 사용되는 데이터를 전자세금계산서 XML 자체 내에서 추출하게 되는데, 논리적으로 동일하더라도 물리적으로 서로 다르게 표현된 XML 문서라면 전자서명 값이 서로 달라지므로 XML 문서의 물리적 표현 방식을 일정한 규칙 하에 통일하는 정규화 과정이 선행되어야 하는 것이다.

정규화 된 XML 문서는 전체 XML데이터 중에서 실제 서명 대상이 되는 데이터를 선택하기 위해 XPath 필터링을 수행한다. 하나의 전자서명 대상에 대해서 다양한 XPath 표현식이 존재할 수 있지만, 각 전자서명 모듈간 상호운용성 보장을 위해 아래 정의된 XPath 표현식만을 사용해야 한다.


위 과정을 통해 전자서명 대상 데이터를 추출하고, 해시(Digest)값을 생성한다. 해시 알고리즘으로는 공인인증서의 갱신/발급 기준일 에 따라 SHA-1 또는 SHA256를 사용한다. 생성 된 해시 값은 <ds:SignedInfo>노드 하위의 <ds:DigestValue>에 추가된다. 최종 전자서명은 <ds:SignedInfo>노드에 대해 해시값과 마찬가지로 공인인증서의 구분에 따라 전자서명 알고리즘 RSAWithSHA-1 또는 RSAWithSHA256를 통해 이루어진다. 전자서명값은 <ds:SignedInfo>노드 하위의 <ds:SignatureMethod>에 추가된다.

마지막으로 전자세금계산서 XML 문서를 수신하는 국세청에서 해당 전자서명의 유효성을 검증하기 위해 전자서명에 사용된 인증서가 필요하며 이에 따라 인증서 정보를 전자세금계산서 XML 문서에 추가해야 한다.

아래 소스코드는 전자세금계산서 전자서명 프로세스에 따라 Apache XML Security 라이브러리를 사용해 전자서명을 수행하는 Signer Class 이다.
import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import javax.xml.XMLConstants;

import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class Signer {

	private Document document;
	private PrivateKey privateKey;
	private X509Certificate x509Cert;
	
	/**
	 * @param document 전자세금계산서 XML 문서
	 * @param privateKey PKCS#8 개인키
	 * @param x509Cert X.509 공인인증서
	 */
	public Signer(Document document, PrivateKey privateKey, X509Certificate x509Cert) {
		this.document = document;
		this.privateKey = privateKey;
		this.x509Cert = x509Cert;
	}

	public Document doSign() throws XMLSecurityException {
		// ROOT 노드
		NodeList nodeList = document.getElementsByTagName("TaxInvoice"); 
		Element root = (Element)nodeList.item(0);
		
		// SignatureMethodURI - the Signature method to be used.
		// CanonicalizationMethodURI - the canonicalization algorithm to be used to c14nize the SignedInfo element.
		XMLSignature sig = new XMLSignature(document, 
				XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
				"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
				"http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
		
		// TaxInvoiceDocument
		nodeList = document.getElementsByTagName("TaxInvoiceDocument");
		Element taxInvoiceDocElement = (Element)nodeList.item(0);
				
		// ExchangedDocument와 TaxInvoiceDocument 사이에 Signature를 삽입
		root.insertBefore(sig.getElement(), taxInvoiceDocElement);
		
		Transforms transforms = new Transforms(document);
		
		// 정규화
		transforms.addTransform("http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
		
		// XPath 필터링
		String xPath = "not(self::*[name() = 'TaxInvoice'] | ancestor-or-self::*[name() = 'ExchangedDocument'] | ancestor-or-self::ds:Signature)";
		Element xpath = document.createElementNS("http://www.w3.org/2000/09/xmldsig#", "XPath");
		xpath.appendChild(document.createTextNode(xPath));
		xpath.setPrefix("ds");
		
		transforms.addTransform("http://www.w3.org/TR/1999/REC-xpath-19991116", xpath);
		
		// referenceURI - URI according to the XML Signature specification.
		// trans - List of transformations to be applied.
		// digestURI - URI of the digest algorithm to be used.
		sig.addDocument("", transforms, "http://www.w3.org/2000/09/xmldsig#sha1");
		
		sig.addKeyInfo(x509Cert);
		sig.sign(privateKey); 
		
		return document;
	}

	
}

공인인증서의 종류에는 PEM, DER, PKCS#12 가 있는데 위 소스코드의 예제에서 사용되는 공인인증서는 DER (Distingulished Encoding Rules) 형식의 공인인증서이다. DER형식의 공인인증서는 *.der, *.key라는 2개의 파일이 있는데 der가 공인인증서이며 key는 개인키 파일이다. 공인인증서에서 공개키를 가져오는 것은 다음과 같이 간단하게 처리할 수 있다.

public static X509Certificate readX509Cert(String filePath) throws Exception {
	return (X509Certificate) 
		CertificateFactory.getInstance("X.509")
			.generateCertificate(
				FileUtils.openInputStream(new File(filePath)));
}
개인키를 가져오는 과정은 꽤 복합한데 이는 우리나라에서 사용되는 공인인증서는 개인키를 SEED 블록 암호화 알고리즘을 통해 암호화하기 때문에 이를 복호화해서 개인키를 구해야 하기 때문이다. 또한 SEED 블록 암호화 알고리즘에 사용되는 OID(Object IDentifier)는 초기벡터 IV 의 생성 방식에 따라 2가지로 구분되며 이를 모두 처리해줘야 한다. 
OID는 개인키 암호화에 사용된 알고리즘 식별자 이며, OID에 대한 자세한 정보는 http://www.oid-info.com/cgi-bin/display 에서 확인할 수 있다. 또한 SEED 블록 암호화에 대한 정의는 한국정보보호진흥원의 암호 알고리즘 규격 KCAC.TS.ENC를 참고하도록 한다.

다음은 *.key에서 암호화된 개인키를 가져오는 TrustCertAndKey class 이다.
// *.der 파일의 공인인증서는 *.key 형태의 개인키 파일이 따로 존재한다.
// *.key 파일에는 SEED 암호화 알고리즘을 통해 암호화된 개인키를 저장하고 있기 때문에 이를 복호화해서 개인키를 구해야 한다.
// 현재 우리나라 전자서명인증체계에서 SEED 블럭 암호화 알고리즘에 사용되는 OID는 초기 벡터(IV)의 생성 방식에 따라 2가지로 구분할 수 있다.
// 한국정보보호진흥원(http://www.rootca.or.kr/)의 암호 알고리즘 규격(KCAC.TS.ENC)
public static PrivateKey readPrivateKey(String filePath, String passwd) throws Exception {
	byte[] encodedKey = FileUtils.readFileToByteArray(new File(filePath));
	EncryptedPrivateKeyInfo epki = new EncryptedPrivateKeyInfo(encodedKey);
		
	String OID = epki.getAlgName();
		
	if( "1.2.410.200004.1.15".equals(OID) ) { // Key Generation with SHA1 and Encryption with SEED CBC mode
		// 추출키(DK) 생성
		// 8바이트의 salt와 이터레이션카운트 를 선탯하여 PBKDF1를 통해 DK를 생성한다.
		// salt는 공인 인증서의 21~28바이트 사이의 8바이트를 사용하며, 이터레이션 카운트는 31~32바이트의 2바이트이며 해쉬 함수의 반복 횟수를 나타낸다.
		byte[] salt = new byte[8];
		System.arraycopy(encodedKey, 20, salt, 0, 8);

		byte[] cBytes = new byte[4];
		System.arraycopy(encodedKey, 30, cBytes, 2, 2);
		ByteBuffer buffer = ByteBuffer.wrap(cBytes);
		buffer.order(ByteOrder.BIG_ENDIAN);
		
		int iterationCount = buffer.getInt();

		// Password Based Key Derivation Function, 패스워드 기반의 키 추출 함수
		// PBKDF1 RFC2898(http://www.ietf.org/rfc/rfc2898.txt) 참조
		byte[] derivedKey = new byte[20];
		MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
		md.update(passwd.getBytes());
		md.update(salt);
		derivedKey = md.digest();
		for(int i=1; i<iterationCount; i++) {
			derivedKey = md.digest(derivedKey);
		}

		byte key[] = new byte[16];
		byte iv[] = new byte[16];
		byte ivTemp[] = new byte[4];
		byte derivedIV[] = new byte[20];
		
		// DK에서 처음 16바이트를 암호화 키(K)로 사용
	        System.arraycopy(derivedKey, 0, key, 0, 16);
	        
	        // DK에서 암호화 키(K)를 제외한 나머지 4바이트를 SHA-1으로 해쉬하여 20바이트의 값(DIV)를 생성하고, 그중 처음 16바이트를 초기 벡터(IV)로 사용
	        System.arraycopy(derivedKey, 16, ivTemp, 0, 4);
	        MessageDigest sha1 = MessageDigest.getInstance("SHA-1", "BC");
	        derivedIV = sha1.digest(ivTemp);
	        System.arraycopy(derivedIV, 0, iv, 0, 16);
	        
	        IvParameterSpec ivSpec = new IvParameterSpec(iv);
	        SecretKeySpec secKey = new SecretKeySpec(key, "SEED");
	        
	        Cipher cipher = Cipher.getInstance("SEED/CBC/PKCS5Padding", "BC");
	        cipher.init(Cipher.DECRYPT_MODE , secKey, ivSpec);
	        
	        byte[] decryptedKey = cipher.doFinal(epki.getEncryptedData());
	        
	        PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(decryptedKey);
		KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
		return kf.generatePrivate(ks);
	}
	else if( "1.2.410.200004.1.4".equals(OID) ) { // SEED Encryption (CBC mode) 
		byte[] salt = new byte[8];
		System.arraycopy(encodedKey, 20, salt, 0, 8);

		byte[] cBytes = new byte[4];
		System.arraycopy(encodedKey, 30, cBytes, 2, 2);
		ByteBuffer buffer = ByteBuffer.wrap(cBytes);
		buffer.order(ByteOrder.BIG_ENDIAN);
		
		int iterationCount = buffer.getInt();
			
		byte[] derivedKey = new byte[20];
		MessageDigest md = MessageDigest.getInstance("SHA1", "BC");
		md.update(passwd.getBytes());
		md.update(salt);
		derivedKey = md.digest();
		for(int i=1; i<iterationCount; i++) {
			derivedKey = md.digest(derivedKey);
		}
			
		byte key[] = new byte[16];
		System.arraycopy(derivedKey, 0, key, 0, 16);
			
		// 추출키(DK) 와 상관없이 16 바이트의 초기 벡터(IV) 는 아래와 같은값 으로 표현 으로 고정하여 사용한다.
		// IV 문자열 값은 “0123456789012345”
		byte iv[] = { 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48, 49, 50, 51, 52, 53 };
			
		IvParameterSpec ivSpec = new IvParameterSpec(iv);
        	SecretKeySpec secKey = new SecretKeySpec(key, "SEED");
	        
        	Cipher cipher = Cipher.getInstance("SEED/CBC/PKCS5Padding", "BC");
        	cipher.init(Cipher.DECRYPT_MODE , secKey, ivSpec);
	        
        	byte[] decryptedKey = cipher.doFinal(epki.getEncryptedData());
	        
        	PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(decryptedKey);
		KeyFactory kf = KeyFactory.getInstance("RSA", "BC");
		return kf.generatePrivate(ks);
	}
		
	throw new Exception("not support OID: "+OID);
}

자 이제 공인인증서 파일, 개인키 파일, 개인키 비밀번호, 전자세금계산서 XML 파일, 전자서명 된 전자세금계산서 XML 파일 출력 경로를 프로그램 실행 인자로 하는 main 함수의 구현은 다음과 같이 작성한다. 각종 암복호화 알고리즘을 제공해주는 BouncyCastle 라이브러리 추가와 Apache XML Security를 초기화 하는 것에 주의한다.

import java.io.File;
import java.io.FileOutputStream;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.X509Certificate;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.apache.log4j.Logger;
import org.apache.xml.security.utils.XMLUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.w3c.dom.Document;

import com.hellowd.etax.Signer;
import com.hellowd.etax.TrustCertAndKey;

public class TaxInvoiceSignerWrapper {

	public static Logger logger = Logger.getLogger(TaxInvoiceSignerWrapper.class);
	
	public static Document readXml(File source) throws Exception {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		dbf.setNamespaceAware(true);
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document document = db.parse(source);
		return document;
	}
	
	public static void main(String[] args) throws Exception {
		Options options = new Options();
		options.addOption("d", "der", true, "der file");// DER암호화 인증서 파일
		options.addOption("k", "key", true, "private key file");	// 인증서PKCS#8 Key
		options.addOption("p", "passwd", true, "key password"); // 인증서 비밀번호
		options.addOption("s", "source", true, "tax invoice xml file");
		options.addOption("o", "out", true, "signed tax invoice xml file");
		
		CommandLineParser parser = new PosixParser();
		CommandLine cmd = parser.parse(options, args);
		
		if( !cmd.hasOption("d") || !cmd.hasOption("k") || !cmd.hasOption("p") ) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("Help...", options);
			return;
		}
		
		String derFile = cmd.getOptionValue("d");
		String keyFile = cmd.getOptionValue("k");
		String passwd = cmd.getOptionValue("p");
		String source = cmd.getOptionValue("s");
		String output = cmd.getOptionValue("o");
		
		logger.debug("Der File Path: "+derFile);
		logger.debug("Key File Path: "+keyFile);
		logger.debug("Key Password: "+passwd);
		logger.debug("taxinvoice xml file: "+source);
		logger.debug("signed taxinvoice xml file: "+output);
		
		// BouncyCastle을 프로바이더로 추가
		Security.addProvider(new BouncyCastleProvider());
		// Apaceh Xml Security 초기화
		org.apache.xml.security.Init.init();
		
		X509Certificate x509Cert = TrustCertAndKey.readX509Cert(derFile);
		PrivateKey privateKey = TrustCertAndKey.readPrivateKey(keyFile, passwd);
		
		logger.debug(x509Cert);
		logger.debug(x509Cert.getSigAlgOID()); // 1.2.840.113549.1.1.5(RSA (PKCS #1 v1.5) with SHA-1 signature)
		
		logger.debug(privateKey);
		
		Document document = readXml(new File(source));
		
		Signer signer = new Signer(document, privateKey, x509Cert);
		signer.doSign();
		
		FileOutputStream os = new FileOutputStream(new File(output));
		XMLUtils.outputDOM(document, os);
	}
}

전자서명 검증

전자서명 된 전자세금계산서 XML 문서 예제는 다음과 같다.
<TaxInvoice xmlns="urn:kr:or:kec:standard:Tax:ReusableAggregateBusinessInformationEntitySchemaModule:1:0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:kr:or:kec:standard:Tax:ReusableAggregateBusinessInformationEntitySchemaModule:1:0 http://www.kec.or.kr/standard/Tax/TaxInvoiceSchemaModule_1.0.xsd">
  <ExchangedDocument>
    <ID>201208064100000100000001</ID>
    <IssueDateTime>20120806174653</IssueDateTime>
    <ReferencedDocument>
      <ID>201208064100000100000001</ID>
    </ReferencedDocument>
  </ExchangedDocument>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:Transform>
<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<ds:XPath>not(self::*[name() = 'TaxInvoice'] | ancestor-or-self::*[name() = 'ExchangedDocument'] | ancestor-or-self::ds:Signature)</ds:XPath>
</ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
<ds:DigestValue>S8F6mYpe6roBxGzZRZlqyOtKjjk=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
MMiM0czp9gOJRu+l1L7wxkvGKcGcC5CzFKwz2PWbW4s86kkv2hm2XtzXH5qG5XvI3fcxxG/3M8nr
ndYu9oOq4YMHRRS7exWhTAsfMZGaS2eUh7T+emI4GZh2xlBXPFP6Dlor+HUL+JNxc0WyhCZNlrSm
JM6k+GkYXGCmg/Z7QRM=
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIFIzCCBAugAwIBAgIEDrFIfjANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJrcjEQMA4GA1UE
CgwHeWVzc2lnbjEVMBMGA1UECwwMQWNjcmVkaXRlZENBMRIwEAYDVQQDDAl5ZXNzaWduQ0EwHhcN
MTEwODMxMTUwMDAwWhcNMTIwOTAxMTQ1OTU5WjCBizELMAkGA1UEBhMCa3IxEDAOBgNVBAoMB3ll
c3NpZ24xEzARBgNVBAsMCnhVc2U0RXNlcm8xDDAKBgNVBAsMA0lCSzEOMAwGA1UECwwFMjY4NDQx
NzA1BgNVBAMMLijso7wp7Zes66Gc7JuU65OcKDI2ODQ0KTAwMDM2ODIyMDExMDkwMTA5MzkxNDAw
gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANngCizCg4RYkgb9Leio4tn0OIj7kjZ461xbrPOS
exAufiULx9gxBdymWegsrrz8gof7lY1udZKn852aZNmdm1wxSGnqeA9OvZDiQ7cJB2Hj0vb103C1
nIqmpVgHm0D5gD8+aCu6xHHC9JcEv5U5L43VjgQgN2Jv78YQ325FJLo1AgMBAAGjggJRMIICTTCB
jwYDVR0jBIGHMIGEgBRK+70zLYux0YyUa//gQjZfHJHLCKFopGYwZDELMAkGA1UEBhMCS1IxDTAL
BgNVBAoMBEtJU0ExLjAsBgNVBAsMJUtvcmVhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENlbnRy
YWwxFjAUBgNVBAMMDUtJU0EgUm9vdENBIDGCAidgMB0GA1UdDgQWBBS++Ex9zPIVCl5hlf3dqEfD
VVQTnzAOBgNVHQ8BAf8EBAMCBsAwegYDVR0gAQH/BHAwbjBsBgoqgxqMmkUBAQYIMF4wLgYIKwYB
BQUHAgIwIh4gx3QAIMd4yZ3BHLKUACCs9cd4x3jJncEcACDHhbLIsuQwLAYIKwYBBQUHAgEWIGh0
dHA6Ly93d3cueWVzc2lnbi5vci5rci9jcHMuaHRtMGAGA1UdEQRZMFegVQYJKoMajJpECgEBoEgw
RgwRKOyjvCntl6zroZzsm5Trk5wwMTAvBgoqgxqMmkQKAQEBMCEwBwYFKw4DAhqgFgQUdai0Vxm/
JQUlCgBHXM62/dmCvnkwcgYDVR0fBGswaTBnoGWgY4ZhbGRhcDovL2RzLnllc3NpZ24ub3Iua3I6
Mzg5L291PWRwM3A2MjgwOSxvdT1BY2NyZWRpdGVkQ0Esbz15ZXNzaWduLGM9a3I/Y2VydGlmaWNh
dGVSZXZvY2F0aW9uTGlzdDA4BggrBgEFBQcBAQQsMCowKAYIKwYBBQUHMAGGHGh0dHA6Ly9vY3Nw
Lnllc3NpZ24ub3JnOjQ2MTIwDQYJKoZIhvcNAQEFBQADggEBAGwj1RRBqQgU27sO9lCUHce2TOs1
76n0hn5oxrwgyUE/tEqm+UjOvbAX5M1/10oICzze4InfHIuuctgDuGDOP3AN/7iTcVXicBX1r/yQ
pfm4Sr9aawN8MajL5ERhzZXJ9VULo4wZrCR5+ClkthbbqLQvebPax/u5WNgoq98b+/T6fuaCepkx
KswyncT6RUYj06xDydp9dxbsiaJdZKuEvRRyIuJ7ubFuBKRNNLxKT5FCImH4GwsLpNYe63KGX8Ag
+EWgkptQbSximgR8BGDWR0YzQCj1I6lANd99vtr6YNETQo9zyeryilDE7jaRjeo0j8uRZ1tKhqrn
X3IueQP15VM=
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature><TaxInvoiceDocument>
    <IssueID>201208064100000100000001</IssueID>
    <TypeCode>0101</TypeCode>
    <IssueDateTime>20091005</IssueDateTime>
    <PurposeCode>01</PurposeCode>
  </TaxInvoiceDocument>
  <TaxInvoiceTradeSettlement>
    <InvoicerParty>
      <ID>2158757426</ID>
      <TypeCode>서비스</TypeCode>
      <NameText>(주)헬로월드</NameText>
      <ClassificationCode>정보처리</ClassificationCode>
      <SpecifiedPerson>
        <NameText>대표자명</NameText>
      </SpecifiedPerson>
      <DefinedContact>
        <DepartmentNameText>담당부서</DepartmentNameText>
        <PersonNameText>담당자이름</PersonNameText>
        <TelephoneCommunication>전화번호</TelephoneCommunication>
        <URICommunication>전자우편</URICommunication>
      </DefinedContact>
      <SpecifiedAddress>
        <LineOneText>서울시 강남구 삼성동</LineOneText>
      </SpecifiedAddress>
    </InvoicerParty>
    <InvoiceeParty>
      <ID>2158757426</ID>
      <TypeCode>서비스</TypeCode>
      <NameText>(주)헬로월드</NameText>
      <ClassificationCode>정보처리</ClassificationCode>
      <SpecifiedOrganization>
        <BusinessTypeCode>01</BusinessTypeCode>
      </SpecifiedOrganization>
      <SpecifiedPerson>
        <NameText>대표자명</NameText>
      </SpecifiedPerson>
      <PrimaryDefinedContact>
        <DepartmentNameText>담당부서</DepartmentNameText>
        <PersonNameText>담당자이름</PersonNameText>
        <TelephoneCommunication>전화번호</TelephoneCommunication>
        <URICommunication>전자우편</URICommunication>
      </PrimaryDefinedContact>
      <SecondaryDefinedContact>
        <DepartmentNameText>담당부서</DepartmentNameText>
        <PersonNameText>담당자이름</PersonNameText>
        <TelephoneCommunication>전화번호</TelephoneCommunication>
        <URICommunication>전자우편</URICommunication>
      </SecondaryDefinedContact>
      <SpecifiedAddress>
        <LineOneText>서울시 강남구 삼성동</LineOneText>
      </SpecifiedAddress>
    </InvoiceeParty>
    <SpecifiedPaymentMeans>
      <TypeCode>10</TypeCode>
      <PaidAmount>150000</PaidAmount>
    </SpecifiedPaymentMeans>
    <SpecifiedMonetarySummation>
      <ChargeTotalAmount>136364</ChargeTotalAmount>
      <TaxTotalAmount>13636</TaxTotalAmount>
      <GrandTotalAmount>150000</GrandTotalAmount>
    </SpecifiedMonetarySummation>
  </TaxInvoiceTradeSettlement>
  <TaxInvoiceTradeLineItem>
    <SequenceNumeric>01</SequenceNumeric>
    <InvoiceAmount>136364</InvoiceAmount>
    <ChargeableUnitQuantity>1</ChargeableUnitQuantity>
    <NameText>물품명</NameText>
    <PurchaseExpiryDateTime>20090928</PurchaseExpiryDateTime>
    <TotalTax>
      <CalculatedAmount>13636</CalculatedAmount>
    </TotalTax>
    <UnitPrice>
      <UnitAmount>150000</UnitAmount>
    </UnitPrice>
  </TaxInvoiceTradeLineItem>
</TaxInvoice>

전자서명 된 전자세금계산서 XML 문서의 유효성을 확인하기 위해 앞서 언급한 정보통신산업진흥원 전자세금계산서 인증 시스템(http://www.taxcerti.or.kr/etax/)에서 전자세금계산서 단위 검증을 수행해 볼 수 있다. 전자서명 과정에 문제가 없다면 다음과 같이 모든 테스트 케이스를 성공한 것을 확인할 수 있다. (단위검증은 회원가입 후 법인 공인인증서 등록을 하지 않아도 수행해 볼 수 있다.)



* 첨부

1. taxInvoiceSigner.zip 예제 소스 코드

2. 전자세금계산서 예제 TaxInvoice.xml

3. 전자서명 된 전자세금계산서 예제 TaxInvoice_Signed.xml


taxInvoiceSigner.zip


taxInvoice.xml


taxInvoice_signed.xml



* 참고문헌 및 자료

1. 전자세금계산서 개발지침 v1.0

2. 한국인터넷진흥원 암호 알고리즘 규격 KCAC.TS.ENC v1.21

3. 공인인증서로 전자서명 해보기 http://blog.kangwoo.kr/49

4. Apache XML Security http://santuario.apache.org/

5. BouncyCastle http://www.bouncycastle.org/

6. PBKDF1 RFC2898 http://www.ietf.org/rfc/rfc2898.txt

Posted by devop

댓글을 달아 주세요

프로그래밍/JAVA2010. 3. 17. 15:28
다음과 같은 XML 파일을 Apache Xml Security를 이용하여 Eveloped-Signature를 생성하는 과정은 다음과 같다.


1. 원본 XML의 내용
<?xml version="1.0" encoding="UTF-8"?>
<Book>
	<Title>XML 전자서명</Title>
	<Author>이연복</Author>
	<Price>10000</Price>
	<Publisher>SNUT</Publisher>
</Book>

2. 전자서명 코드
package org.sopt.dev;

import java.security.PrivateKey;
import java.security.cert.X509Certificate;

import javax.xml.XMLConstants;

import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

public class XMLSigner {

	private Document document = null;
	
	private PrivateKey privatekey = null;
	
	private X509Certificate x509Cert = null;
	
	public XMLSigner(Document document, PrivateKey privatekey, X509Certificate x509Cert) {
		this.document = document;
		this.privatekey = privatekey;
		this.x509Cert = x509Cert;
	}
	
	public Document sign() throws XMLSecurityException {
		XMLSignature sig = new XMLSignature(document, 
				XMLConstants.XMLNS_ATTRIBUTE_NS_URI,
				"http://www.w3.org/2000/09/xmldsig#rsa-sha1",
				"http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
		
		NodeList nodeList = document.getElementsByTagName("Book");
		Element bookElement = (Element)nodeList.item(0);
		nodeList = document.getElementsByTagName("Title");
		bookElement.insertBefore(sig.getElement(), (Element)nodeList.item(0));
		
		Transforms transforms = new Transforms(document);
		transforms.addTransform("http://www.w3.org/2000/09/xmldsig#enveloped-signature");
		transforms.addTransform("http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
		sig.addDocument("", transforms, "http://www.w3.org/2000/09/xmldsig#sha1");
		
		sig.addKeyInfo(x509Cert);
		sig.sign(privatekey); 
		
		return document;
	}
	
}

3. 전자서명 결과
<?xml version="1.0" encoding="UTF-8"?>
<Book>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:CanonicalizationMethod>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"></ds:SignatureMethod>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>
<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:Transform>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod>
<ds:DigestValue>eJGWkV4mDEzNaQebWXTmAGFP20Q=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
vURLd5SJnYi85UDMGutu9Czmh4SuxR56iC7Wv5znKlev0ubUCqhvZrN+9mp3H10IaW1ZJxaGu0V1
/Rg576FwVR1GVr6d3at2SLkbUZDN2DdtNPYIvBGlmRNVmdSDtDB6QhG1lhv3WmStI6Huo4lk75Ig
yyDz5PJy8OlH+R5nk+k=
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
MIIFSTCCBDGgAwIBAgIECkDV5DANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJrcjEQMA4GA1UE
CgwHeWVzc2lnbjEVMBMGA1UECwwMQWNjcmVkaXRlZENBMRIwEAYDVQQDDAl5ZXNzaWduQ0EwHhcN
MDkxMTE3MTUwMDAwWhcNMTAxMTE4MTQ1OTU5WjCBqTELMAkGA1UEBhMCa3IxEDAOBgNVBAoMB3ll
c3NpZ24xEzARBgNVBAsMCnhVc2U0RXNlcm8xDDAKBgNVBAsMA0tNQjEcMBoGA1UECwwTU0FNSlVO
RyBEQVRFU0VSVklDRTFHMEUGA1UEAww+7IK87KCV642w7J207YOA7ISc67mE7IqkKChTQU1KVU5H
IERBVEVTRVJWSUNFKTAwMDQ2ODg3MDA0MDY2NDMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB
AMHbMPCKVTKMVkZe4s1tW/wTDzqijdU7Oe2rGlfOuKNcdAjDxkLj77IOgLou/DIwJ0imN6xWcfd1
1stHDfl3Yi6Ehz91gaQCXENXhyhsOFP7Gie06emxstSE3tn725+U6A2w1s7Ty311k0l7U4Bf1LNJ
Db8Imp5XAaPWg+zk2U7LAgMBAAGjggJZMIICVTCBjwYDVR0jBIGHMIGEgBRK+70zLYux0YyUa//g
QjZfHJHLCKFopGYwZDELMAkGA1UEBhMCS1IxDTALBgNVBAoMBEtJU0ExLjAsBgNVBAsMJUtvcmVh
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IENlbnRyYWwxFjAUBgNVBAMMDUtJU0EgUm9vdENBIDGC
AidgMB0GA1UdDgQWBBTH8Zj4a7Qm0qa80rc6wvATAZ694jAOBgNVHQ8BAf8EBAMCBsAwegYDVR0g
AQH/BHAwbjBsBgoqgxqMmkUBAQYIMF4wLgYIKwYBBQUHAgIwIh4gx3QAIMd4yZ3BHLKUACCs9cd4
x3jJncEcACDHhbLIsuQwLAYIKwYBBQUHAgEWIGh0dHA6Ly93d3cueWVzc2lnbi5vci5rci9jcHMu
aHRtMGgGA1UdEQRhMF+gXQYJKoMajJpECgEBoFAwTgwZ7IK87KCV642w7J207YOA7ISc67mE7Iqk
KDAxMC8GCiqDGoyaRAoBAQEwITAHBgUrDgMCGqAWBBSlWn1wqt/tN4hKoAVB0RQKp7gj/jByBgNV
HR8EazBpMGegZaBjhmFsZGFwOi8vZHMueWVzc2lnbi5vci5rcjozODkvb3U9ZHAzcDM3OTgzLG91
PUFjY3JlZGl0ZWRDQSxvPXllc3NpZ24sYz1rcj9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0MDgG
CCsGAQUFBwEBBCwwKjAoBggrBgEFBQcwAYYcaHR0cDovL29jc3AueWVzc2lnbi5vcmc6NDYxMjAN
BgkqhkiG9w0BAQUFAAOCAQEAB9Im0fBWHNmU8ROrlGnsAPINOvBpOu1IG7CSfxgsrSqW4DVnjuT+
+ShEccR0+IpHuBDp1a/IYzWMln69Fh7Uso+Ac60btXoPKJh8+HtlkQfaVgfdAk2zihpuB05Gg1p2
qJIk6wQgkAgreDRYMCKz00je7DD2v4tNfBmslJ4CkMXO3IVHdyudoHChDoLjuYBWDKu0JIlUiO0m
ydvyKT48SrVgNxVCA52BOsTc5XDT0f/lVRIDQ90KYA6UA1l8oZ5P6GEIwWNDnH97NGblobyhoFfT
s5ERFjKsZjygZxAdQroBWH8TNFZvLxMoOionBy/A3SGyMkmrWcr+rb/u1wC5YA==
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>

	<Title>XML 전자서명</Title>
	<Author>이연복</Author>
	<Price>10000</Price>
	<Publisher>SNUT</Publisher>
</Book>
위 코드는 입력된 XML파일의 모든 노드셋을 대상으로 c14n 정규화 알고리즘을 적용한 후 sha1 알고리즘으로 digest value를 생성한다. 이후 CanonicalizationMethod와 SignatureMethod에 명시된 알고리즘에 따라 SignedInfo를 정규화하고, 서명을 수행한다.

 KeyInfo 노드는 서명을 제3 자가 서명을 검증할수 있도록 검증키(공개키)를 포함한다.

XMLSigner의 privatekey와 x509Cert는 각각 공인인증서의 개인키(signPri.key)와 공개키(signCert.der)이다.

'프로그래밍 > JAVA' 카테고리의 다른 글

Jersey와 Spring으로 RESTful 웹 서비스 구축하기  (0) 2011.02.14
Javadoc  (1) 2010.04.30
이클립스에서 JUnit 사용하기  (0) 2010.04.22
Java 어노테이션(Annotation)  (0) 2010.04.21
Java Decompiler jad  (2) 2010.03.17
Apache Xml Security을 이용한 XML 전자서명  (1) 2010.03.17
MSSQL with JDBC  (0) 2010.02.03
JavaMail 첨부파일 읽기  (0) 2010.01.28
JavaMail with IMAP  (0) 2010.01.27
JavaMail을 이용하며 메일전송  (0) 2010.01.15
RSA 암호화  (0) 2010.01.15
Posted by devop

댓글을 달아 주세요

  1. 잘 보고 갑니다.

    2012.04.18 17:25 신고 [ ADDR : EDIT/ DEL : REPLY ]

프로그래밍/기타2009. 11. 6. 11:24
1. 전자서명이란?
펜과 종이로 쓰여진 문서와 같이 전자 서명은 데이터를 읽는 어느 누구에게도 그 데이터의 저작자를 보장한다. 전자 서명은 암호학에서 말하는 '인증(authentication)'을 제공하며, 또한 자료의 내용이 저작자가 확인한 것과 정확하게 같은지를 확인한다(추가 되거나 제거되지 않음). 암호학에서 이것은 '무결성(integrity)'이라고 한다.

보다 자세한 정의는 다음을 참조한다.


2. 전자서명 절차
전자 서명을 실행하는 데에는 두 단계가 있다. 첫 번째로 데이터는 해시 알고리즘 (hashing algorithm)을 통해 구동된다. 전형적인 해시 알고리즘은 자료를 스캔하여 어느 정도의 크기를 생성하는 것이다. 이는 일반적으로 "다이제스트(digest)"라고 한다. 해시 알고리즘을 통해 같은 자료가 다시 구동되면, 같은 다이제스트가 생성되어야만 한다. 데이터에 약간의 수정이 가해졌을 때 좋은 해시 알고리즘은 예상하기 힘든 다이제스트로 바꾸게 된다. 이에 따라 원본 데이터, 즉 주어진 다이제스트의 역공학(reverse engineering)이 불가능하게 된다.

두번째 단계는 저작자 개인키를 사용하여 다이제스트를 암호화하는 것이다. 만약 '공용 키(public key)' 또는 '개인 키(private key)'라는 용어에 익숙하지 않다면, 아마도 공용키 암호법에 대해 알기 못하기 때문일 것이다.공용키 암호법의 기본 개념은 간단하다. 개개인의 개인 키를 이용하여 암호화된 어느 것이든 동일한 개인의 공용키를 사용하여야만 암호문을 해독 할 수 있다. 그 반대 또한 성립하는데, 개인의 공용키를 사용하여 암호화된 것은 동일한 개인의 개인키를 사용하여야만 암호문을 해독할 수 있다. 두개의 키는 수학적으로 연결되어 있다. 사용자의 개인키에 다이제스트를 암호화하면 뒤섞인 자료들이 원본 문서 데이터에 추가된다.

공용키는 다른 사람들과 공유되며 개인키는 저작자만이 알 수 있기 때문에 전자 서명을 확인하는 것은 간단하다. 그 단계는 다음과 같다.

  • 받은 문서 데이터를 개작한다.
  • 일반적으로 문서에 수반된 저작자의 공용키를 이용하여 암호화된 다이제스트를 해독한다. 
  • 두개의 다이제스트를 비교하여 만약 그들이 같으면 서명은 유효한 것이다.

만약 두개의 다이제스트가 같지 않으면 문서가 변경된 것이거나 문서의 저작자가 서명을 한 사람과 동일하지 않은 것이다. 그러나 이 정보에서는 둘 중 어느 쪽(혹은 둘다)이 잘못되었는 지는 나타나지 않는다.


3. 전자서명의 유형
전자서명의 유형은 3가지로 분류한다.

Enveloping Signature

  • 대상 데이터가 Signature 구조 안에 존재
  • XML 패이로드 안에 패키지화된 데이터의 전자서명에 좋음

  
    
    
    
      
      C2g9BLcGyGPCVKuF2byR1Ym+6pE=
    
  
  +R/XEOHDvR/jbmmpiuH4ZcRqC6c=
  

Enveloped Signature

  • 대상 데이터가 밖에서 Signture 구조를 포함함
  •  XML 문서 일부 또는 전체를 전자서명에 하는데 좋음

content

  
    
    
    
      
        
      
      
      MMMkB0ZPp82XrUvJMFqDIEuXy0o=
    
  
  mVPvfcVSXi9elKL+IcSCAzD4Jbk=


Detached Signature

  • 대상 데이터가 밖에 있으며 Signture 구조를 포함하지 않음
  • URI 주소로 명시된 리모트 위치에 존재하는 데이터를 전자서명함

  
    
    
    
      
      oLZZOWcLwsAQ9NXWoLPk5FkPuSs=
    
  
  O9ykpFMXmkddzJ3CySrpzHBUW/Q=


4. XML Signature의 구성
다음 그림은 XML Signature의 스키마 구조이다. 


SignedInfo

  • 전자서명의 대상이 되는 정보를 가져오기 위한 각종 알고리즘과 대상 데이터를 해시(Digest)한 값이 들어있다.
  • 실제로 서명되는 정보로서 서명자의 개인키를 이용해 SignatureValue을 생성한다.

XML 전자서명은 반드시 정규화 과정을 거친 후 수행되어야 한다. 이유는 다음과 같다.

- 논리적으로 동일한 문서에 대해 물리적으로 여러 가지 표현이 가능
- 논리적으로 동일한 XML 문서에 대한 해시 값이 항상 같음을 보장할 수 없음
- 논리적으로 동일한 XML 문서가 하나의 물리적인 데이터로 변환하는 정규화 필요

CanonicalizationMethod에는 SignedInfo 원소를 다이제스트하기 전에 그 원소를 정규화할때 사용하는 알고리즘을 지정한다.

SignatureMethod에는 정규화된 SignedInfo를 SignatureValue로 변환할 때 사용하는 알고리즘이 들어있다..

Transforms는 자원의 콘텐트가 다이제스트 되기 전에 그에 적용된 처리 단계의 순서화된 목록,  정규화, 인코딩/디코딩(압축포함), XSLT, XPath(소스 문서의 일부분을 누락한 XML 문서 변환 허용)가 들어간다.

DigestMethod에는 Transforms가 적용된 이후 DigestValue를 만들어 내기 위해 데이터에 적용되는 알고리즘이 들어있다.

DigestValue에는 데이터객체에 Transform들을 적용한 후 결과로부터 얻은 다이제스트 값이 들어있다. BASE64로 인코딩된다.

SignatureValue

  • SignedInfo에 지정된 알고리즘에 근거하여 SignedInfo를 정규화하고 그 결과에 대해 서명된 디지털 서명의 실제 값이 들어간다. BASE64로 인코딩된다.

KeyInfo

  • 서명 검증에 사용될 공개키(검증키)를 포함


5. XML 전자서명 오픈소스

Posted by devop

댓글을 달아 주세요

  1. 좋은 정보 감사합니다.

    2012.04.18 17:25 신고 [ ADDR : EDIT/ DEL : REPLY ]