加密 SOAP 讯息

加密 SOAP 讯息

作者:Rob Howard
Microsoft Corporation

2001 9 27

XML Web Service 已广为业界所拥护,认为它具有将信息从 HTML 的限制中解放出来的能力。使用 SOAP 这种遵守 W3C 规范的通讯协议,数据可以编码为 XML,并使用多种因特网通讯协议加以传送,而 HTTP 则是最常使用的一种。只要寄送者和接收者都能够接受讯息格式也就是 SOAP 所定义的通讯协议信息就可以轻易地以不拘平台的方式进行交换。

Microsoft® 在确保 XML Web Service 的成功上扮演了领导者的角色,不只是提出了它的愿景,同时也实施了技术和产品。当然,支持 XML Web Service 最受期待的产品之一,就是 Microsoft® ASP.NET

ASP.NET 建立 XML Web Service

ASP.NET 让建立 Web Service 变成一件简单的事情。就像 ASP 为早期的网页开发人员彻底革新了网页的开发一样不知您是否回想起使用 PERL/CGI 进行开发的日子—ASP.NET 也将再次革新发送 HTML XML (遵守 SOAP 通讯协议) 的网页应用程序的建立方式。

正如上述所言,使用 ASP.NET 建立 XML Web Service 是相当容易的。身为开发人员,我们以自定义属性 (自定义属性是 .NET 的独特功能) 所标记的方法定义,制作出类别,它们全都存在于一个使用 .asmx 为扩展名的档案之中。例如,下列就是一个简单的网站服务:

<%@ WebService Language="C#" Class="SimpleMath" %>

 

using System.Web.Services;

 

public class SimpleMath {

 

  [WebMethod]

  public int Add(int a, int b) {

    return a+b;

  }

}

可以送出一个 SOAP 讯息到这项服务:

<?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>

    <Add xmlns="http://tempuri.org/" />

      <a>5</a>

      <b>8</b>

    </Add>

  </soap:Body>

</soap:Envelope>

然后 ASP.NET XML Web Service 会依序回应:

<?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>

    <AddResponse xmlns="http://tempuri.org/" />

      <AddResult>13</AddResult>

    </AddResponse>

  </soap:Body>

</soap:Envelope>

对开发人员而言,好处是相当明显的。并不需要了解 XMLHTTPSOAP 或任何其他用来建立 XML Web Service 的通讯协议。相反地,开发人员只需将焦点放在建立解决方案即可。

安全性如何?

对于开发人员来说,其中一个关键考虑就是安全性。上面的范例显示了两个 XML Web Service 之间互通有无的例子。正如您所注意到的一样,交换是藉由纯文本完成的。因为数据乃是以纯文本传送,并经由因特网转送到它的最终目的地,因此任何人都有可能检视到交换的讯息。在上述范例的情况下,这可能不会造成什么问题。但是,如果交换包含了对于银行的请求,而响应包含了帐户号码和收支的列表,或者请求传回更攸关利益的事情,像是信用卡号码时又该如何?显然这些就是我们并不希望任何其他的人可以检视的事情了!

支援加密

如果我们有数据想要交换,又不希望被加以窥探检视,我们就必须透过加密来安全化通讯。SOAP 规格书并未定义 XML Web Service 的加密。相反地,这个部份是交由 SOAP 通讯协议的实作程序加以处理,而在许多情况下,建议的安全性方法为 HTTPS (安全 HTTP)

HTTPS 上的 SOAP 是安全的。整个 HTTP 讯息,包括页首和 HTTP 讯息的内文,都使用了公开不对称加密运算法加以加密。不过,这种加密类型会在传输通讯协议放置一个附属物:HTTP,然而,SOAP 是一个独立于传输的通讯协议。SOAP 讯息可以使用 HTTPSMTPTCPUDP 等加以传送。SOAP 讯息也可以依规定路线发送,也就是说一台服务器可以接收到一个讯息,判断出它并非最终目的地,然后将讯息转送到其他的地方。在路由的过程中,可以使用不同的传输通讯协议,例如 HTTP -> UDP -> SMTP -> HTTP。因为传输通讯协议上的安全性附属物可能会有一些内在的问题:对路由服务器能够有多少的信任?因为路由服务器必须能够解密讯息,以便阅读内容,然后以另外的传输通讯协议重新加密。另外还有一些问题:

