XML签名验签

目前大部分的签名都是直接放在参数中的,以k-v形式或json形式进行传递,但是还有很多系统仍然使用xml交互,多用于银行系统(系统庞大,更新复杂,体制腐败,没办法咯)

这里介绍一个使用java进行xml加签验签的工具(http://www.w3.org/TR/xmldsig-core/规范)

1.提供jks文件以及cer证书文件(已有请忽略)

生成证书(windows):

在windows+R:cmd

检查java是否安装:java -version 未安装需要安装

生成服务器端的jks文件):keytool -genkey -alias test -keyalg RSA -keysize 1024 -keystore test.jks -validity 365(我这里指定别名为test,文件名为test.jks)

输入密码:123456(自己设置)

输入一大串信息:(随便填填即可,不用于互联网不需要关注)

确认一大串信息:

私钥生成成功:keytool -list -v -keystore test.jks(有输出代表成功)

导出cer证书:keytool -alias test -exportcert -keystore test.jks -file test.cer

去你的用户名下面去找吧,发现多了jks文件和cer文件

如果有问题可以参考:http://jingyan.baidu.com/article/b0b63dbfe18eff4a483070f4.html


2.copy如下代码 使用main方法测试

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
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.spec.C14NMethodParameterSpec;
import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
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.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 *    
 * 项目名称:balanceFinanceApi   
 * 类名称:SignXML xml签名类  
 * 类描述:TODO  
 * 创建人: yy
 * 创建时间:2016年12月6日 下午2:42:09   
 * @version  
 */
 
