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. 进行RS验签 * </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(); } } }
https://www.ibm.com/developerworks/cn/xml/x-cn-java6xmlsignature/java
http://blog.csdn.net/chaijunkun/article/details/7275632/node