【问题标题】:How to produce XML signature with no whitespaces and line-breaks in Java?如何在 Java 中生成没有空格和换行符的 XML 签名?
【发布时间】:2011-06-11 07:52:18
【问题描述】:

我与巴西的“Nota Fiscal Eletronica”项目合作,他们在该项目中定义了一种标准的方式来签署 XML 文档。

最近,他们开始要求标签之间绝对不能有空格,包括签名标签(*)。

我们碰巧使用了 apache 的 XMLSignature,但我似乎无法生成未缩进的签名。

如果我在签名后删除空格,签名就会被破坏。

我也无法更改规范化器/转换器集,因为它们是预定义的。

我在 XMLSignature API 中找不到用于控制缩进或空格的选项或参数。

下面是代码:

    // the element where to insert the signature
    Element element = ...;
    X509Certificate cert = ...;
    PrivateKey privateKey = ...;

    XMLSignature signer =
            new XMLSignature(doc, "http://xml-security",
            XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);

    element.appendChild(signer.getElement());

    Transforms transforms = new Transforms(doc);

    // Define as regras de transformação e canonicalização do documento
    // XML, necessário para fazer a verificação do parsing e da
    // assinatura pelos destinatários
    transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); //, xpath.getElementPlusReturns());

    transforms.addTransform(Transforms.TRANSFORM_C14N_OMIT_COMMENTS); //,xpath.getElementPlusReturns());

    String id = "";

    id = ((Element) element.getElementsByTagName("infNFe").item(0)).getAttributeNode("Id").getNodeValue();

    signer.addDocument("#" + id, transforms, 
                       Constants.ALGO_ID_DIGEST_SHA1);
    signer.addKeyInfo(cert);
    signer.sign(privateKey);

下面是生成的签名(sn-p):

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#NFe43110189716583000165550010000076011492273645">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>fas0ra5uRskQgRHSrIYhEjFEjKQ=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>
2RGltUZy0HfNoiKtVanAeN+JUPyglWDuQNnMudSgA7kESoHBZ/q/GMbc+xMSN1eV8u7+2PxSKl1T
Zl592FWmCSAkL8pwMujDxJ4iTLU20Hf0dNF7oGcyB+g9GgbipW2udq0kwJLz6HzXUD/Evf/0y+3T
NtsXeIaA6A29ttD/UEs=
</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>
MIIFqTCCBJGgAwIBAgIEQeNSuzANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJicjETMBEGA1UE
ChMKSUNQLUJyYXNpbDEgMB4GA1UECxMXQ2FpeGEgRWNvbm9taWNhIEZlZGVyYWwxFDASBgNVBAMT
C0FDIENBSVhBIFBKMB4XDTEwMDYwODE5MjQwNVoXDTExMDYwODE5NTQwNVowgYQxCzAJBgNVBAYT
AmJyMRMwEQYDVQQKEwpJQ1AtQnJhc2lsMSAwHgYDVQQLExdDYWl4YSBFY29ub21pY2EgRmVkZXJh
bDEUMBIGA1UECxMLQUMgQ0FJWEEgUEoxKDAmBgNVBAMTH0EgQlVITEVSIFNBIENVUlRVTUU6NDA5
NDI0OTAwMTAwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOFxgvG35RQWgXec4zVrzoUHolnJ
fP76rpO2Vo40593W9Gf0WwHt36gVmli0ZeQitFmzFSoE5KhgXQGZg6RpV3WJUFcIrPBHPdqOSfiB
988kf962P+j8fZ38BNmo7TV9H9hMBkV9bD/QOe73wFDc+rT6/9io++Z+7/wup/3glKntAgMBAAGj
ggLOMIICyjAOBgNVHQ8BAf8EBAMCBeAwVwYDVR0gBFAwTjBMBgZgTAECAQkwQjBABggrBgEFBQcC
ARY0aHR0cDovL2ljcC5jYWl4YS5nb3YuYnIvcmVwb3NpdG9yaW8vZHBjYWNjYWl4YXBqLnBkZjAp
BgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwgbYGA1UdEQSBrjCBq4EV
YnVobGVyQGFidWhsZXIuY29tLmJyoD4GBWBMAQMEoDUEMzE0MDkxOTQ2NDA5NDI0OTAwMTAxMDg0
NDcwODE3NTAwMDAwODAzMjkyMjM1NlNTUCBSU6AeBgVgTAEDAqAVBBNOQUlSIEJVSExFUiBTQ0hO
RUNLoBkGBWBMAQMDoBAEDjg5NzE2NTgzMDAwMTY1oBcGBWBMAQMHoA4EDDAwMDAwMDAwMDAwMDCC
ATIGA1UdHwSCASkwggElMIGuoIGroIGohjJodHRwOi8vaWNwLmNhaXhhLmdvdi5ici9yZXBvc2l0
b3Jpby9BQ0NBSVhBUEoxLmNybIY0aHR0cDovL2ljcDIuY2FpeGEuZ292LmJyL3JlcG9zaXRvcmlv
Mi9BQ0NBSVhBUEoxLmNybIY8aHR0cDovL3JlcG9zaXRvcmlvLmljcGJyYXNpbC5nb3YuYnIvbGNy
L2NhaXhhL0FDQ0FJWEFQSjEuY3JsMHKgcKBupGwwajELMAkGA1UEBhMCYnIxEzARBgNVBAoTCklD
UC1CcmFzaWwxIDAeBgNVBAsTF0NhaXhhIEVjb25vbWljYSBGZWRlcmFsMRQwEgYDVQQDEwtBQyBD
QUlYQSBQSjEOMAwGA1UEAxMFQ1JMNDEwHwYDVR0jBBgwFoAUjkAvCv4T1ao5oHZ0htO8fcfx5c8w
CQYDVR0TBAIwADAZBgkqhkiG9n0HQQAEDDAKGwRWNy4xAwIDqDANBgkqhkiG9w0BAQUFAAOCAQEA
nZHUvdnZsiCIDjKm1zHehbtuDtDJha4O4FZ03J74Y+AxyAFs/4JED+xUvZ5jFuEsdqgA0V/dxUFy
Uz/ca10Ievd578GQdGwYl1GFhRtO/SlxeaOEf7eDdGOWXO3VmUA3NmNo0X8RRTIoifnhpDXu7RbN
5sijyH/uXyRFWX9XH2N0U/r3oJtNKXsvoUlbDrkalgkuLzLKsaEj0TkwisXO3cmMoWGuBpAZC+46
e4x/2vTqOvYkzZO+O9NLi0YWSYY7OJKiKBjMC6MzdlPM9VTkIwO9WvWEMdbU0/jhO2cMcVMzNZc1
r6ZmdTDrwqV3elSTkQtJ0RIZNgMJUn+Y8c7Aog==
</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>

