1. 程式人生 > >如何用 Java 對 PDF 檔案進行電子簽章(四)如何生成PKCS12證書

如何用 Java 對 PDF 檔案進行電子簽章(四)如何生成PKCS12證書

參考:

1. PKCS的簡單介紹

  PKCS:The Public-Key Cryptography Standards (簡稱PKCS)是由美國RSA資料安全公司及其合作伙伴制定的一組公鑰密碼學標準,其中包括證書申請、證書更新、證書作廢表釋出、擴充套件證書內容以及數字簽名、數字信封的格式等方面的一系列相關協議。

到1999年底,PKCS已經公佈了以下標準:

  • PKCS#1:定義RSA公開金鑰演算法加密和簽名機制,主要用於組織PKCS#7中所描述的數字簽名和數字信封[22]。
  • PKCS#3:定義Diffie-Hellman金鑰交換協議[23]。
  • PKCS#5:描述一種利用從口令派生出來的安全金鑰加密字串的方法。使用MD2或MD5 從口令中派生金鑰,並採用DES-CBC模式加密。主要用於加密從一個計算機傳送到另一個計算機的私人金鑰,不能用於加密訊息[24]。
  • PKCS#6:描述了公鑰證書的標準語法,主要描述X.509證書的擴充套件格式[25]。
  • PKCS#7:定義一種通用的訊息語法,包括數字簽名和加密等用於增強的加密機制,PKCS#7與PEM相容,所以不需其他密碼操作,就可以將加密的訊息轉換成PEM訊息[26]。
  • PKCS#8:描述私有金鑰資訊格式,該資訊包括公開金鑰演算法的私有金鑰以及可選的屬性集等[27]。
  • PKCS#9:定義一些用於PKCS#6證書擴充套件、PKCS#7數字簽名和PKCS#8私鑰加密資訊的屬性型別[28]。
  • PKCS#10:描述證書請求語法[29]。
  • PKCS#11:稱為Cyptoki,定義了一套獨立於技術的程式設計介面,用於智慧卡和PCMCIA卡之類的加密裝置[30]。
  • PKCS#12:描述個人資訊交換語法標準。描述了將使用者公鑰、私鑰、證書和其他相關資訊打包的語法[31]。
  • PKCS#13:橢圓曲線密碼體制標準[32]。
  • PKCS#14:偽隨機數生成標準。
  • PKCS#15:密碼令牌資訊格式標準[33]。

  PKCS12也就是以上標準的PKCS#12,主要用來描述個人身份資訊;本次分享中要進行簽章操作的是醫生和藥師,他們就是一個個人主體,給他們分配一個PKCS12的證書,就等於給他們分配了一個用於蓋章的印章。這裡就看思路吧,結合你自己的業務 就好。

2. 使用JAVA生成一個PKCS12證書並進行存貯,相關分析見程式碼註解

    public class
