如何在Java中没有空格和线路破坏的情况下产生XML签名?
-
12-10-2019 - |
题
我与巴西人一起工作”nota财政eltronica“项目,在其中定义了签署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);
下面是由此产生的签名(片段):
<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>
因为在后一种情况下,白空间(线路断开)位于两个标签之间,或者换句话说,将其放置在仅元素标签内。
其他提示
签名块将二进制信息编码为base64, 必须 遵循一些格式,包括线路休息(请参阅 http://en.wikipedia.org/wiki/base64)。因此,如果没有更改信息,您根本无法将它们删除。
减少网络流量的一种更好的方法是在发送数据之前使用Comression。
我找到了一个(可耻的)解决方案。
但是,这不是预期的解决方案:用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的解决方案,因为此代码已经非常成熟,而且我们不希望更改整个签名实现。
我们只需要为“ ignoreline breaks”参数设置“ true”值
这是要避免或删除线路破坏的代码
Field f = XMLUtils.class.getDeclaredField("ignoreLineBreaks");
f.setAccessible(true);
f.set(null, Boolean.TRUE);
然后,我们可以确保使用下一个代码行的新值是正确的
System.err.println(XMLUtils.ignoreLineBreaks());
我遇到了同样的问题,这对我有用。
你可以试试:
System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");
XML签名符号是XML文档的一部分,以给定元素(即DOM中的子树)开始使用C14N算法归一化。您使用的标准C14N算法您使用固定线路断裂和白色空间(请参阅 http://www.w3.org/tr/xml-c14n#example-whitespaceincontent).
因此,原始文档的签名部分中的所有线路断裂(包括数据的最后标签和 <Signature>
标签,之间 </Signature>
和下一个关闭标签) *必须 保留以免改变签名。线路断裂和空间 Signature
元素本身并不重要,并且可以在不更改签名的情况下删除。
这里一个示例:
<root id="signedpart">
<data>
...
</data>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<Reference URI="#signedpart">
...
</Reference>
</SignedInfo>
</Signature>
</root>
这是您可能的选择:
定义您自己的C14N算法,该算法将消除空间并自我断裂。我会劝阻这一点,因为另一侧还必须使用此非标准C14N算法。
删除线路从您的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>