public class SignXML {  
    
private String jksfile;//秘钥路径
private String jksfileName;//秘钥路径名称
private String password;//密码
private String cerfile;//公钥路径
private String cerfileName;//公钥名称
   /** 
 * @Title: SignatureXML
 * @Description: TODO 签名
 * @date 2016年12月6日
 * @author yy
 * @param @param xml 字符串 utf8格式写入内存
 * @param @return
 * @param @throws Exception   
 * @return String  
 * @throws 
 */
public String SignatureXML(String xml) throws Exception {
       Document doc = getDocumentByXml(xml);
       XMLSignatureFactory fac = XMLSignatureFactory.getInstance();  
       String uri=((Element) doc.getLastChild().getFirstChild()).getAttribute("id");//加密引用至当前ID
       /*创建 <Reference> 元素,引用整个 XML 文档: 
       *创建 Reference 的时候将 URI 参数指定为 "" 表示对整个 XML 文档进行引用; 
       *摘要算法指定为 SHA1;这里将转换方式指定为 ENVELOPED , 
       *这样在对整个文档进行引用并生成摘要值的时候,<Signature> 元素不会被计算在内。*/  
       Transform envelopedTransform = fac.newTransform(Transform.ENVELOPED,(TransformParameterSpec) null);  
       DigestMethod sha1DigMethod = fac.newDigestMethod(DigestMethod.SHA1,   null);  
       Reference refToRootDoc = fac.newReference("#"+uri, sha1DigMethod,Collections.singletonList(envelopedTransform), null, null);  
         
       /*创建 <SignedInfo> 元素 
         *因为最终的数字签名是针对 <SignedInfo> 元素而生成的,所以需要指定该 XML 元素的规范化方法, 
         * 以确定最终被处理的数据。这里指定为 INCLUSIVE_WITH_COMMENTS ,  
         * 表示在规范化 XML 内容的时候会将 XML 注释也包含在内。 
         *    至此,待签名的内容(<SignedInfo> 元素)已指定好,再只需要签名所使用的密钥就可以创建数字签名了。*/  
       CanonicalizationMethod c14nWithCommentMethod =   
           fac.newCanonicalizationMethod(CanonicalizationMethod.INCLUSIVE,(C14NMethodParameterSpec) null);  
       SignatureMethod dsa_sha1SigMethod = fac.newSignatureMethod(SignatureMethod.RSA_SHA1, null);  
       SignedInfo signedInfo = fac.newSignedInfo(c14nWithCommentMethod,dsa_sha1SigMethod,Collections.singletonList(refToRootDoc));  
         
        /*XML 数字签名规范规定了多种在 <KeyInfo> 中指定验证密钥的方式,比如 <KeyName>,<KeyValue>,<X509Data>,<PGPData> 等等。 
          * 这里使用 XML 数字签名规范规定必须实现的 <DSAKeyValue> 来指定验证签名所需的公共密钥。 
          * 在程序中使用 java.security 包生成 RSA 密钥对。*/  
        //创建密钥对  
       /**NVAY修改开始*/  
       PrivateKey privateKey = null;
      
        KeyStore jks = KeyStore.getInstance("jks");  
        jks.load(new FileInputStream(jksfile), password.toCharArray());  
        privateKey = (PrivateKey) jks.getKey(jksfileName, password.toCharArray());  
        //Certificate cert =  jks.getCertificate("test");  
        //PublicKey publicKey = cert.getPublicKey(); 
        /**NVAY修改结束*/  
       //以公钥为参数创建 <KeyValue> 元素  
       //KeyInfoFactory keyInfoFac = fac.getKeyInfoFactory();  
       //KeyValue keyValue = keyInfoFac.newKeyValue(publicKey);  
       //根据创建好的 <KeyValue> 元素创建 <KeyInfo> 元素:  
       // KeyInfo keyInfo = keyInfoFac.newKeyInfo(Collections.singletonList(keyValue));  
       /*这里创建的密钥对,其中的公钥已经用于创建 <KeyInfo> 元素并存放在其中,供签名验证使用,而其中的私钥则会在下一步被用于生成签名。*/  
         
       //创建 <Signature> 元素  
       /*前面已经创建好 <SignedInfo> 和 <KeyInfo> 元素,为了生成最终的数字签名, 
       * 需要根据这两个元素先创建 <Signature> 元素,然后进行签名, 
       * 创建出 <SignatureValue> 元素。*/  
       XMLSignature signature = fac.newXMLSignature(signedInfo, null);  
         
       /*XMLSignature 类中的 sign 方法用于对文档进行签名,在调用 sign 方法之前, 
       * 还需要创建 DOMSignContext 对象,为方法调用提供上下文信息, 
       * 包括签名所使用的私钥和最后生成的 <Signature> 元素所在的目标父元素:*/  
       DOMSignContext dsc =    new DOMSignContext(privateKey, doc.getDocumentElement());
       //生成签名  
       /*sign 方法会生成签名值,并作为元素值创建 <SignatureValue> 元素,然后将整个 <Signature> 元素加入为待签名文档根元素的直接子元素。*/  
       signature.sign(dsc);   
       DOMSource source=new DOMSource(doc); 
       TransformerFactory tf = TransformerFactory.newInstance();
       Transformer former=tf.newTransformer();
       former.setOutputProperty(OutputKeys.STANDALONE, "yes");
       StringWriter sw = new StringWriter();
       StreamResult sr = new StreamResult(sw);
       former.transform(source, sr);
       String result=sw.toString();
       sw.close();
       
       return result;
   }


/** 
 * @Title: getDocumentByXml 
 * @Description: TODO 根据xml构建带ID声明的参数
 * @date 2016年12月6日
 * @author yy
 * @param @param xml
 * @param @param dbf
 * @param @return
 * @param @throws SAXException
 * @param @throws IOException
 * @param @throws ParserConfigurationException
 * @param @throws UnsupportedEncodingException   
 * @return Document  
 * @throws 
 */
private Document getDocumentByXml(String xml)
		throws SAXException, IOException, ParserConfigurationException,
		UnsupportedEncodingException {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();  
	    dbf.setNamespaceAware(true);
		Document doc = dbf.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes("UTF-8"))); 
       //添加ID声明 <!DOCTYPE root [ <!ATTLIST CSReq  id ID #REQUIRED >]>
       String MsgNodeName=doc.getLastChild().getNodeName();
       String reqNodeName=doc.getLastChild().getFirstChild().getNodeName();
       String doctypeStr="<!DOCTYPE root [ <!ATTLIST "+MsgNodeName+"  id ID #REQUIRED >"
       									+ "<!ATTLIST "+reqNodeName+"  id ID #REQUIRED >]>";
       String head=xml.substring(0, xml.indexOf(">")+1);
       head+=doctypeStr;
       xml=head+xml.substring(xml.indexOf(">")+1);
       
       doc=dbf.newDocumentBuilder().parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));//重新构建新的xml
	return doc;
}  
     
 
   /** 
 * @Title: validate 
 * @Description: TODO 验证xml签名
 * @date 2016年12月6日
 * @author yy
 * @param @param xml 字符串 utf-8格式写入内存
 * @param @return
 * @param @throws Exception   
 * @return boolean  
 * @throws 
 */
