[출처] [본문스크랩] SOAP 메세지 암호화 하기|작성자 독수리
SOAP 메세지 암호화 하기
Rob Howard
Microsoft Corporation
2001년 10월
XML 웹 서비스는 정보를 HTML의 한계로부터 벗어나게 해준 원조자로서, 산업계로부터 찬사를 받고 있습니다. W3C에 제출된 프로토콜인 SOAP을 사용하면, 데이터는 XML로 인코드되고, 여러 인터넷 프로토콜들(가장 대표적인 것으로는 HTTP)을 사용하여 전송될 수 있습니다. 송신자와 수신자 모두가 메시지 형식(즉, SOAP이 정의한 프로토콜)에 동의한다면, 정보는 플랫폼에 독립적인 방식으로 쉽게 교환될 수 있습니다.
Microsoft는 XML 웹 서비스의 성공을 이루어 나감에 있어서 지도자 역할을 하고 있습니다. 이의 비전을 제시할 뿐만 하니라, 기술들과 제품들을 구현함으로써 말입니다. 그리고, 또한, XML 웹 서비스를 지원하는 가장 기대되는 제품 중의 하나도 바로 Microsoft의 ASP.NET입니다.
ASP.NET으로 XML 웹 서비스 구축하기
ASP.NET은 웹 서비스를 쉽게 구축할 수 있게 해 줍니다. 기존의 웹 개발자들(PERL/CGI에 대해 좋은 추억을 가진 자들이 여기에 해당됩니다)에게 ASP가 혁신적인 웹 개발이었던 것과 마찬가지로, ASP.NET은 XML(SOAP 프로토콜을 따르는)이나 HTML을 생산해내는 웹 응용 프로그램을 구축하는데 있어 혁신적일 것입니다.
언급한대로, ASP.NET으로 XML 웹 서비스를 구축하는 것은 간단합니다. 개발자로서, 우리는 사용자 정의 특성이 표기된 메서드 정의들을 갖는 클래스를 작성합니다. 이들은 모두 .asmx 확장자를 사용하는 파일 안에 존재해야 합니다. 다음은 간단한 웹 서비스의 예입니다.
다음의 SOAP 메시지가 이 서비스에게로 보내질 수 있습니다.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<Add xmlns="http://tempuri.org/" />
<a>5</a> <b>8</b>
</Add>
</soap:Body>
</soap:Envelope>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<AddResponse xmlns="http://tempuri.org/" />
<AddResult>13</AddResult>
</AddResponse>
</soap:Body>
</soap:Envelope>
개발자에게 주어지는 이익은 명확합니다. 즉, XML, HTTP, SOAP 및 XML 웹 서비스를 구축하기 위해 사용되는 다른 모든 프로토콜들을 이해할 필요가 없다는 것입니다. 대신, 개발자는 솔루션을 구축하는 데에만 집중할 수 있습니다.
보안은 어떻습니까?
개발자들의 핵심 관심사 중에 하나는 보안입니다. 위의 예제는 '연결(the wire)' 상에서 두 개의 XML 웹 서비스 사이에 보내진 것이 무엇인지를 보여주고 있습니다. 여러분도 알겠지만, 교환은 일반 텍스트로 이루어졌습니다. 데이터가 일반 텍스트로 전송되고, 최종 목적지에 도달하기 위해 인터넷을 통해서 발송되었기 때문에, 누군가가 잠재적으로 메시지 교환을 볼 수도 있을 것입니다. 위의 예제의 경우라면, 이것이 문제가 되지는 않을 것입니다. 하지만, 만일, 이러한 데이터 교환이 은행으로의 요청으로 이루어지고 있고, 응답이 계좌번호와 잔고의 목록을 포함한다면? 또는 신용카드 번호와 같은 뭔가 보다 흥미로운 정보를 반환하는 요청이라면? 그렇다면, 다른 사람에게 이 정보가 보여지기를 결코 원하지 않을 것입니다!
암호화 지원하기
교환해야 할 데이터가 있고, 누군가가 이를 훔쳐보기를 원하지 않는 경우라면, 암호화를 통해서 통신을 보안적이게 해야만 합니다. SOAP 사양은 XML 웹 서비스에 대한 암호화를 정의하고 있지는 않습니다. 대신, 이는 SOAP 프로토콜의 구현자의 몫으로 남겨져 있습니다. 많은 경우, 권장되는 보안 표준은 HTTPS(secure HTTP)입니다.
HTTPS 상에서의 SOAP은 안전합니다. HTTP 메시지의 헤더들과 본문을 포함하는 전체 HTTP 메시지는 공개된 비대칭 암호화 알고리즘을 사용하여 암호화됩니다. 그렇지만, 이러한 암호화의 형식은 전송 프로토콜(HTTP)에 종속적입니다. 반면, SOAP 메시지는 전송-독립적인 프로토콜입니다. SOAP 메시지는 HTTP, SMTP, TCP, UDP 등을 사용하여 전송될 수 있습니다. SOAP 메시지는 라우팅 될 수 있으며, 다른 곳으로 메시지를 라우팅할 수도 있습니다. 즉, 서버는 최종 목적지가 확정되지 않은 메시지를 받을 수 있다는 것입니다. 라우팅되는 동안, 서로 다른 전송 프로토콜들이 사용될 수도 있습니다. 예를 들면, HTTP->UDP->SMTP->HTTP와 같이 말입니다. 그렇기 때문에, 보안에 대한 전송 프로토콜 상의 종속성은 본질적인 문제를 가지고 있습니다. 예를 들면, 라우팅 서버가 메시지를 읽기 위해서 이를 복호화할 수 있어야 하고, 그 다음 다른 전송 프로토콜에 대해 재 암호화할 수 있어야만 함에 따라, 얼마나 많은 트러스트가 라우팅 서버들 상에 놓여져야 합니까?와 같은 질문이 그것입니다. 그 외의 질문들은 다음과 같습니다.
- 전송 프로토콜이 보안 통신을 지원합니까?
- 데이터의 일부를 암호화하는 비용 대비 모든 데이터를 암호화하는 데 드는 비용은?
비록 위의 문단에 완벽하게 부합한다 하더라도, 많은 경우에 있어 HTTPS가 보안적인 SOAP 메시지 교환을 위해 권장되는 해결책이라는 것을 기억하십시오. 왜냐하면, 오늘날 거의 모든 XML 웹 서비스가 전송 프로토콜로 HTTP를 사용하고 있고, 극소수의 XML 웹 서비스들만이 현재 메시지 라우팅을 지원하기 때문입니다. 반면, SOAP 메시지의 암호화를 지원하는 ASP.NET 웹 서비스를 구축하는 것은 쉽게 가능합니다. 암호화는 전송 프로토콜에서 종속성을 제거하고, 메시지의 오직 선택한 부분들만이 암호화될 수 있게 해주는 공개 암호화 알고리즘을 사용하여 이룰 수 있습니다. 예를 들면, 밑의 해결책은 단일 전달 Data Encryption Standard(DES) 암호화(양쪽 업체 모두가 공용 키를 공유해야만 하는 공개 대칭 암호화 알고리즘)를 사용하고 있으며, 오직 메시지의 본문만이 암호화됩니다(라우팅 서버가 헤더들을 읽을 수 있게 합니다. 본문은 읽을 수 없습니다)
SOAP 확장
ASP.NET 웹 서비스에 대해 구현할 암호화 지원은 SOAP 확장의 형태로 이루어집니다. SOAP 확장은 ASP.NET 웹 서비스를 구현하는 개발자들이 단순히 메서드에 또 다른 특성을 추가함으로써(WebMethod 특성과 함께) 가능하며, 메시지는 암호화되거나 복호화 됩니다. 다음은 이러한 새로운 확장을 갖는 ASP.NET 웹 서비스의 코드 예제입니다.
<%@ WebService Language="C#" Class="CreditCardService" %>
using System.Web.Services;
public class CreditCardService
{
[WebMethod] [EncryptionExtension(Encrypt=EncryptMode.Response)]
public string GetCreditCardNumber()
{
return "MC: 4111-1111-1111-1111";
}
}
이는 매우 단순한 예제이긴 합니다만, GetCreditCardNumber 메서드에 단순히 하나의 사용자 정의 특성을 추가했다는 사실에 주목할 필요가 있습니다.
[EncryptionExtension(Encrypt=EncryptMode.Response)]이것이 우리가 잠시 살펴보려 하는 사용자 정의 SOAP 확장 특성의 소스입니다. 이 기사를 쓰고 있는 시점에는, Encrypt=EncryptMode.Response와 Decrypt=DecryptMode=Request만이 지정 가능합니다(물론, EncryptMode.None과 Decrypt.None 값도 가능합니다).
요청은 일반 텍스트로 웹 서비스에게 전송될 수 있으나, 응답들은 암호화될 것입니다. 다음은 전형적인 교환이 어떤 지를 보여줍니다. 우선 요청이 들어옵니다.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetCreditCardNumber xmlns="http://tempuri.org/" />
</soap:Body>
</soap:Envelope>
그 다음, 응답이 옵니다.
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetCreditCardNumber xmlns="http://tempuri.org/">
<GetCreditCardNumberResult>83 151 243 32 53 95 86 13 190 134 188 241 198 209 72 114 122 38 180 34 194 138 16 97 221 195 239 86 26 152 94 27
</GetCreditCardNumberResult>
</GetCreditCardNumber>
</soap:Body>
</soap:Envelope>
확연하게 보이듯이, 응답은 여전히 유효한 SOAP 메시지입니다. 하지만, 신용카드 번호는 일반 텍스트로 전송하는 대신, 암호화된 바이트들의 배열로 교환됩니다.
여기에 또 다른 보안 문제가 있다는 사실도 기억하십시오. 예를 들면, 메서드는 아마도 GetCreditCardNumber라고 불려서는 안될 것입니다. 그 명칭은 숙련된 해커에게 귀뜸이 되는 정보가 될 수도 있을 테니 말입니다. 그렇긴 하지만, 역시나 요점은 XML 웹 서비스에 의해 보내지는 데이터가 여전히 HTTP, SMTP, 혹은 다른 전송 프로토콜 상에서 라우팅될 수 있는 유효한 SOAP 메시지로 전송된다는 것입니다 - 올바른 키를 가지고 있는 최종 클라이언트만이 메시지를 복호화할 수 있기에, 메시지의 보안은 안전합니다. .NET 클라이언트들의 경우, 이는 wsdl.exe나 Visual Studio .NET에 의해 생성되는 프록시 클래스에 단순히 특성을 추가하는 문제일 뿐입니다.
... [SoapDocumentMethodAttribute("http://tempuri.org/GetCreditCardNumber", Use= SoapBindingUse.Literal, ParameterStyle= SoapParameterStyle.Wrapped)] [EncryptionExtension(Decrypt=DecryptMode.Request)] public string GetCreditCardNumber() { object[] results = this.Invoke("GetCreditCardNumber", new object[0]); return ((string)(results[0])); } ...
위의 경우들 모두에서, 우리는 SOAP 확장을 사용하고 있습니다. SOAP 확장이란 SOAP 직렬화(serialization) 프로세스와 저 수준에서 상호작용을 할 수 있게 해 주는, 우리가 개발하는 클래스들(SoapExtension에서 파생된)을 말합니다. 밑의 도표에서, 빨간 별표들은 SOAP 확장을 통해서 상호작용을 할 수 있는 기회 시점들을 나타내고 있습니다.
그림 1. SOAP 직렬화와(serialization) 직렬화 해제(deserialization)
자. 그럼 이제 SOAP EncryptionExtension의 코드를 살펴보도록 하겠습니다. 단, 전체 소스가 아닌 작은 일부 구역들만을 살펴보려 합니다. 전체 소스를 원하시면 http://www.gotdotnet.com/team/rhoward (영문) 를 살펴보시기 바랍니다.
EncryptionExtension 클래스는 System.Web.Services.Protocols 네임스페이스에 있는 SoapExtension에서 파생됩니다. SoapExtension에서 상속된 클래스들은 반드시 몇몇 메서드들과 속성들을 구현해야만 합니다. 그 중 가장 중요한 것은 다음에서 보여지고 있듯이, ProcessMessage(SoapMessage message) 메서드입니다.
public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
Encrypt();
break;
case SoapMessageStage.BeforeDeserialize:
Decrypt();
break;
case SoapMessageStage.AfterDeserialize:
break;
default:
throw new Exception("invalid stage");
}
} ...
}
재정의된 메서드 안에서, 우리는 SOAP 메시지 직렬화와 상호작용하기 위한 네 단계의 기회(위의 도표에서 표기한 것처럼)를 갖습니다. EncryptionExtension의 경우는, 암호화와 복호화를 위해서 오직 AfterSerialize와 BeforeDeserialize 단계에만 관여하고 있습니다. 만일, 예를 들어, 응답이 직렬화된 후에 GetCreditCardNumber 웹 서비스로 요청이 들어온다면, 우리의 EncryptionExtension는 ProcessMessage ()로부터 Encrypt()를 호출할 것입니다.
{
newStream.Position = 0;
if ((encryptMode == EncryptMode.Request)
|| (encryptMode == EncryptMode.Response))
{
newStream = EncryptSoap(newStream);
}
Copy(newStream, oldStream);
}
Encrypt() 메서드 안에서는 EncryptionExtension 특성의 EncryptMode 속성이 설정되었는 지를 검사합니다. 만일, 이것이 EncryptMode.Request나 EncryptMode.Response로 설정되었다면, 내부 메서드인 EncryptSoap()가 호출됩니다. EncryptSoap()은 SOAP 메시지를 나타내는 스트림을 받고, 암호화된 값들을 갖는 새로운 스트림을 반환합니다.
public MemoryStream EncryptSoap(Stream streamToEncrypt)
{
streamToEncrypt.Position = 0;
XmlTextReader reader = new XmlTextReader(streamToEncrypt);
XmlDocument dom = new XmlDocument();
dom.Load(reader);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(dom.NameTable);
nsmgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
XmlNode node = dom.SelectSingleNode("//soap:Body", nsmgr);
node = node.FirstChild.FirstChild;
byte[] outData = Encrypt(node.InnerText);
StringBuilder s = new StringBuilder();
for(int i=0; i<outData.Length; i++)
{
if(i==(outData.Length-1)) s.Append(outData[i]);
else s.Append(outData[i] + " ");
}
node.InnerText = s.ToString();
MemoryStream ms = new MemoryStream();
dom.Save(ms);
ms.Position = 0;
return ms;
}
EncryptSoap() 안에서는 soap:Body 안에 있는 첫 번째 노드를 암호화합니다. 적절한 XmlNode가 선택되고, 노드 안에 있는 텍스트는 문자열을 받아서 바이트들의 배열을 반환하는 Encrypt()에게로 다시 전달됩니다. 다음은 문자열을 암호화하기 위해 .NET DES 클래스들을 사용하고, 암호화된 값의 바이트 배열을 반환하는 Encrypt() 메서드입니다.
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputByteArray = Encoding.UTF8.GetBytes(stringToEncrypt);
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor( key, IV ), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock(); return ms.ToArray();
}
많은 사람들이 이 해결책이 .NET에 특화된 것이 아니냐고 물어오곤 합니다만, 이는 전혀 그렇지 않습니다. 하지만, 양쪽 업체 모두가 여기에 DES 암호화가 사용되었다는 것을 알고 있어야 하고, .NET 기반이 아닌 호출자도 DES 알고리즘을 사용해야 한다는 요구조건은 있습니다(DES는 공개적으로 사용 가능한 알고리즘임을 기억하십시오).
위의 예제를 보강하고자 한다면, 다음의 것들을 고려해 보십시오.
- 비대칭 암호화를 지원합니다: 호출자는 암호화를 위해서 서버의 공개 키를 사용하고, 서버는 응답을 암호화하기 위해서 클라이언트의 키를 사용합니다.
- 현재 예제는 오직 일반 텍스트 요청과 암호화된 응답만을 제공하고 있습니다.
- 인증정보 전송을 위한 SOAP 헤더들과 그들의 상세정보에 대해 암호화를 지원합니다.
요약
여러분도 아시다시피, ASP.NET 웹 서비스는 대단히 강력합니다. 대부분의 개발자들은 SOAP 확장의 능력을 완전하게 이해하고 있지 못하며, SOAP 확장이 웹 서비스에 대한 모든 종류의 새롭고 흥미로운 것들을 하기 위해서 어떻게 사용될 수 있는지도 잘 이해하지 못하고 있습니다. 예를 들면, ProcessMessage()에 의해 제공되는 기능성으로 인해서, XML 압축, 라우팅, 암호화, 로깅, 지불 처리 등과 같은 ASP.NET 웹 서비스 선택들을 구축하는 것은 상당히 쉽습니다. 이러한 해법을 사용하면, SOAP을 여전히 사용하면서 인터넷 너머로 안전하게 통신할 수 있는 XML 웹 서비스를 구축하는 것이 가능합니다.
'웹서비스' 카테고리의 다른 글
Tomcat을 이용하여 SOAP 설정 (0) | 2010.07.07 |
---|---|
SOAP을 이용하여 간단한 예제 (0) | 2010.07.07 |
SOAP 와 XMLHTTP를 사용한 응용프로그램 사용하기 (0) | 2010.07.07 |
본문스크랩] 2.웹 서비스를 이용한 이기종간 오브젝트 전달 (0) | 2010.07.07 |
[SOAP] 인코딩이 웹서비스 퍼포먼스에 미치는 영향 (0) | 2010.07.07 |