现在的位置: 首页 > 综合 > 正文

XML 安全: 实现安全层,第 2 部分

2013年03月29日 ⁄ 综合 ⁄ 共 11464字 ⁄ 字号 评论关闭
核心技术——XML 加密与 XML 签名

级别: 中级

Manish Verma
首席架构师, Second Foundation
2003 年 12 月

许多新兴的技术,比如 Web 服务,都将 XML 广泛应用于数据交换。因此,XML 在传输和存储时的安全性成为非常重要的问题。本次系列文章介绍了保护 XML 的各种技术。第 1 部分介绍了 XML 安全性方面的基本支持技术。本文在前一部分的基础上,介绍了保障 XML 安全性的核心技术——XML 加密和 XML 签名。本文还循序渐进地介绍了用这些技术保护 XML 消息的过程。

有许多现有的加密技术,比如安全套接字层(Secure Sockets Layer,SSL),可以对 XML 文档进行加密,这和其他任何文档都一样,那么为什么还需要另一种加密标准呢?在本文中,我将从考察其目标和动机开始,引出另一种加密标准—— XML 加密(XML Encryption)。

XML 加密
XML 加密的首要目标是:

  • 支持对任意数字内容的加密,包括 XML 文档。
  • 确保经过加密的数据不管是在传输过程中还是在存储时,都不能被未经授权的人员访问到。
  • 即便在消息传送中的每一跳,都能维持数据的安全性——这句话的意思是,不仅数据正在传输的过程中要保证安全性(这就是 SSL 所作出的保证),当数据在某个特定的节点停留的时候,也要保证其安全性。
  • 以 XML 形式表现被加密的数据。
  • 可以从 XML 文档中选出一部分内容进行加密。

我们拿这个目标与基于 HTTP 的 SSL(又称 HTTPS)必须提供的功能进行比较。如果使用基于 HTTP 的 SSL,整条消息都会被加密;在第一个目的地,整条消息又被解密,在它被重新全部加密传输到第二跳的节点之前,可能受到嗅探器的威胁。基于 HTTP 的 SSL 提供的加密仅仅在传输的过程中存在,它是不持久的。

XML 加密概览
设计目标 之一清晰地表明,经过加密的 XML 数据应该用 XML 的形式表现。在得到的 XML 中,有两个重要的元素值得理解清楚: <EncryptedData> <EncryptedKey><EncryptedData> 中包含除加密密钥之外所有经过加密的内容。当对加密密钥进行加密的时候,得到的结果就放置在 <EncryptedKey> 元素中。

除了加密过的内容以外,XML加密还允许您在上面讨论的两个元素中指定用于加密的算法,或者是加入用于加密的密钥。这意味着您即便不将它们分别保存,也可以在后续引用这些数据;或者通过其他传输机制发送给接收方。

注意:XML 加密没有定义任何新的加密算法,只使用已有的算法。

XML 加密过程
在本节中,我先向您展示一段普通的 XML 代码,然后介绍用 XML Encryption 标准(参阅 参考资料)对这段代码进行加密的各个步骤。我将从清单 1 中显示的 XML 开始。

清单 1. XML 示例


<?xml version="1.0"?>
<PurchaseOrderRequest>
  <Order>
    <Item>
      <Code>Screw001</Code>
      <Description>Screw with half centimeter thread</Description>
    </Item>
    <Quantity>2</Quantity>
  </Order>
  <Payment>
    <CreditCard>
      <Type>MasterCard</Type>
      <Number>1234567891234567</Number>
      <ExpiryDate>20050501</ExpiryDate>
    </CreditCard>
    <PurchaseAmount>
      <Amount>30000</Amount>
      <Currency>INR</Currency>
      <Exponent>-3</Exponent>
    </PurchaseAmount>
  </Payment>
</PurchaseOrderRequest>

清单 2 示范了如何对 清单 1中的部分 XML 进行加密。在这个清单之后,我解释了加密过程中的每一个步骤。

清单 2. XML 加密


//Get the DOM document object for the XML that you 
// want to encrypt.
// getDocument method that takes XML file name as input 
// and returns DOM document provided in Listing 3 (Step 1)
Document doc = XmlUtil.getDocument(xmlFileName);
String xpath = "/PurchaseOrderRequest/Payment";

