当需要创建数字签名并发送给对等方进行验证时,通常会使用 XML 规范化。由于数字签名是基于 XML 数据创建的,因此 XML 数据必须先进行规范化,然后才能计算其签名值。即使是一个额外的空格也可能会影响计算出的签名值,因此它必须遵循一些规则来规范化 XML 数据,使其具有标准格式。这就是 W3C 创建 规范 XML 版本 1.1 规范的原因。
此规范提供了格式化元素节点、属性节点和命名空间节点等的规则。不同的编程语言已经实现了此规范,因此我们可以使用它们轻松地规范化 XML 数据,而无需了解有关该规范的详细信息。
在本教程中,我们不会深入探讨该规范的细节,我们将只关注如何调用 Java API 来规范化 XML 数据。基于 XPath 数据模型,XML 文档由一组节点(节点集)表示。而 Java 中的 XML API 接收一个要规范化的 Data 对象。为了演示规范化,我们将创建一个自定义的 NodeSetDataImpl,它实现了 NodeSetData 和 Iterator 接口。
import java.util.Iterator; import javax.xml.crypto.NodeSetData; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.traversal.DocumentTraversal; import org.w3c.dom.traversal.NodeFilter; import org.w3c.dom.traversal.NodeIterator; public class NodeSetDataImpl implements NodeSetData, Iterator { private Node ivNode; private NodeFilter ivNodeFilter; private Document ivDocument; private DocumentTraversal ivDocumentTraversal; private NodeIterator ivNodeIterator; private Node ivNextNode; public NodeSetDataImpl(Node pNode, NodeFilter pNodeFilter) throws Exception { ivNode = pNode; ivNodeFilter = pNodeFilter; if (ivNode instanceof Document) { ivDocument = (Document) ivNode; } else { ivDocument = ivNode.getOwnerDocument(); } ivDocumentTraversal = (DocumentTraversal) ivDocument; } private NodeSetDataImpl(NodeIterator pNodeIterator) { ivNodeIterator = pNodeIterator; } public Iterator iterator() { NodeIterator nodeIterator = ivDocumentTraversal.createNodeIterator(ivNode, NodeFilter.SHOW_ALL, ivNodeFilter, false); return new NodeSetDataImpl(nodeIterator); } private Node checkNextNode() { if (ivNextNode == null && ivNodeIterator != null) { ivNextNode = ivNodeIterator.nextNode(); if (ivNextNode == null) { ivNodeIterator.detach(); ivNodeIterator = null; } } return ivNextNode; } private Node consumeNextNode() { Node nextNode = checkNextNode(); ivNextNode = null; return nextNode; } public boolean hasNext() { return checkNextNode() != null; } public Node next() { return consumeNextNode(); } public void remove() { throw new UnsupportedOperationException("Removing nodes is not supported."); } public static NodeFilter getRootNodeFilter() { return new NodeFilter() { public short acceptNode(Node pNode) { if (pNode instanceof Element && pNode.getParentNode() instanceof Document) { return NodeFilter.FILTER_SKIP; } return NodeFilter.FILTER_ACCEPT; } }; } }
然后我们需要创建一个 CanonicalizationMethod 来执行实际的规范化。这里有几个选择:
CanonicalizationMethod.INCLUSIVE CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS CanonicalizationMethod.EXCLUSIVE CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS
在本教程中,我们将只演示包含式规范化。以下是执行此操作的代码。
import java.io.ByteArrayInputStream; import javax.xml.crypto.Data; import javax.xml.crypto.OctetStreamData; import javax.xml.crypto.dsig.CanonicalizationMethod; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; public class CanonicalizationTest { private static String INPUT = "" + "hello" + "world" + ""; public static void main(String[] args){ transform(INPUT); } /** * Create new document with data * * @param xml * @return */ private static Document createNewDocument(String xml){ try{ byte[] bytes = xml.getBytes("UTF-8"); ByteArrayInputStream bin = new ByteArrayInputStream(bytes); DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); fac.setNamespaceAware(true); DocumentBuilder docBuilder = fac.newDocumentBuilder(); Document doc = docBuilder.parse(bin); return doc; } catch (Exception ex){ ex.printStackTrace(); } return null; } /** * Transform a XML string * * @param xml */ private static void transform(String xml){ Document doc = createNewDocument(xml); try{ Data data = new NodeSetDataImpl(doc, NodeSetDataImpl.getRootNodeFilter()); XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM"); CanonicalizationMethod canonicalizationMethod = fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE, (C14NMethodParameterSpec)null); // Doing the actual canonicalization OctetStreamData transformedData =(OctetStreamData) canonicalizationMethod.transform(data, null); byte[] bytes = tool.Util.readStream(transformedData.getOctetStream()); String str = new String(bytes); System.out.println(str); } catch (Exception ex){ ex.printStackTrace(); } } }
首先让我们看一下输出:
<my:Node xmlns:my="http://example.com" Id="1">hello</my:Node><my:Node xmlns:my="http://example.com" Id="2">world</my:Node>
测试将首先创建一个 Document,然后将此 Document 传递给 NodeSetDataImpl 以创建要规范化的节点集。从输出中我们可以看到,Root 节点在规范化数据中被删除,这是因为 NodeSetDataImpl 中的 NodeFilter 过滤了此节点。接下来,第二个 my:Node 在规范化形式中,xmlns:my 节点在 Id 节点之前。这是基于规范 XML 规范,其中节点应按词汇顺序排列。
对于其他规范化方法,这里的逻辑是相同的。不同之处在于输出不相同,因为不同的规范化方法有不同的规则。