xml signature

数字签名说明
在金融渠道工作时候有的银行使用的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>

代码实现

  1. jar包,不推荐 使用sun jdk自带,在IBMjdk环境编译不过
    xmlsec xercesImpl
    1. 代码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");
    }
}
  1. 代码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);
                    }
                }
            }
        }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值