// Step 2. Get the shared secret. This key is used to encrypt the
// XML content
Key dataEncryptionKey = getKey();
// Algorithm type used to generate shared secret
// i.e. content encryption key
AlgorithmType dataEncryptionAlgoType = 	AlgorithmType.TRIPLEDES;
// Get the key pair. You are interested in the public key 
// as that is the one you will use for encrypting the 
// XML content
KeyPair keyPair = getKeyPair();
// Step 3. Get the public key of the key pair
Key keyEncryptionKey = keyPair.getPublic();
// Algorithm type used to generate the public 
	// private key pair
AlgorithmType keyEncryptionAlgoType = AlgorithmType.RSA1_5;

KeyInfo keyInfo = new KeyInfo();

// Step 4
try {
	Encryptor enc =
		new Encryptor(
			doc,
			dataEncryptionKey,
			dataEncryptionAlgoType,
			keyEncryptionKey,
			keyEncryptionAlgoType,
			keyInfo);
	XPath xpath = new XPath(xPath);
	// Step 5
	try {
		enc.encryptInPlace(xpath);
	} catch (XPathException e1) {
		System.out.println("XPAth is not correct");
		e1.printStackTrace();
	}
	XmlUtil.XmlOnStdOut(doc);
} catch (Exception e) {
	System.out.println("Some exception");
	e.printStackTrace();
} 

步骤 1:将 XML 转换成 DOM 对象,如清单 3 所示:

清单 3. 根据 XML 创建 DOM 对象


public static Document getDocument(String fileName) {
Document doc = null;

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

File f = new File(fileName);
DocumentBuilder builder = null;
try {
	builder = factory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
	System.out.println("Parse configuration exception");
	e.printStackTrace();
}
try {
	doc = builder.parse(f);
} catch (Exception e1) {
	System.out.println("Some exception");
	e1.printStackTrace();
} 
return doc;
}

步骤 2:获得共享密钥(shared secret)。您要用这个密钥来加密 XML 内容。本文附带的源代码使用的 XML 加密方法只能识别三重 DES(Triple-DES)加密算法,因此我就用这种算法创建密钥。

步骤 3:参照本系列文章 第 1 部分所述,获得公-私密钥对中的公钥;您需要用这个公钥给共享密钥加密。您从第 1 部分中可以看到,这个公钥是基于 RSA 算法生成的。

步骤 4:利用一个数据加密密钥、一个密钥加密密钥、与这两个密钥相关联的算法、以及将来包含在输出信息中的密钥信息,根据它们来创建一个 Encryptor 对象。创建 Encryptor 对象时指定的算法必须与密钥相符。 Encryptor 是加密过程中的主要对象。它的类在 com.verisign.xmlenc 这个包中。 Encryptor 根据 W3C XML Encryption 规范进行加密。您可以指定想要使用哪种加密类型,是 Element 还是 Content。在 清单 2 中,加密类型是 Element,这也是默认的类型。 Encryptor 要理解 XPath 表达式,这样才能识别出需要加密的 XML 元素。

步骤 5:最后一步,调用 Encryptor 对象的 encrypt 或者 encryptInPlace 方法,并将 XPath 作为输入参数传入。XPath 定义了 XML 内部需要进行加密的元素。这个元素的所有子元素,以及 XPath 所指向的属性也都要进行加密。在本例中,您加密的是 XML 中的 /PurchaseOrderRequest/Payment 元素。 encryptencryptInPlace 两个方法都用传入的共享密钥对 XPath 指定的 XML 元素进行加密,两种方法也都用公钥对共享密钥进行加密,并将加密结果嵌入到 XML 加密后的内容之中。这两种方法的唯一区别在于, encrypt 返回一个全新的 DOM 文档,其中包含加密后的数据,而 encryptInPlace 方法对原有的文档本身进行修改,使其中包含加密后的数据。加密过的 XML 如清单 4 所示。

清单 4. 加密后的 XML


