【Java密碼學】Java SE 6中XML數字簽名的實現

package test.xml.signature;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
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;

public class SignatureXML {
   
   public void saveKey(PublicKey publicKey, PrivateKey privateKey) throws Exception{
       X509EncodedKeySpec ksp = new X509EncodedKeySpec(publicKey.getEncoded());
       FileOutputStream fos = new FileOutputStream("C:\\public.key");
       fos.write(ksp.getEncoded());
       fos.close();

       PKCS8EncodedKeySpec pks = new PKCS8EncodedKeySpec(privateKey.getEncoded());
       fos = new FileOutputStream("C:\\private.key");
       fos.write(pks.getEncoded());
       fos.close();
  }
   
  public Key LoadKeyFromFile(boolean ispk, String keyFile) {
       Key key = null;
       FileInputStream is = null;
       try {
           is = new FileInputStream(keyFile);
           byte[] buf = new byte[is.available()];
           KeyFactory keyFactory = KeyFactory.getInstance("DSA");
           is.read(buf);
           EncodedKeySpec keySpec;
           if (ispk) {
               keySpec = new PKCS8EncodedKeySpec(buf);
           } else {
               keySpec = new X509EncodedKeySpec(buf);
           }
           key = (!ispk ? (Key) keyFactory.generatePublic(keySpec) : (Key) keyFactory.generatePrivate(keySpec));
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           try {
               is.close();
           } catch (IOException iex) {
               iex.printStackTrace();
           }
       }
       return key;
   }
   
   public void SignatureXMLDocument(String docPath) throws Exception {
       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       dbf.setNamespaceAware(true);
       Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(docPath));
       this.SignatureXMLDocument(doc);
   }

   public void SignatureXMLDocument(Document doc) throws Exception {
       
       XMLSignatureFactory fac = XMLSignatureFactory.getInstance();
       /*建立 <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("", 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 dsa_sha1SigMethod = fac.newSignatureMethod(SignatureMethod.DSA_SHA1, null);
       SignedInfo signedInfo = fac.newSignedInfo(c14nWithCommentMethod,dsa_sha1SigMethod,Collections.singletonList(refToRootDoc));
       
        /*XML 數字簽名規範規定了多種在 <KeyInfo> 中指定驗證密鑰的方式,好比 <KeyName>,<KeyValue>,<X509Data>,<PGPData> 等等。
          * 這裏使用 XML 數字簽名規範規定必須實現的 <DSAKeyValue> 來指定驗證簽名所需的公共密鑰。
          * 在程序中使用 java.security 包生成 DSA 密鑰對。*/
        //建立密鑰對
       KeyPairGenerator kpGen = KeyPairGenerator.getInstance("DSA");
       kpGen.initialize(512);
       KeyPair keyPair = kpGen.generateKeyPair();
       
       PublicKey publicKey   = keyPair.getPublic();
       PrivateKey privateKey = keyPair.getPrivate();
       
       this.saveKey(publicKey, privateKey);
       
       //以公鑰爲參數建立 <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, keyInfo);
       
       /*XMLSignature 類中的 sign 方法用於對文檔進行簽名,在調用 sign 方法以前,
       * 還須要建立 DOMSignContext 對象,爲方法調用提供上下文信息,
       * 包括簽名所使用的私鑰和最後生成的 <Signature> 元素所在的目標父元素:*/
       
       DOMSignContext dsc =    new DOMSignContext(privateKey, doc.getDocumentElement());   
       
       //生成簽名
       /*sign 方法會生成簽名值,並做爲元素值建立 <SignatureValue> 元素,而後將整個 <Signature> 元素加入爲待簽名文檔根元素的直接子元素。*/
       signature.sign(dsc);   
       
       TransformerFactory tf = TransformerFactory.newInstance();
       Transformer transformer = tf.newTransformer();
       DOMSource source=new DOMSource(doc);
       transformer.transform(source, new StreamResult(System.out));   
       StreamResult result = new StreamResult(new File("C:\\old.xml"));
       transformer.transform(source,result);

   }
   
   private void validate(String signedFile) throws Exception {
        //Parse the signed XML document to unmarshal <Signature> object.
       DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
       dbf.setNamespaceAware(true);
       Document doc = dbf.newDocumentBuilder().parse(new FileInputStream(signedFile));
       this.validate(doc);
   }

   private void validate(Document doc) throws Exception {

       // Search the Signature element
       NodeList nl = doc.getElementsByTagNameNS(XMLSignature.XMLNS,"Signature");
       if (nl.getLength() == 0) {
           throw new Exception("Cannot find Signature element");
       }
       
       Node signatureNode = nl.item(0);

       XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM");
       XMLSignature signature = fac.unmarshalXMLSignature(new DOMStructure(signatureNode));

       // Get the public key for signature validation
       KeyValue keyValue = (KeyValue) signature.getKeyInfo().getContent().get(0);
       PublicKey pubKey = keyValue.getPublicKey();

       // Create ValidateContext
       DOMValidateContext valCtx = new DOMValidateContext(pubKey,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);
           }
       } else {
           System.out.println("Signature passed core validation");
       }
   }
   
   
   public static void main(String[] args) {
       SignatureXML signatureXML=new SignatureXML();
       try {
//           signatureXML.SignatureXMLDocument("C:\\new.xml");
           signatureXML.validate("C:\\old.xml");
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

}
相關文章
相關標籤/搜索