注意(不需要的)换行符。

任何帮助将不胜感激。

非常感谢。

(*) 澄清:新规则禁止元素标签之间的空格(或任何其他文本)。例如,这将是允许的

<a><b>
  text
  inside
  tag
</b></a>

虽然这是禁止

<a>
<b>text</b>
</a>

因为在后一种情况下,空格(换行符)位于两个标签之间,或者换句话说,位于仅元素标签内。

【问题讨论】:

  • 问题是从 XML 和 XMLDSig 的角度来看,他们的要求没有意义。他们很可能在代码中犯了一些错误,现在他们不想确认错误并修复代码,而是希望其他人处理他们的错误实现。
  • 我支持@Eugene Mayevski 'EldoS Corp 。我已经像上面那样处理了xml,没有任何问题。
  • 他们声称他们制定此规则是为了减少网络流量(实际上是巨大的),因为某些软件包含滥用数量的缩进空格。他们能够成功验证签名。应用了另一个(不相关且新的)规则,该规则会导致消息被拒绝。不过,我同意他们不应该对签名部分施加这种限制。
  • 无论是他们的错与否(我同意很可能是他们的错),都不会改变 Jonathas 的要求。 +1 的问题,我想知道解决方案是什么。

标签: java xml apache signature xml-signature


【解决方案1】:

你可以试试:

static {
    System.setProperty("com.sun.org.apache.xml.internal.security.ignoreLineBreaks", "true");
    com.sun.org.apache.xml.internal.security.Init.init();
}

或者

static {
    System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");
    org.apache.xml.security.Init.init();
}

将此添加到执行签名工作的类中。