<?xml version="1.0" encoding="UTF-8"?>
  <PurchaseOrderRequest>
    <Order>
      <Item>
        <Code>Screw001</Code>
          <Description>Screw with half centimeter thread</Description>
      </Item>
      <Quantity>2</Quantity>
    </Order>
    <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" 
    xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
      <xenc:EncryptionMethod Algorithm=
      "http://www.w3.org/2001/04/xmlenc#tripledes-cbc"/>
        <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
          <xenc:EncryptedKey>
            <xenc:EncryptionMethod Algorithm=
            "http://www.w3.org/2001/04/xmlenc#rsa-1_5"/>
              <xenc:CipherData>
              <xenc:CipherValue>
       F1aIpdp3axm8nFofx/xX62VlsxildddHcxaevd7sbr+lv/fzZ7e8ovmKGQopAjclxPTybpkW
       YG8GVcOIbD4UGR24CNxeB7eZCws5/RKBTqKp+76FkVxf+G+EqgMmueRqoaF4oYOrTKquWLnR
       kiSOFmplRaJ8G7bR2j0eTFdiFRk=
              </xenc:CipherValue>
            </xenc:CipherData>
          </xenc:EncryptedKey>
        </ds:KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>
       KMkufRUY7rs0i+4jX6VhviiUIbYWay1KbwhTQxH9SaqJ6HA+Qc2Ce7TVZUQuH0GGD4xTR8hB
       hOls+hgHA16EfmmxLd3E+YqO4sXQ+GkX9O9EcO4ULha/q1KmP2yNGNy/tavdj9a7JuZnnNGV
       /M4gxdt5fCJXT0A9bw9HwKR/Pc81rZYWa7fOrmvDvC7Q+//OCzkqcAaCmAHEySWbv2vK3T+a
       GlQOI2Wooxa9hm7Dx70BuLI8ihhSAV3moK+JAPdn1vdCpoFKdzzq2HSh/yOisYZvQOh+jIks
       MW8oUzWnVUe/DFztPtvvDKbPE/xoAasixlbDLa42gFFe9uzEeIG89XBMSkZtTio0zn9xppSf
       Dc0WFMy+UoLnCA==
          </xenc:CipherValue>
        </xenc:CipherData>
      </xenc:EncryptedData>
  </PurchaseOrderRequest>

清单 4是部分加密的 XML 代码片断。只有当接收者具有与加密数据时使用的公钥相对应的私钥时,才能阅读这部分被加密的数据。

最后说一下,清单 5 中的代码可以对加密过的 XML 进行解密。

清单 5. XML 解密


// Get the DOM document object for the XML that you 
// want to decrypt (The one shown in Listing 4)
// The getDocument method that takes an XML file name as input 
// and returns a DOM document is provided in Listing 3 (Step 1)
Document doc = XmlUtil.getDocument(encryptedXmlFileName);

// Step 2. Get the private key of the pair whose public key was
// used to encrypt the XML
Key privateKey = keyPair.getPrivate();
// Specifying the XPath at which encrypted data is lying 
// in the XML

// Step 3. Specify XPath expression
String xpath = "//xenc:EncryptedData";
// Specify the namespace that relates to the XPath
// expression 
String[] ns = 
	{ "xenc", "http://www.w3.org/2001/04/xmlenc#" };
// Create the XPath helper with the XPath expression and a map 
// of namespaces that relate to the XPath expression

XPath xpath = new XPath(xPath, ns);

// Step 4. Create the Decryptor object with decryption key and 
// location of the encrypted data to be decrypted
Decryptor decrypt = null;
try {
	decrypt = new Decryptor(doc, privateKey, xpath);
} catch (Exception e) {
	System.out.println("Some exception");
	e.printStackTrace();
} 
// Step 5. Method decryptInPlace is called to decrypt the
// encrypted contents of the XML
try {
	decrypt.decryptInPlace();
} catch (Exception e1) {
	System.out.println("Some exception");
	e1.printStackTrace();

}

清单 5示范了当您有正确的私钥时,如何对加密的数据进行解密。下面的步骤解释了解密的过程。

步骤 1:将加密过的 XML 转换成 DOM 对象,这一步与加密过程相同。

步骤 2:根据用于加密 XML 的公钥,获取密钥对中对应的私钥。请注意,解密过程使用的是加密 XML 时使用的公钥所对应的私钥。.

