XML数字签名-Java语言签名实例

签名类

package com.test;

import java.io.File;
import java.io.FileInputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collections;
import java.util.List;

import javax.xml.crypto.dom.DOMStructure;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.crypto.dsig.DigestMethod;
import javax.xml.crypto.dsig.Reference;
import javax.xml.crypto.dsig.SignatureMethod;
import javax.xml.crypto.dsig.SignedInfo;
import javax.xml.crypto.dsig.Transform;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.crypto.dsig.XMLSignatureFactory;
import javax.xml.crypto.dsig.dom.DOMSignContext;
import javax.xml.crypto.dsig.dom.DOMValidateContext;
import javax.xml.crypto.dsig.keyinfo.KeyInfo;
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
import javax.xml.crypto.dsig.keyinfo.KeyValue;
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 签名  工具类  https://www.ibm.com/developerworks/cn/xml/x-cn-java6xmlsignature/
 * 类SignXML.java的实现描述:
 * @author Administrator 2017年8月7日 下午3:15:00
 */
public class SignXML {
  
   public void signatureXMLDocument(String docPath,PrivateKey privateKey,PublicKey publicKey) throws Exception {
       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       dbf.setNamespaceAware(true);
       //获取XML文档对象
       Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(docPath));
       this.signatureXMLDocument(doc,privateKey,publicKey);
   }

   /**
    * 生成签名文件方法
    * @param doc   文本对象
    * @param privateKey 私钥
    * @param publicKey  公钥
    * @throws Exception 异常
    */
   public void signatureXMLDocument(Document doc,PrivateKey privateKey,PublicKey publicKey) throws Exception {
       
       /*
        * XMLSignatureFactory 是与签名相关的 XML 元素对象的创建工厂
        */
       XMLSignatureFactory fac = XMLSignatureFactory.getInstance();
       /*
        * 创建 Reference
        * URI 参数指定为 "" 表示对整个 XML 文档进行引用;"#PeterPayment" 表示对 id为PeterPayment 元素引用
        * 摘要算法指定为 SHA1;
        * 这里将转换方式指定为 ENVELOPED,这样在对整个文档进行引用并生成摘要值的时候,<Signature> 元素不会被计算在内。
        */
       //指定 Transform 转化方式
       Transform envelopedTransform = fac.newTransform(Transform.ENVELOPED,(TransformParameterSpec) null);
       //指定  DigestMethod 摘要算法
       DigestMethod sha1DigMethod = fac.newDigestMethod(DigestMethod.SHA1,   null);
       //创建 Reference 并且 URI 为  “” 表示对整个 XML 文档进行引用;并且设置  DigestValue 摘要 
       Reference refToRootDoc = fac.newReference("", sha1DigMethod,Collections.singletonList(envelopedTransform), null, null);
       
       /*
        * 创建 <SignedInfo> 元素
        * 因为最终的数字签名是针对 <SignedInfo> 元素而生成的,所以需要指定该 XML 元素的规范化方法,
        * 以确定最终被处理的数据。这里指定为 INCLUSIVE_WITH_COMMENTS , 表示在规范化 XML 内容的时候会将 XML 注释也包含在内。
        * 至此,待签名的内容(<SignedInfo> 元素)已指定好,再只需要签名所使用的密钥就可以创建数字签名了。
        */
       CanonicalizationMethod c14nWithCommentMethod = 
           fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE_WITH_COMMENTS,(C14NMethodParameterSpec) null);
       //指定验证摘要算法  <SignatureMethod>
       SignatureMethod dsa_sha1SigMethod = fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);
       //<SignedInfo>元素是你的签名信息
       SignedInfo signedInfo = fac.newSignedInfo(c14nWithCommentMethod,dsa_sha1SigMethod,Collections.singletonList(refToRootDoc));
       
       /*
        * XML 字签名规范规定了多种在 <KeyInfo> 中指定验证密钥的方式,比如 <KeyName>,<KeyValue>,<X509Data>,<PGPData> 等等。
        * 这里使用 XML 数字签名规范规定必须实现的 <DSAKeyValue> 来指定验证签名所需的公共密钥。
        */
       //以公钥为参数创建 <KeyValue> 元素
       KeyInfoFactory keyInfoFac = fac.getKeyInfoFactory();
       KeyValue keyValue = keyInfoFac.newKeyValue(publicKey);
       //根据创建好的 <KeyValue> 元素创建 <KeyInfo> 元素:
       KeyInfo keyInfo = keyInfoFac.newKeyInfo(Collections.singletonList(keyValue));

       
       /*
        * 创建 <Signature> 元素
        * 前面已经创建好 <SignedInfo> 和 <KeyInfo> 元素,为了生成最终的数字签名,
        * 需要根据这两个元素先创建 <Signature> 元素,然后进行签名,
        * 创建出 <SignatureValue> 元素。
        */
       XMLSignature signature = fac.newXMLSignature(signedInfo, keyInfo);
       
       /*
        * XMLSignature 类中的 sign 方法用于对文档进行签名,在调用 sign 方法之前,
        * 还需要创建 DOMSignContext 对象,为方法调用提供上下文信息,
        * 包括签名所使用的私钥和最后生成的 <Signature> 元素所在的目标父元素:
        */
       //DOMSignContext对象用来生成DOM树。在创建数字签名的过程中,DOM树会被附上XML数字签名。DOMSignContext对象要求输入私钥和XML文档的根元素。
       DOMSignContext dsc = new DOMSignContext(privateKey, doc.getDocumentElement());   
       
       /*
        * 生成签名<SignatureValue>包含了实际的签名以及使用Base64加密的内容;
        * sign 方法会生成签名值,并作为元素值创建 <SignatureValue> 元素,
        * 然后将整个 <Signature> 元素加入为待签名文档根元素的直接子元素。
        */
       signature.sign(dsc);   
       
       /*
        * 数字签名生成之后,使用 JAX P的 XML 转换接口将签名后的 XML 文档输出,查看签名结果
        */
       TransformerFactory tf = TransformerFactory.newInstance();
       Transformer transformer = tf.newTransformer();
       DOMSource source=new DOMSource(doc);
       
       //控制条打印输出 source内容
       transformer.transform(source, new StreamResult(System.out)); 
       /*
        * 将source内容写入新文件new.xml中
        */
       StreamResult result = new StreamResult(new File("E://new.xml"));
       transformer.transform(source,result);

   }
   
   private void validate(String signedFile,PublicKey publicKey) throws Exception {
        //将 xml 对象转换  为  Documnet 对象,使用 JAXP 将签名后生成的 XML 文档解析
       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       dbf.setNamespaceAware(true);
       Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(signedFile));
       this.validate(doc,publicKey);
   }

   /**
    * 验证 XML对象签名<br/>
    * 步骤:<br/>
    *   1.得到XML文档和公钥。<br/>
    *   2.验证<SignedInfo> 元素的数字签名。<br/>
    *   3.计算<SignedInfo> 元素的摘要并对值进行比较<br/>
    * @param doc Document 对象
    * @param publicKey  公钥
    * @throws Exception 异常
    */
   private void validate(Document doc,PublicKey publicKey) throws Exception {
       /*
        * 所有与 XML 数字签名相关的信息都存放在 <Signature> 元素中,
        * 所以需要先取到 <Signature> 元素。由于前面在生成签名的时候将 <Signature> 元素存放为文档根元素的直接子元素,
        * 所以这里根据元素名 ”Signature” 进行搜索,搜索到的第一个元素即为 <Signature> 元素
        */
       NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS,"Signature");
       if (nl.getLength() == 0) {
           throw new Exception("Cannot find Signature element");
       }
       Node signatureNode = nl.item(0);

       /*
        * 使用 XMLSignatureFactory 类的 unmarshalXMLSignature 方法从 DOM 节点构造出 XMLSignature 对象,为下一步验证签名作准备。
        */
       XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
       XMLSignature signature = fac.unmarshalXMLSignature(new DOMStructure(signatureNode));
       
       // 在验证签名的过程中,需要创建 DOMValidateContext 对象来指定上下文信息,参数为前面获取到的验证公钥和 <Signature> 元素
       DOMValidateContext valCtx = new DOMValidateContext(publicKey,signatureNode);
       
       // 验证签名所需的所有信息都已就绪,开始使用XMLSignature 对象提供的接口进行验证
       boolean coreValidity = signature.validate(valCtx);

       //检查验证状态
       if (coreValidity == false) {
           System.err.println("Core validation failed");
           /*
            * 对 <SignedInfo> 签名值的验证
            *     1) 从 <KeyInfo> 元素中的 <KeyValue> 元素或者根据 <KeyInfo> 元素中指定的信息从外部获取用于验证数字签名的数据发送方公共密钥。
            *     2) 使用验证密钥将 <SignatureValue> 元素中的加密签名值解密,得到值 D
            *     3) 使用 <SignatureMethod> 元素指定的签名算法对规则化之后的 <SignedInfo> 元素计算摘要值,得到值 D’
            *     4) 判断 D 和 D’ 是否匹配,如果不匹配,则验证失败
            * 
            */
           boolean sv = signature.getSignatureValue().validate(valCtx);
           System.out.println("Signature validation status: " + sv);
           // <SignedInfo> 中每一个 <Reference> 进行验证  
           /*
            * 对 <SignedInfo> 中每一个 <Reference> 执行如下验证步骤:
            *     1) 应用指定的数据转换方法取得引用的数据对象
            *     2) 使用指定的摘要生成算法生成摘要值
            *     3) 将生成的摘要值同 <Reference> 中 <DigestValue> 元素包含的摘要值相比较,如果不匹配,则验证失败
            */
           List<?> refs = signature.getSignedInfo().getReferences();
           for (int i = 0; i < refs.size(); i++) {
               Reference ref = (Reference) refs.get(i);
               boolean refValid = ref.validate(valCtx);
               System.out.println("Reference[" + i + "] validity status: "   + refValid);
           }
       } else {
           System.out.println("Signature passed core validation");
       }
   }
   
   
   public static void main(String[] args) {
       SignXML signatureXML=new SignXML();
       try {
           
           RSAEncrypt rsaEncrypt= new RSAEncrypt();
           //rsaEncrypt.genKeyPair();

           //加载公钥
           try {
               rsaEncrypt.loadPublicKey(RSAEncrypt.DEFAULT_PUBLIC_KEY);
               System.out.println("加载公钥成功");
           } catch (Exception e) {
               System.err.println(e.getMessage());
               System.err.println("加载公钥失败");
           }

           //加载私钥
           try {
               rsaEncrypt.loadPrivateKey(RSAEncrypt.DEFAULT_PRIVATE_KEY);
               System.out.println("加载私钥成功");
           } catch (Exception e) {
               System.err.println(e.getMessage());
               System.err.println("加载私钥失败");
           }
           
           PrivateKey privateKey=rsaEncrypt.getPrivateKey();
           PublicKey publicKey=rsaEncrypt.getPublicKey();
           
           /**
            * param: source.xml是被签名的源文件
            * param: new.xml是签名完成后新生成的文件。通过验证此文件来确认源文件是否被篡改。
            */
           signatureXML.signatureXMLDocument("E:/signed.xml",privateKey,publicKey);
           signatureXML.validate("E:/new.xml",publicKey);
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

}

签名/验证工具类

package com.test;

import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 签名/验签工具类,包含以下功能
 * <pre>
 * 1. 获取RSA的公钥
 * 2. 获取RSA的私钥
 * 3. 进行RSA签名
 * 4. 进行RSA验签
 * </pre>
 * 
 * @author jin.xie
 * @version $Id: SignatureUtils.java, v 0.1 2016年2月1日 上午10:49:06 jin.xie Exp $
 */
public class SignatureUtils {
    static {
        //实例化 签名的各个参数 默认 xmls:ds
        org.apache.xml.security.Init.init();
    }

    public static void signatureXMLDocument(String docPath,PrivateKey privateKey) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        //获取XML文档对象
        Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(docPath));
        signXmlElement(privateKey,doc,"document",XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1,1);
    }

    /**
     * XML签名
     *
     * @param priKeyData 私钥数据,PKCS#8编码格式
     * @param xmlDocBytes XML文件内容, UTF-8编码
     * @param elementTagName 续签签名的Tag名称
     * @param algorithm 签名算法 {@link XMLSignature} 支持下列算法
     * <ul>
     * <li>XMLSignature.ALGO_ID_SIGNATURE_RSA</li>
     * <li>XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1</li>
     * <li>XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256</li>
     * <li>XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384</li>
     * <li>XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512</li>
     * </ul>
     * @param signatureAppendMode 签名节点的附加模式
     * {@link com.alipay.fc.cryptprod.common.service.facade.constant.XmlSignatureAppendMode}
     * <ul>
     * <li>作为子节点: XmlSignatureAppendMode.AS_CHILDREN</li>
     * <li>作为兄弟节点:XmlSignatureAppendMode.AS_BROTHER</li>
     * </ul>
     * @return 签名后的文档 string
     * @throws Exception the exception
     */
    public static String signXmlElement(PrivateKey privateKey, Document xmlDocument,
                                        String elementTagName, String algorithm,
                                        int signatureAppendMode) throws Exception {
        /**
         * 设置命名空间名 例如:xmls:ds
         */
        //Constants.setSignatureSpecNSprefix("ds");
        //生成签名对象
        XMLSignature xmlSignature = new XMLSignature(xmlDocument, xmlDocument.getDocumentURI(),
            algorithm);

        //获取签名元素
        NodeList nodeList = xmlDocument.getElementsByTagName(elementTagName);
        if (nodeList == null || nodeList.getLength() - 1 < 0) {
            throw new Exception("Document element with tag name " + elementTagName + " not fount");
        }
        Node elementNode = nodeList.item(0);
        if (elementNode == null) {
            throw new Exception("Document element with tag name " + elementTagName + " not fount");
        }
        //设置签名对象的位置
        if (signatureAppendMode == XmlSignatureAppendMode.AS_CHILDREN) {
            elementNode.appendChild(xmlSignature.getElement());
        } else if (signatureAppendMode == XmlSignatureAppendMode.AS_BROTHER) {
            elementNode.getParentNode().appendChild(xmlSignature.getElement());
        } else {
            throw new IllegalArgumentException("Illegal Append Mode");
        }
        //
        Transforms transforms = new Transforms(xmlDocument);
        transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
        /**
         * 设置给哪个节点加签
         */
        xmlSignature.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);
        xmlSignature.sign(privateKey);

        ByteArrayOutputStream os = null;
        try {
            os = new ByteArrayOutputStream();
            XMLUtils.outputDOM(xmlDocument, os);
            BufferedWriter out = new BufferedWriter(new FileWriter("E://new1.xml",false));  
            out.write(os.toString("UTF-8"));  
            out.close();                                                
            return os.toString("UTF-8");
        } finally {
            IOUtils.closeQuietly(os);
        }
    }
    
    private static void validate(String signedFile,PublicKey publicKey) throws Exception {
        //将 xml 对象转换  为  Documnet 对象,使用 JAXP 将签名后生成的 XML 文档解析
       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       dbf.setNamespaceAware(true);
       Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(signedFile));
       System.out.println(verifyXmlElement(publicKey,doc));
   }

    /**
     * 验证XML签名
     *
     * @param pubKeyData 公钥数据 X509编码
     * @param xmlDocBytes XML内容byte
     * @return 签名验证结果 boolean
     * @throws Exception the exception
     */
    public static boolean verifyXmlElement(PublicKey publicKey, Document xmlDocument)
                                                                                     throws Exception {
        NodeList signatureNodes = xmlDocument.getElementsByTagNameNS(Constants.SignatureSpecNS,
            "Signature");
        if (signatureNodes == null || signatureNodes.getLength() < 1) {
            throw new Exception("Signature element not found!");
        }

        Element signElement = (Element) signatureNodes.item(0);
        if (signElement == null) {
            throw new Exception("Signature element  not found");
        }

        XMLSignature signature = new XMLSignature(signElement, "");
        return signature.checkSignatureValue(publicKey);
    }
    
    public static void main(String[] args) {
        try {
            RSAEncrypt rsaEncrypt= new RSAEncrypt();
            //加载公钥
            try {
                rsaEncrypt.loadPublicKey(RSAEncrypt.DEFAULT_PUBLIC_KEY);
                System.out.println("加载公钥成功");
            } catch (Exception e) {
                System.err.println(e.getMessage());
                System.err.println("加载公钥失败");
            }

            //加载私钥
            try {
                rsaEncrypt.loadPrivateKey(RSAEncrypt.DEFAULT_PRIVATE_KEY);
                System.out.println("加载私钥成功");
            } catch (Exception e) {
                System.err.println(e.getMessage());
                System.err.println("加载私钥失败");
            }
            
            PrivateKey privateKey=rsaEncrypt.getPrivateKey();
            PublicKey publicKey=rsaEncrypt.getPublicKey();        
            /**
             * param: source.xml是被签名的源文件
             * param: new.xml是签名完成后新生成的文件。通过验证此文件来确认源文件是否被篡改。
             */
            signatureXMLDocument("E:/paymentInfo.xml",privateKey);
            validate("E:/new1.xml",publicKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
最后修改:2022 年 02 月 20 日 04 : 55 PM
如果觉得我的文章对你有用,请随意赞赏

发表评论