【讨论】:

    【解决方案2】:

    你可以试试:

    System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");
    

    【讨论】:

    • 这对我没有影响。仍然得到“ ”在每个证书和签名行之后
    • 该属性在 org.apache.xml.security.utils.XMLUtils 中进行评估。如果 org.apache.xml.securit.Init.init() 被调用,这个类将被加载。您必须在调用 init() 方法之前设置此属性。将此属性设置为 true 时,将使用 RFC4648 Base64 编码器,该编码器不会插入将被编码为“ ”的 clrfs(如默认的 RFC2045 Base64 Mime 编码器)
    【解决方案3】:

    我们只需要将“ignoreLineBreaks”参数设置为“true”值, 因为'默认值为false,这允许签名API添加LineBreaks

    这里是避免或删除 LineBreaks 的代码

    Field f = XMLUtils.class.getDeclaredField("ignoreLineBreaks");
    f.setAccessible(true);
    f.set(null, Boolean.TRUE);
    

    然后,我们可以在下一行代码中确保新值为真

    System.err.println(XMLUtils.ignoreLineBreaks());
    

    我遇到了同样的问题,这对我有用。

    【讨论】:

    • 这行得通,但是 WTF,这对 XMLdSig API 开发人员来说太丢人了!
    【解决方案4】:

    在使用 C14N 算法对其进行规范化后,XML 签名以给定元素(即 DOM 中的子树)开始对 XML 文档的一部分进行签名。您使用的标准 C14N 算法保留换行符和空格(请参阅http://www.w3.org/TR/xml-c14n#Example-WhitespaceInContent)。

    所以原始文档的签名部分中的所有换行符(包括数据的最后一个标签和&lt;Signature&gt;标签之间,以及&lt;/Signature&gt;和下一个结束标签之间)*必须保存,以免更改签名。 Signature 元素本身中的换行符和空格并不重要,可以在不更改签名的情况下删除。

    这里是一个例子:

    <root id="signedpart">
      <data>
         ...
      </data>
      <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
         <SignedInfo>
           <Reference URI="#signedpart">
              ...
           </Reference>
         </SignedInfo>
      </Signature>
    </root> 
    

    以下是您可能的选择:

    1. 定义您自己的 C14N 算法,该算法将自行删除空格和换行符。我不鼓励这样做,因为对方也必须使用这种非标准 C14N 算法。

    2. 从您的 XML 签名之前删除换行符(并可能在之后删除签名中的空格)

    通过示例,这将为您提供以下签名的 XML:

    <root id="signedpart"><data>...</data><Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
           <Reference URI="#signedpart">
              ...
           </Reference>
         </SignedInfo>
      </Signature></root>
    

    去掉签名中的空格

    <root id="signedpart"><data>...</data><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><Reference URI="#signedpart">...</Reference></SignedInfo></Signature></root>
    

    【讨论】:

    • 选项 1。我不允许这样做,因为规范化器和转换是预定义的,无法更改。
    • 选项2。如果我之后从签名中删除空格,签名不可避免地会被破坏。我认为唯一的方法是直接生成没有换行符的签名。
    • 我自己测试了它(使用 apache xmlsec 1.4.4)并删除换行符和空格 标记之间的空格不会改变它。
    • 我们仔细检查了:我们得到了一个已经签名的 XML,在签名中带有换行符(不管用于生成它的软件),在从 标记中删除单个换行符之后,签名被破坏了。我们使用 xmlsec 1.4.2。你的签名是用换行符产生的吗?如果不是,这可能是版本问题。
    • 使用 xmlsec 1.4.4 生成的签名也会产生换行符,但我可以在没有中断签名的情况下删除它们,这并不奇怪,因为签名显然不是签名内容的一部分
    【解决方案5】:

    您可以简单地设置 -Dorg.apache.xml.security.ignoreLineBreaks=true 来禁用 XML 生成中的 '\n'。 original mail

    bug description

    【讨论】:

    • 我无法测试此解决方案,因为在您回答时,我已经通过选择另一个签名 API 应用了解决方案。但我查看了链接,这似乎正是我一直在寻找的简单且创伤小得多的解决方案。
    • @JonathasCarrijo 你选择了什么签名 API?我正在研究德国 EBICS 协议的解决方案,我正面临这个问题,因为银行无法接受多行 SignatureValue 标签数据。
    【解决方案6】:

    我找到了一个(可耻的)解决方案。

    但这不是预期的解决方案:用 javax.xml.crypto API 替换 apache 的 API。

    这是修改后的代码:

    // the element where to insert the signature
    Element element = ...;
    X509Certificate cert = ...;
    PrivateKey privateKey = ...;
    // Create a DOM XMLSignatureFactory that will be used to
    // generate the enveloped signature.
    XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
    
    // Create a Reference to the enveloped document (in this case,
    // you are signing the whole document, so a URI of "" signifies
    // that, and also specify the SHA1 digest algorithm and
    // the ENVELOPED Transform.
    List<Transform> transformList = new ArrayList<Transform>();
    TransformParameterSpec tps = null;
    Transform envelopedTransform;
    try {
        envelopedTransform = fac.newTransform(Transform.ENVELOPED,
                tps);
        Transform c14NTransform = fac.newTransform(
                "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", tps);
    
        transformList.add(envelopedTransform);
        transformList.add(c14NTransform);
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
    } catch (InvalidAlgorithmParameterException e) {
        throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
    }
    
    // Create the KeyInfo containing the X509Data.
    KeyInfoFactory kif = fac.getKeyInfoFactory();
    List<Serializable> x509Content = new ArrayList<Serializable>();
    x509Content.add(cert);
    javax.xml.crypto.dsig.keyinfo.X509Data xd = kif.newX509Data(x509Content);
    KeyInfo ki = kif.newKeyInfo(Collections.singletonList(xd));
    
    // Obtem elemento do documento a ser assinado, será criado uma
    // REFERENCE para o mesmo
    Element el = (Element) element.getElementsByTagName(subTag).item(0);
    String id = el.getAttribute("Id");
    
    // Create a DOM XMLSignatureFactory that will be used to
    // generate the enveloped signature.
    
    Reference ref;
    javax.xml.crypto.dsig.SignedInfo si;
    try {
        ref = fac.newReference("#" + id, fac.newDigestMethod(
                DigestMethod.SHA1, null), transformList, null, null);
    
        // Create the SignedInfo.
        si = fac.newSignedInfo(fac.newCanonicalizationMethod(
                CanonicalizationMethod.INCLUSIVE,
                (C14NMethodParameterSpec) null), fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null),
                Collections.singletonList(ref));
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
    } catch (InvalidAlgorithmParameterException e) {
        throw new RuntimeException("Erro inesperado: " + e.getMessage(), e);
    }
    
    // Create the XMLSignature, but don't sign it yet.
    javax.xml.crypto.dsig.XMLSignature signature = fac.newXMLSignature(si, ki);
    
    // Marshal, generate, and sign the enveloped signature.
    // Create a DOMSignContext and specify the RSA PrivateKey and
    // location of the resulting XMLSignature's parent element.
    DOMSignContext dsc = new DOMSignContext(privateKey, element);
    signature.sign(dsc);
    

    此 API 生成的签名完全没有标签之间的空格。

    仍然希望看到 apache 的 API 的解决方案,因为该代码已经非常成熟,我们不希望冒险更改整个签名实现。

    【讨论】:

    • 我正在使用 javax.xml.crypto.dsig.SignedInfo 和 javax.xml.crypto.dsig.XMLSignature 但生成的证书被添加到 x509Content 和 SignatureValue 都包含“ "在每一行之后。
    • @user3217883 我遇到了同样的问题。你找到解决办法了吗?
    • 对不起,我已经过了太多时间来记住细节。我可能刚刚做了一个 java 替换,比如:signatureValue.replace(" ",'');
    【解决方案7】:

    幸运的是XMLSignature is opensource,所以我猜你必须获取源代码并自己破解它。

    您的解决方案可能会在未来对其他人有所帮助,因此请创建一个补丁并将其发送回项目。

    祝你好运!

    :) :)

    【讨论】:

      【解决方案8】:

      签名块将二进制信息编码为 Base64,必须遵循一些格式,包括换行符(请参阅http://en.wikipedia.org/wiki/Base64)。因此,您根本无法在不更改信息的情况下删除它们。

      减少网络流量的更好方法是在发送数据之前使用压缩。

      【讨论】:

      • +1,很好的答案。维基百科文章指出有最大行长度和行分隔符。使用压缩是解决此问题的正确方法。
      • 新规则只在标签之间禁止空格。叶标签内的文本(例如 base64 编码信息)可能包含空格。我将更新问题以澄清这一点。
      • 所以我们想念我们。在这种情况下,你可能会得到它的工作(见我的另一个答案)。
      • 如果它们被转换为 base64 不应该不会有任何新行和空白。 Base64 不包含该内容。我想错了吗?
      • 换行符不是 Base64 的一部分(正如@SurajJain 所说)。当内容被解码时,它们会被简单地忽略。删除换行符或 来自 SignatureValue 不应更改签名。
      猜你喜欢
      • 1970-01-01
      • 2012-07-27
      • 1970-01-01
      • 2020-03-31
      • 2020-08-29
      • 1970-01-01
      • 1970-01-01
      • 2016-01-05
      • 1970-01-01
      相关资源
      最近更新 更多