·         传输通讯协议是否支持安全通讯?

·         对所有数据加密和对部份数据加密的成本为何?

虽然上述所提的短评完全是有根据的,但我还是应该提醒大家,在许多情况下,HTTPS 还是安全 SOAP 讯息交换的建议解决方案,因为现在几乎所有的 XML Web Service 都使用 HTTP 做为传输通讯协议,而且目前只有非常少的 XML Web Service 支持讯息路由。然而,还是有可能轻易地建立出支持 SOAP 讯息加密的 ASP.NET Web Service 的。加密可以使用公开加密算法,它会将附属物从传输通讯协议拿掉,并允许只选择部份的讯息进行加密。例如,下面的解决方案使用了单次数据加密标准 (Data Encryption StandardDES) 加密 (它是一个公开对称加密算法,所有的参与者都必须共享一个通用密钥),并且只对讯息的内文部份进行加密 (可以让路由服务器阅读页首,但无法阅读内文)

SOAP 延伸功能

建立 ASP.NET Web Service 的加密支持,会以 SOAP 延伸功能的型式出现。SOAP 延伸功能能够让开发人员实施 ASP.NET Web Service 时将另外的属性加入方法当中 (配合 WebMethod 属性),并将讯息加密或解密。下列的范例,是我们的 ASP.NET Web Service 使用这个新的延伸功能后程序代码的模样:

<%@ 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 的数值。

请求可以采用纯文本传送到 Web Service,而响应则会予以加密。下列是一个典型交换的模样。首先是请求的部份:

<?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 Web Service 所传送的数据,还是以有效的 SOAP 讯息加以传送,而且可以在 HTTPSMTP 或其他任何传输通讯协议上转送而不会因此牺牲讯息的安全性,并且只有能够处理正确密钥的一般客户端,才能够对讯息加以解密。在 .NET 客户端的情况下,这只要对由 wsdl.exe Visual Studio .NET 所产生的 proxy 类别,加入属性即可:

...

[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 延伸功能是我们所开发的类别它们乃是衍生自 SoapExtension可以让我们以低阶进行 SOAP 串行化过程的互动。在下列的图表中,带有红色星号的点,代表透过 SOAP 延伸功能进行互动的机会:

 

1 SOAP 串行化和还原串行化

让我们来看看 SOAP EncryptionExtension 背后的程序代码。请注意,我们并不会检视所有的原始码,只会查看其中的一小部份。若想查看完整的原始码,请参见 http://www.gotdotnet.com/team/rhoward

EncryptionExtension 类别是从 SoapExtension 衍生来的,可以在 System.Web.Services.Protocols 命名空间中找到。继承自 SoapExtension 的类别必须为许多方法和属性提供一个实作方式。最重要的一个就是可以在下列程序代码中看到的 ProcessMessage(SoapMessage message)方法。

public class EncryptionExtension : SoapExtension {

  

  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()

  private void 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() 方法,该方法接受一个字符串,并传回一组位数组。也就是这个最后的 Encrypt() 方法使用了 .NET DES 类别,对字符串进行加密,并传回加密数值的位数组:

  private byte[] Encrypt(string stringToEncrypt) {

    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 Web Service 是非常强大的。大部份的开发人员都完全不了解 SOAP 延伸功能的力量,也不知道它们可以如何用在网站服务之上,制作出一些新鲜和有趣的功能。例如,因为 ProcessMessage() 所提供的功能,因此可以非常容易地对 ASP.NET Web Service 建立一些像是 XML 压缩、路由、加密、记录和付费处理等选项。使用这样的解决方案,当然就能够继续使用 SOAP,并建立出可以安全地在因特网上通讯的 XML Web Service 了。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值