数字签名说明
在金融渠道工作时候有的银行使用的W3C的xml signature。使用中有一些难度,比正常使用RSA加签验签,w3c xml signature有现成的开源代码,但是不推荐使用jdk自带的,不通jdk版本兼容不行。
签名规范遵循XML-Signature Syntax and Processing, W3C Recommendation规范(http://www.w3.org/TR/xmldsig-core/)。
Finance协议使用分离签名(Detached signature),即元素与被签名的元素(如:CSReq/CSRes等)各自独立存在。被签名的元素和元素包含在同一文档中。签名元素通过当地引用(如’# CSReq1234’)被引用。被签名的元素内容包括从CSReq/CSRes等开始标签的开始括号开始到CSReq/CSRes等结束标签的结束括号为止的内容。
签名结构图
签名要求
元素 要求
Signature 没有KeyInfo实例;没有Object实例
CanonicalizationMethod 元素为空,但出现Algorithm属性
SignatureMethod 元素为空,但出现Algorithm属性
Transforms 存在且都包含一个Transform的实例
Transform 元素为空,但出现Algorithm属性
DigestMethod 元素为空,但出现Algorithm属性
KeyInfo 不出现。
Canonicalization http://www.w3.org/TR/2001/REC-xml-cl4n-20010315
Digest http://www.w3.org/2000/09/xmldsig#shal
Encoding http://www.w3.org/2000/09/xmldsig#base64
Signature http://www.w3.org/2000/09/xmldsig#rsa-sha1
Transform http://www.w3.org/2000/09/xmldsig#enveloped-signature
签名命名空间
消息的签名必须被声明在一个缺省的命名空间:http://www.w3.org/2000/09/xmldsig#中。
签名例子
< Finance>
<Message id="111">
<CSReq id="CSReq">
<version>1.0.1</version>
<date>20070625 11:51:39</date>
<instId>WZCB</instId>
<certId>123456</certId>
<signNo>200705131234567890</signNo>
<cardNo>123456</cardNo>
<cardType>D</cardType>
<name>张三</name>
<gender>M</gender>
<certType>1</certType>
<certNo>111111111111111</certNo>
<email>zhangsan@msn.com</email>
<cell>13588888888</cell>
<address>杭州市西湖区</address>
<memo>测试</memo>
</CSReq>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315">
</CanonicalizationMethod>
<SignatureMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1">
</SignatureMethod>
<Reference URI="#CSReq">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature">
</Transform>
</Transforms>
<DigestMethod
Algorithm="http://www.w3.org/2000/09/xmldsig#sha1">
</DigestMethod> <DigestValue>4t79dXZ7/BQqgiBdkziaKTUslVU=</DigestValue>
</Reference>
</SignedInfo> <SignatureValue>RyWMXvxlhmLuOIOvGSIkpV1iRV400F1B2W0zmW9nc5qfvfNMtpyp+uNwksBUjcO8H//NW4GvxcDdKMuuc6k5MDMH1E4OUg+624FUY23qs3R2ztubtD3MU7xk4f0iq9L16GK4ZBeID/Lyj6CxjaCcp3FuK1CznNj4Kr+qRLtxx+s= </SignatureValue>
</Signature>
</Message>
</ Finance>
代码实现
- jar包,不推荐 使用sun jdk自带,在IBMjdk环境编译不过
xmlsec xercesImpl
- 代码1 OfflineResolver
import org.apache.xerces.util.URI;
import org.apache.xml.security.Init;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.utils.resolver.ResourceResolverException;
import org.apache.xml.security.utils.resolver.ResourceResolverSpi;
import org.w3c.dom.Attr;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
public class OfflineResolver extends ResourceResolverSpi
{
public OfflineResolver()
{
}
public XMLSignatureInput engineResolve(Attr uri, String BaseURI)
throws ResourceResolverException
{
try
{
String URI = uri.getNodeValue();
if(_uriMap.containsKey(URI))
{
String newURI = (String)_uriMap.get(URI);
InputStream is = new FileInputStream(newURI);
XMLSignatureInput result = new XMLSignatureInput(is);
result.setSourceURI(URI);
result.setMIMEType((String)_mimeMap.get(URI));
return result;
} else
{
Object exArgs[] = {
"The URI " + URI + " is not configured for offline work"
};
throw new ResourceResolverException("generic.EmptyMessage", exArgs, uri, BaseURI);
}
}
catch(IOException ex)
{
throw new ResourceResolverException("generic.EmptyMessage", ex, uri, BaseURI);
}
}
public boolean engineCanResolve(Attr uri, String BaseURI)
{
String uriNodeValue = uri.getNodeValue();
if(uriNodeValue.equals("") || uriNodeValue.startsWith("#"))
return false;
try
{
URI uriNew = new URI(new URI(BaseURI), uri.getNodeValue());
if(uriNew.getScheme().equals("http"))
{
return true;
}
}
catch(URI.MalformedURIException malformeduriexception) { }
return false;
}
private static void register(String URI, String filename, String MIME)
{
_uriMap.put(URI, filename);
_mimeMap.put(URI, MIME);
}
static Map<String, String> _uriMap = null;
static Map<String, String> _mimeMap = null;
static
{
// TODO : 注意此处的静态初始化!必须,否则第一次不加载会出现//加签问题
Init.init();
_uriMap = new HashMap<String, String>();
_mimeMap = new HashMap<String, String>();
register("http://www.w3.org/TR/xml-stylesheet", "data/org/w3c/www/TR/xml-stylesheet.html", "text/html");
register("http://www.w3.org/TR/2000/REC-xml-20001006", "data/org/w3c/www/TR/2000/REC-xml-20001006", "text/xml");
register("http://www.nue.et-inf.uni-siegen.de/index.html", "data/org/apache/xml/security/temp/nuehomepage", "text/html");
register("http://www.nue.et-inf.uni-siegen.de/~geuer-pollmann/id2.xml", "data/org/apache/xml/security/temp/id2.xml", "text/xml");
register("http://xmldsig.pothole.com/xml-stylesheet.txt", "data/com/pothole/xmldsig/xml-stylesheet.txt", "text/xml");
register("http://www.w3.org/Signature/2002/04/xml-stylesheet.b64", "data/ie/baltimore/merlin-examples/merlin-xmldsig-twenty-three/xml-stylesheet.b64", "text/plain");
}
}
- 代码2, 签名、验签
import org.apache.commons.io.IOUtils;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;
import org.apache.xpath.XPathAPI;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
public class XmlSignature {
/**
* 加签
* @param document
* @param messageType
* @param privateKey
* @param password
* @return
* @throws Exception
*/
public static String generateXMLDigitalSignature1(Document document, String messageType, PrivateKey privateKey, String password) throws Exception {
ByteArrayOutputStream os = null;
// 如果document直接是W3C的无须转换
org.w3c.dom.Document doc = parse(document);// 标准转换DOM4J >>> W3C
XMLSignature sig = new XMLSignature(doc, doc.getDocumentURI(), XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1);// 设置各个节点参数
sig.getSignedInfo().addResourceResolver(new OfflineResolver());
Node messageNode = doc.getElementsByTagName("Message").item(0);
messageNode.appendChild(sig.getElement());
Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
// 针对哪个节点加签
sig.addDocument("#" + messageType, transforms, Constants.ALGO_ID_DIGEST_SHA1);
sig.sign(privateKey);
os = new ByteArrayOutputStream();// 将签名好的XML文档写出
XMLUtils.outputDOM((Node) doc, os);
return os.toString("UTF-8");
}
/**
* 验签
* @param signatureXml
* @param publicKeyByte
* @return
* @throws Exception
*/
public static boolean validateXMLDigitalSignature(String signatureXml, byte[] publicKeyByte) throws Exception {
ByteArrayInputStream is = null;
try {
CertificateFactory certificatefactory;
certificatefactory = CertificateFactory.getInstance("X.509");
// 将二进制转换为文件
is = new ByteArrayInputStream(publicKeyByte);
X509Certificate cert = (X509Certificate) certificatefactory.generateCertificate(is);
PublicKey pk = cert.getPublicKey();
org.w3c.dom.Document doc = (org.w3c.dom.Document) parse(DocumentHelper.parseText(signatureXml));
Element nscontext = XMLUtils.createDSctx(doc, "ds", "http://www.w3.org/2000/09/xmldsig#");
Element signElement = (Element) XPathAPI.selectSingleNode(doc, "//ds:Signature[1]", nscontext);
// 判断是否加密
if (signElement == null) {
return false;
}
XMLSignature signature;
signature = new XMLSignature(signElement, doc.getDocumentURI());
return signature.checkSignatureValue(pk);
} catch (TransformerException e) {
throw new Exception("变化异常!Exception is : " + e);
} catch (XMLSignatureException e) {
throw new Exception("XML验签异常!Exception is : " + e);
} catch (XMLSecurityException e) {
throw new Exception("XML证书异常!Exception is :" + e);
} catch (CertificateException e) {
throw new Exception("证书异常!Exception is :" + e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
throw new Exception("XML证书异常!Exception is : " + e);
}
}
}
}
public static PrivateKey getPrivateKey(byte[] privateKeyStream, String privateKeyPassword,String keyAlias) throws Exception {
if (privateKeyStream == null) {
throw new RuntimeException("nullPointException: privateKey byte is null");
}
if (privateKeyPassword == null) {
throw new RuntimeException("nullPointException: privateKeyPassword is null");
}
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream inputStream = new ByteArrayInputStream(privateKeyStream);
keyStore.load(inputStream, privateKeyPassword.toCharArray());
PrivateKey p =(PrivateKey) keyStore.getKey(keyAlias, privateKeyPassword.toCharArray());
return p;
}
public static PublicKey getPublicKey(byte[] publicKeyStream) throws Exception {
if (publicKeyStream == null) {
throw new RuntimeException("nullPointException: publicKey byte is null");
}
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream inputStream = new ByteArrayInputStream(publicKeyStream);
Certificate c = cf.generateCertificate(inputStream);
return c.getPublicKey();
}
/**
* 实现dom4j向org.w3c.dom.Document的转换
* @param doc
* @return
* @throws
*/
public static org.w3c.dom.Document parse(Document doc) throws Exception{
if (doc == null) {
return (null);
}
InputStream is = null;
try {
is = IOUtils.toInputStream(doc.asXML(), "UTF-8");
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
is.close();
return docBuilder.parse(is);
} catch (IOException e) {
throw new Exception("XML证书异常!Exception is : " + e);
} catch (SAXException e) {
throw new Exception("证书转换异常!Exception is : " + e);
} catch (ParserConfigurationException e) {
throw new Exception("证书转换异常!Exception is : " + e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
throw new Exception("XML证书异常!Exception is : " + e);
}
}
}
}
}