XML 簽名實例

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

相關文章
相關標籤/搜索