Extension { private String oid; private boolean critical; private byte[] value; public String getOid() { return oid; } public byte[] getValue() { return value; } public boolean isCritical() { return critical; } }

   import java.io.ByteArrayInputStream;
   import java.io.ByteArrayOutputStream;
   import java.io.File;
   import java.io.FileOutputStream;
   import java.io.IOException;
   import java.math.BigInteger;
   import java.security.KeyPair;
   import java.security.KeyPairGenerator;
   import java.security.KeyStore;
   import java.security.NoSuchAlgorithmException;
   import java.security.PrivateKey;
   import java.security.PublicKey;
   import java.security.SecureRandom;
   import java.security.cert.Certificate;
   import java.security.cert.CertificateFactory;
   import java.security.cert.X509Certificate;
   import java.text.SimpleDateFormat;
   import java.util.Calendar;
   import java.util.Date;
   import java.util.HashMap;
   import java.util.List;
   import java.util.Map;
   import java.util.Random;

   import org.bouncycastle.asn1.ASN1ObjectIdentifier;
   import org.bouncycastle.asn1.ASN1Primitive;
   import org.bouncycastle.asn1.x500.X500Name;
   import org.bouncycastle.asn1.x509.BasicConstraints;
   import org.bouncycastle.asn1.x509.CRLDistPoint;
   import org.bouncycastle.asn1.x509.DistributionPoint;
   import org.bouncycastle.asn1.x509.DistributionPointName;
   import org.bouncycastle.asn1.x509.GeneralName;
   import org.bouncycastle.asn1.x509.GeneralNames;
   import org.bouncycastle.asn1.x509.KeyUsage;
   import org.bouncycastle.cert.X509CertificateHolder;
   import org.bouncycastle.cert.X509v3CertificateBuilder;
   import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
   import org.bouncycastle.jce.provider.BouncyCastleProvider;
   import org.bouncycastle.operator.ContentSigner;
   import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

   public class Pkcs {

   private static KeyPair getKey() throws NoSuchAlgorithmException {
  	   // 金鑰對 生成器,RSA演算法 生成的  提供者是 BouncyCastle
       KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA",  new BouncyCastleProvider());
       generator.initialize(1024);  // 金鑰長度 1024
       // 證書中的金鑰 公鑰和私鑰
       KeyPair keyPair = generator.generateKeyPair();
       return keyPair;
   }

   /**
    * @param password  密碼   
    * @param issuerStr 頒發機構資訊
    * @param subjectStr 使用者資訊
   * @param certificateCRL 頒發地址
    * @return
    */
   public static Map<String, byte[]> createCert(String password, String issuerStr, String subjectStr, String certificateCRL) {
   
       Map<String, byte[]> result = new HashMap<String, byte[]>();
       ByteArrayOutputStream out = null;
       try {
           //  生成JKS證書
           //  KeyStore keyStore = KeyStore.getInstance("JKS");
           //  標誌生成PKCS12證書
           KeyStore keyStore = KeyStore.getInstance("PKCS12",  new BouncyCastleProvider());
           keyStore.load(null, null);
           KeyPair keyPair = getKey();
           //  issuer與 subject相同的證書就是CA證書
           Certificate cert = generateCertificateV3(issuerStr, subjectStr,  keyPair, result, certificateCRL, null);
           // cretkey隨便寫,標識別名
           keyStore.setKeyEntry("cretkey",  keyPair.getPrivate(),  password.toCharArray(),  new Certificate[] { cert });
           out = new ByteArrayOutputStream();
           cert.verify(keyPair.getPublic());
           keyStore.store(out, password.toCharArray());
           byte[] keyStoreData = out.toByteArray();
           result.put("keyStoreData", keyStoreData);
           return result;
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           if (out != null) {
               try {
                   out.close();
               } catch (IOException e) {
               }
           }
       }
       return result;
   }

   /**
    * @param issuerStr
    * @param subjectStr
    * @param keyPair
    * @param result
    * @param certificateCRL
    * @param extensions
    * @return
    */
   public static Certificate generateCertificateV3(String issuerStr, String subjectStr, KeyPair keyPair, Map<String, byte[]> result,
     String certificateCRL, List<Extension> extensions) {
     
       ByteArrayInputStream bout = null;
       X509Certificate cert = null;
       try {
           PublicKey publicKey = keyPair.getPublic();
           PrivateKey privateKey = keyPair.getPrivate();
           Date notBefore = new Date();
           Calendar rightNow = Calendar.getInstance();
           rightNow.setTime(notBefore);
           // 日期加1年
           rightNow.add(Calendar.YEAR, 1);
           Date notAfter = rightNow.getTime();
           // 證書序列號
           BigInteger serial = BigInteger.probablePrime(256, new Random());
           X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
                   new X500Name(issuerStr), serial, notBefore, notAfter,new X500Name(subjectStr), publicKey);
           JcaContentSignerBuilder jBuilder = new JcaContentSignerBuilder( "SHA1withRSA");
           SecureRandom secureRandom = new SecureRandom();
           jBuilder.setSecureRandom(secureRandom);
           ContentSigner singer = jBuilder.setProvider(  new BouncyCastleProvider()).build(privateKey);
           // 分發點
           ASN1ObjectIdentifier cRLDistributionPoints = new ASN1ObjectIdentifier( "2.5.29.31");
           GeneralName generalName = new GeneralName( GeneralName.uniformResourceIdentifier, certificateCRL);
           GeneralNames seneralNames = new GeneralNames(generalName);
           DistributionPointName distributionPoint = new DistributionPointName( seneralNames);
           DistributionPoint[] points = new DistributionPoint[1];
           points[0] = new DistributionPoint(distributionPoint, null, null);
           CRLDistPoint cRLDistPoint = new CRLDistPoint(points);
           builder.addExtension(cRLDistributionPoints, true, cRLDistPoint);
           // 用途
           ASN1ObjectIdentifier keyUsage = new ASN1ObjectIdentifier( "2.5.29.15");
           // | KeyUsage.nonRepudiation | KeyUsage.keyCertSign
           builder.addExtension(keyUsage, true, new KeyUsage( KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
           // 基本限制 X509Extension.java
           ASN1ObjectIdentifier basicConstraints = new ASN1ObjectIdentifier("2.5.29.19");
           builder.addExtension(basicConstraints, true, new BasicConstraints(true));
           // privKey:使用自己的私鑰進行簽名,CA證書
           if (extensions != null){
               for (Extension ext : extensions) {
                   builder.addExtension(
                           new ASN1ObjectIdentifier(ext.getOid()),
                           ext.isCritical(),
                           ASN1Primitive.fromByteArray(ext.getValue()));
               }
            }
           X509CertificateHolder holder = builder.build(singer);
           CertificateFactory cf = CertificateFactory.getInstance("X.509");
           bout = new ByteArrayInputStream(holder.toASN1Structure() .getEncoded());
           cert = (X509Certificate) cf.generateCertificate(bout);
           byte[] certBuf = holder.getEncoded();
           SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
           // 證書資料
           result.put("certificateData", certBuf);
           //公鑰
           result.put("publicKey", publicKey.getEncoded());
           //私鑰
           result.put("privateKey", privateKey.getEncoded());
           //證書有效開始時間
           result.put("notBefore", format.format(notBefore).getBytes("utf-8"));
           //證書有效結束時間
           result.put("notAfter", format.format(notAfter).getBytes("utf-8"));
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           if (bout != null) {
               try {
                   bout.close();
               } catch (IOException e) {
               }
           }
       }
       return cert;
   }

   public static void main(String[] args) throws Exception{
       // CN: 名字與姓氏    OU : 組織單位名稱
       // O :組織名稱  L : 城市或區域名稱  E : 電子郵件
       // ST: 州或省份名稱  C: 單位的兩字母國家程式碼 
       String issuerStr = "CN=線上醫院,OU=gitbook研發部,O=gitbook有限公司,C=CN,[email protected],L=北京,ST=北京";
       String subjectStr = "CN=huangjinjin,OU=gitbook研發部,O=gitbook有限公司,C=CN,[email protected],L=北京,ST=北京";
       String certificateCRL  = "https://gitbook.cn";
       Map<String, byte[]> result = createCert("123456", issuerStr, subjectStr, certificateCRL);

       FileOutputStream outPutStream = new FileOutputStream("c:/keystore.p12"); // ca.jks
       outPutStream.write(result.get("keyStoreData"));
       outPutStream.close();
       FileOutputStream fos = new FileOutputStream(new File("c:/keystore.cer"));
       fos.write(result.get("certificateData"));
       fos.flush();
       fos.close();
   }
   }
   ```