步骤 3:创建 XPath 以及相关名称空间,用于表示加密过的数据在加密过的 XML 中的位置。在本例中,XPath 的值是 //xenc:EncryptedData 。加密过的数据总是在加密过的 XML 中的 xenc:EncryptedData 元素下面,而与哪个元素被加密无关。XPath 为 //xenc:EncryptedData 则表示,从 XML 中可能出现加密数据的任何地方查找 EncryptedData 元素。

步骤 4:用解密密钥和需要解密的加密数据所在的位置创建 Decryptor 对象。 Decryptor 是解密过程中的主要对象。它的类在 com.verisign.xmlenc 包中。 Decryptor 根据 W3C XML Encryption 规范进行解密(参阅 参考资料)。解密过程支持 Element 和 Content 两种类型。为了识别需要解密的 XML 元素, Decryptor 要能理解 XPath 表达式。

步骤 5:解密过程的最后一个步骤是在 Decryptor 对象中调用 decryptInPlace 或者 decrypt 方法。这两种方法调用都使用提供的私钥来解密共享密钥(共享密钥是已加密消息中的一部分),然后用这个共享密钥来解密消息的其余部分。两种调用之间的唯一区别在于, decrypt 对 XML 解密之后创建一个新的 DOM 对象,而 decryptInPlace 在作为输入接收的同一 DOM 对象中解密消息。

