【问题标题】:XMLSignature created in Java is different from C#用 Java 创建的 XMLSignature 与 C# 不同
【发布时间】:2014-12-10 22:20:13
【问题描述】:

这个问题快把我逼疯了。我必须使用由 dotNet 端点应用程序验证的 Java 签署 SOAPMessage。供应商给了我们一个示例 dotNet 客户端应用程序作为示例,因此我受到了这个示例应用程序以及 Internet 上的几个链接的启发,如下所示:

http://www.java2s.com/Tutorial/Java/0410__Web-Services-SOA/SignSOAPmessage.htm

Java equivalent of C# XML signing method

正文(即签名内容)的结构是:

<Body>
    <inbound>
        <content>...text content...</content>
    </inbound>
</Body>

到目前为止,仅当文本内容不包含任何换行符时,我才能够生成相同的签名(使用我的代码和供应商提供的示例代码)。一旦我的文本包含行分隔符,签名值就不再相同了。

经过几天的搜索,我认为问题与规范化有关。

在Java中,以下代码:

...
Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
Element body = getNextSiblingElement(header);
byte outputBytes[] = c14n.canonicalizeSubtree(body);
FileUtils.writeByteArrayToFile(new File("C:\\tmp\\Canonicalized_java.xml"),outputBytes);
...

生产:

<s:Body xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" Id="uuid"><inbound><content>ABC&#xD;DEF&#xD;HIJ&#xD;</content></inbound></s:Body>

而在dotNet(c#)中,代码如下:

...
using (MemoryStream nodeStream = new MemoryStream())
{
    XmlWriterSettings ws = new XmlWriterSettings();
    ws.NewLineHandling = NewLineHandling.None;
    using (XmlWriter xw = XmlWriter.Create(nodeStream, ws))
    {
        soapRequest.GetElementsByTagName("Body")[0].WriteTo(xw);
        xw.Flush();
    }
    nodeStream.Position = 0;

    XmlDsigExcC14NTransform transform = new XmlDsigExcC14NTransform();
    transform.LoadInput(nodeStream);
    using (MemoryStream outputStream = (MemoryStream)transform.GetOutput(typeof(Stream)))
    {
        File.WriteAllBytes("C:\\tmp\\Canonicalized_dotNet.xml", outputStream.ToArray());
    }
}
...

生产:

<s:Body xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" Id="uuid"><inbound><content>ABC
DEF
HIJ
</content></inbound></s:Body>

换行符仅是 LF。 (注意,原来的内容只包含 CR 作为换行符)。

如果需要,我可以提供更多源代码,但也许有人确切地知道这里发生了什么。任何帮助将不胜感激。请注意,我无法更改 dotNet 端点应用程序验证 XML 签名值的方式。

编辑 以下是消息正文的构建方式。我不明白为什么这不会产生与 Aaryn 的答案相同的结果:

public static void main(String[] args) throws Exception { 
    com.sun.org.apache.xml.internal.security.Init.init();
    SOAPMessage soapMessage = MessageFactory.newInstance().createMessage();
    SOAPEnvelope soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
    SOAPBody soapBody = soapEnvelope.getBody();
    SOAPElement inboundMessage = soapBody.addChildElement("inbound");
    SOAPElement payload = inboundMessage.addChildElement("content");
    payload.addTextNode("ABC\rDEF\rGHI\r");
    Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    byte outputBytes[] = c14n.canonicalizeSubtree(soapBody);
    System.out.println(new String(outputBytes));
}

输出:

<SOAP-ENV:Body xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><inbound><content>ABC&#xD;DEF&#xD;GHI&#xD;</content></inbound></SOAP-ENV:Body>

编辑 2 我做了更多测试,尝试在运行时构建 XML,而不是解析输入字符串。这是我的最后一次试验:

public static void main(String[] args) throws Exception {
    com.sun.org.apache.xml.internal.security.Init.init();
    Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
    Element body = doc.createElement("Body");
    Element inbound = doc.createElement("inbound");
    Element content = doc.createElement("content");
    content.appendChild(doc.createTextNode("ABC\rDEF\rGHI\r"));
    doc.appendChild(body);
    body.appendChild(inbound);
    inbound.appendChild(content);
    Canonicalizer c14n =   Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
    byte outputBytes[] = c14n.canonicalizeSubtree(body);
    System.out.println(new String(outputBytes));
}

而且输出仍然是

<Body><inbound><content>ABC&#xD;DEF&#xD;GHI&#xD;</content></inbound></Body>

我真的不明白。为什么 CR 被 &amp;#xD; 代替而不是 LF 就像当文档是从输入字符串的解析中构建的一样?

【问题讨论】:

    标签: java c#


    【解决方案1】:

    如果您发布的文档是您的实际输入,那么您的两种规范化可能都会出现问题。在这种情况下,它们不会保留开始标签和结束标签之间的前导空格。在规范化文档之前,您是否对文档进行了任何类型的处理?在一个简单的测试程序中:

    public static String test = "<Body>\n"
        + "    <inbound>\n"
        + "        <content>...text\r content\n...</content>\n"
        + "    </inbound>\n"
        + "</Body>";
    
    public static void main(String[] args) throws Exception {
        com.sun.org.apache.xml.internal.security.Init.init();
        Canonicalizer c14n = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
        Element body = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(test.getBytes())).getDocumentElement();
        byte outputBytes[] = c14n.canonicalizeSubtree(body);
        System.out.println(new String(outputBytes));
    }
    

    空格的输出保持原样(受行分隔符规范化)。我相信您在 Java 规范化之前的代码中正在做一些事情,这正在逃避您的回车。 XML 规范化的空白保留令大多数人感到惊讶,请参阅W3C Canonicalization Specs。在我第一次处理它时,它肯定让我绊倒了。

    您使用的是XML Digital signatures 还是自定义签名?如果是 W3C,您应该能够使用原生数字签名库。

    响应编辑 1&2

    Document.createTextNode(String data) 方法为您转义数据。这是为了防止您创建无效的 xml 文档(它还会转义 >、

        String text = "ABC\rDEF\rGHI\r";
        payload.addTextNode(text.replace("\r", "\n"));
    

    Using CDATA sections might also be a solution for you.

    【讨论】:

    • 我的结果和你的代码一样。我更改了最初的问题,以添加我的 Body 的构建方式,以及出现问题的原因。
    • 感谢您的宝贵时间。你真的拯救了我的圣诞节时间。我还有 3 天的时间来寻找解决方案,它会一直困扰我直到 1 月份。我们今天早上进行了官方测试,签名被点网端点应用程序成功验证!说真的,全心全意,谢谢!
    • 感谢您告诉我!我很高兴能提供帮助。
    猜你喜欢
    • 1970-01-01
    • 2012-05-26
    • 1970-01-01
    • 2013-11-15
    • 1970-01-01
    • 2011-05-19
    • 1970-01-01
    • 2015-02-21
    • 1970-01-01
    相关资源
    最近更新 更多