private boolean validate(String xml) throws Exception {
	
	  	Document doc=getDocumentByXml(xml);
       // Search the Signature element  
       NodeList nl = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#","Signature");  
       if (nl.getLength() == 0) {  
           throw new Exception("Cannot find Signature element");  
       }  
         
       Node signatureNode = nl.item(0);  
         
       /**NAVY修改开始**/  
       KeyStore jks = KeyStore.getInstance("jks");  
        jks.load(new FileInputStream("E:/test.jks"), "123456".toCharArray());  
        Certificate cert =  jks.getCertificate("test");  
        PublicKey publicKey = cert.getPublicKey();  
        /**NAVY修改结束**/  
       // Create ValidateContext  
       DOMValidateContext valCtx = new DOMValidateContext(publicKey,signatureNode);  
       XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");  
       XMLSignature signature = fac.unmarshalXMLSignature(new DOMStructure(signatureNode));  
       // Validate the XMLSignature  
       boolean coreValidity = signature.validate(valCtx);  
       
       // Check core validation status  
       if (coreValidity == false) {  
           System.err.println("Core validation failed");  
           // Check the signature validation status  
           boolean sv = signature.getSignatureValue().validate(valCtx);  
           System.out.println("Signature validation status: " + sv);  
           // check the validation status of each Reference  
           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);  
           }  
       } 
       
       return coreValidity;
   }  
     
   public static void main(String[] args) throws Exception {  
	   
		
       SignXML signatureXML=new SignXML();  
       signatureXML.jksfile="E:\\test.jks";
       signatureXML.jksfileName="test";
       signatureXML.password="123456";
       signatureXML.cerfile="E:\\test.cer";
       signatureXML.cerfileName="test";
       
       try {  
    	   String xml="<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><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>[email protected]</email><cell>13588888888</cell><address>杭州市西湖区</address><memo>测试</memo></CSReq></Message>";
          String result=signatureXML.SignatureXML(xml);
          System.out.println(result);
          boolean rb= signatureXML.validate(result);
          System.out.println("验证结果:"+rb);
       } catch (Exception e) {  
           e.printStackTrace();  
       }  
   }  
  
}  

在这里需要注意的是:getDocumentByXml方法中会追加ID声明(必须声明,不然会rt.jar中getElementById方法会报错)

指定了根元素和根元素的第一个元素会声明ID(即Message、CSReq),需要加不同标签的Id,需要修改getDocumentByXml第3行


SignedInfo signedInfo = fac.newSignedInfo(c14nWithCommentMethod,dsa_sha1SigMethod,Collections.singletonList(refToRootDoc));

这句话是本文的核心

CanonicalizationMethod:以什么标准构建签名,是否包含注释 等等 (CanonicalizationMethod有提供静态常量)

dsa_sha1SigMethod:签名方式(SignatureMethod有提供静态常量)

refToRootDoc:哪些xml参与签名

发布了38 篇原创文章 · 获赞 19 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/yangyongdehao30/article/details/53487895