XML 签名
数字签名已经应用了相当长一段时间,任何数字化内容都可以用公钥密码标准(Public Key Cryptography Standards,最常用的是 PKCS#7 签名)进行数字签名。安全多用途 Internet 邮件扩展(Secure Multipurpose Internet Mail Extensions,S/MIME)允许将数字签名附加到电子邮件消息上,这样接收方就可以验证签名者的身份。

XML 签名是对现有数字签名基础设施的扩展。下面列出了创建 XML 签名的一些目标和动机:

  • 在数字签名周围建立一些结构,这样就可以用 XML 文档的形式来表现数字签名。
  • 实现对一部分 XML 进行签名,而剩余部分则不签名。
  • 实现对同一份 XML 文档的不同部分使用多于一种的数字签名。
  • 不仅仅在文档传送和通信的时候使用签名,还要使签名能够持久保留。

XML 签名概览
XML 签名可以用于对包括 XML 文档在内的任何数字内容进行签名。对数字内容进行签名的过程分为两个阶段。在第一阶段中,对数字内容进行整理,得到的结果放在一个 XML 元素中。第二阶段挑选出经过整理的值,并对其进行签名。这样做的原因非常简单:对原始内容进行整理之后,可以得到一个很小的但是唯一的加密结果(称为摘要),这样比对原始内容进行签名花费的时间少。

当 XML(或其中的一部分)经过数字签名之后,得到的 XML 签名用一个 XML 元素表现出来,这个元素的标识是 <Signature> ,最初的内容与这个数字签名的关系基于下面几种 XML 签名类型:

  • 封外签名(Enveloping signature)<Signature> 元素中包含了进行数字签名的元素。被签名的元素成为了 <Signature> 元素的子元素。
  • 封内签名(Enveloped signature)<Signature> 元素成为被签名数据的子元素。 <Signature> 元素通过它其中的 <Reference> 元素提供的信息引用被签名的元素。
  • 分离签名(Detached signature )<Signature> 元素与被签名的元素各自独立存在。被签名的元素和 <Signature> 元素可以同属于一个文档,或者, <Signature> 元素也可以在另一个完全不同的文档中。

除了引用被签名的数字内容之外, <Signature> 元素还包括了有关如下方面的信息:

  • 用于使数字内容规范化的方法。
  • 为待签名的规范化元素生成签名的算法。
  • 指定在整理之前如何处理待签名元素的附加信息。

XML 签名过程
接着 清单 1中的例子看,我现在将带你经历对 XML 元素进行数字签名的全过程。清单 6 示范了如何对部分 XML 进行数字签名;签名过程中的每一步骤都会在这个清单后面解释。

清单 6. 数字签名的过程


// Get the DOM document object for the XML that you 
// want to digitally sign.
// getDocument method that takes XML file name as input 
// and returns DOM document is provided in Listing 3 (Step 1)
Document doc = XmlUtil.getDocument(xmlFileName);
// XPath expression that is to be digitally signed
String xpath = "/PurchaseOrderRequest/Payment";

// Step 2. Get both the public and private keys. The private key is
// used to digitally sign the content, whereas the public key 
// is sent along with the digitally signed content for the 
// receiver to verify the digital signature.

contentKeyPair keyPair = getKeyPair();
PublicKey verificationKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();

// Step 3. Create a signer object passing the document whose part 
// is to be signed, the private key to be used for 
// signing, and the public key that is to be used 
// for verification.

Signer signer = null;

// Step 4 
try {
	signer = new Signer(doc, privateKey, 
						verificationKey);
	XPath location1 = 
	new XPath("/PurchaseOrderRequest/Payment/CreditCard");
	signer.addReference(location1);
	
	XPath location2 = 
	new XPath("/PurchaseOrderRequest/Payment/PurchaseAmount");
	signer.addReference(location2);

} catch (Exception e) {
	e.printStackTrace();
}
// Step 5. Use the signer object to sign the document passing the 
// XPath of the element that is to be signed.
try {
	d = signer.sign(new XPath(xPath));
} catch (Exception e1) {
	e1.printStackTrace();
}

步骤 1:同前面加密的步骤一样,将需进行数字签名的 XML 转换为 DOM 对象。

步骤 2:获得您在本系列第 1 部分的“ 生成公-私密钥对”一节中生成的密钥对,其中同时包括公钥和私钥。您将用私钥对内容进行数字签名,然后将公钥随着经过数字签名的消息一起发送到接收方,以便用来验证其中的数字签名。前面讲过,公-私密钥对是基于 RSA 算法的。

步骤 3:根据需要签名的 DOM 文档、用来签名的私钥以及用于验证的公钥,创建一个 signer 对象。验证用的公钥并不是一定要有。 signer 是数字签名过程中的主要对象。它的类在 com.verisign.xmlsig 包中。 signer 对象根据 W3C XML Signature 规范(参阅 参考资料)对 XML 文档签名。共支持全部三种签名模式:封外、封内以及分离模式。

步骤 4:指定 XML 中的哪一部分需要签名。通过增加到 Signer 对象的引用,可以指定数字签名的 XML 位置。增加引用的方法是调用 Signer 对象中的 addReference 方法。需要签名的元素的 XPath 也作为一个参数提供给 addReference 。您可以多次调用 addReference ,来指定想要进行签名的不同元素。

步骤 5:最后一步是对 XML 签名,您可以通过调用 signer 对象中的 sign 方法来完成。当调用 sign 方法时,如果不传递任何参数的话,生成的就是封外签名。调用带有 XPath 参数的 sign 方法,并且数字签名放置在文档中,则生成封内签名。

结束语
随着这个 XML 安全专题深入到这里,我已经定义了安全的含义,并讨论了 XML 规范化、PKI 基础设施、XML 加密和 XML 签名。通过介绍 XML 安全背后的理论,并竭力向您展示 XML 加密和数字签名有多么容易,希望这一切努力能为您揭开 XML 安全的神秘面纱。

在今后的文章中,我将要讨论安全性断言标记语言(Security Assertion Markup Language,SAML)和 XML 密钥管理系统( XML Key Management System,XKMS)。SAML 可以用于在不同的协作实体之间传送凭证以及其他一些与主题、人等相关的信息,而不会损失信息的所属关系。通过将管理公钥基础设施(Public Key Infrastructure,PKI)的复杂性从客户机应用程序提取到一个可信任的第三方机构中, XKMS 使得管理 PKI 变得非常容易。

参考资料

 

关于作者
Manish Verma 是全球性 IT 服务公司 Second Foundation 的首席架构师。Manish 在软件开发生命周期的各个方面具有 10 年的工作经验,曾经为运行异构系统的的客户组织设计集成策略。Manish 的集成经验建立在他对各种技术的理解之上,其中包括各种遗留系统、.NET、Java 技术以及最新的中间件。在加盟 Second Foundation 之前,Manish 先后在 Quark Inc.、Hewlett Packard、Endura Software 和 The Williams Company 等公司担任软件架构师和技术负责人。可以通过 mverma@secf.com 与 Manish 取得联系。


抱歉!评论已关闭.