1. 程式人生 > >使用Bouncy Castle生成數字簽名、數字信封

使用Bouncy Castle生成數字簽名、數字信封

Bouncy Castle(輕量級密碼術包)是一種用於 Java 平臺的開放原始碼的輕量級密碼術包,它支援大量的密碼術演算法,並提供 JCE 1.2.1 的實現。最近專案上正好用到了Bouncy Castle,用於生成數字簽名、數字信封,去網上找了很久,都沒有找到合適的案例,而Bouncy Castle本身的文件也不多,最有用的就是官網上的Java Doc文件,因為這個問題也困擾了我好幾天,最後還是通過閱讀Java Doc文件找到了合適的類和方法,果然閱讀Doc文件還是很有必要的啊。好了,話不多說,把我寫的方法列出來,以防忘記,並給有同樣需求的同學提供一些參考,其中有些程式碼也是參考了網上的寫法,最有用的還是Doc文件裡提供的一些示例寫法,基本的需求已經能夠滿足了。

要使用Bouncy Castle,就需要引入相應的jar包,在官網就可以根據自己的需要進行下載,然後就可以使用了。

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;

public class MessageUtil {
	private String ksType = "PKCS12";

	/**
	 * 生成數字簽名
	 * @param srcMsg 源資訊
	 * @param charSet 字元編碼
	 * @param certPath 證書路徑
	 * @param certPwd 證書密碼
	 * @return
	 */
	public byte[] signMessage(String srcMsg, String charSet, String certPath, String certPwd) {
		String priKeyName = null;
		char passphrase[] = certPwd.toCharArray();

		try {
			Provider provider = new BouncyCastleProvider();
			// 新增BouncyCastle作為安全提供
			Security.addProvider(provider);

			// 載入證書
			KeyStore ks = KeyStore.getInstance(ksType);
			ks.load(new FileInputStream(certPath), passphrase);

			if (ks.aliases().hasMoreElements()) {
				priKeyName = ks.aliases().nextElement();
			}
			
			Certificate cert = (Certificate) ks.getCertificate(priKeyName);

			// 獲取私鑰
			PrivateKey prikey = (PrivateKey) ks.getKey(priKeyName, passphrase);

			X509Certificate cerx509 = (X509Certificate) cert;

			List<Certificate> certList = new ArrayList<Certificate>();
			certList.add(cerx509);

			CMSTypedData msg = (CMSTypedData) new CMSProcessableByteArray(
					srcMsg.getBytes(charSet));

			Store certs = new JcaCertStore(certList);

			CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
			ContentSigner sha1Signer = new JcaContentSignerBuilder(
					"SHA1withRSA").setProvider("BC").build(prikey);

			gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
					new JcaDigestCalculatorProviderBuilder().setProvider("BC")
							.build()).build(sha1Signer, cerx509));

			gen.addCertificates(certs);

			CMSSignedData sigData = gen.generate(msg, true);

			return Base64.encode(sigData.getEncoded());

		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 驗證數字簽名
	 * @param signedData
	 * @return
	 */
	public boolean signedDataVerify(byte[] signedData) {
		boolean verifyRet = true;
		try {
			// 新建PKCS#7簽名資料處理物件
			CMSSignedData sign = new CMSSignedData(signedData);

			// 新增BouncyCastle作為安全提供
			Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

			// 獲得證書資訊
			Store certs = sign.getCertificates();

			// 獲得簽名者資訊
			SignerInformationStore signers = sign.getSignerInfos();
			Collection c = signers.getSigners();
			Iterator it = c.iterator();

			// 當有多個簽名者資訊時需要全部驗證
			while (it.hasNext()) {
				SignerInformation signer = (SignerInformation) it.next();

				// 證書鏈
				Collection certCollection = certs.getMatches(signer.getSID());
				Iterator certIt = certCollection.iterator();
				X509CertificateHolder cert = (X509CertificateHolder) certIt
						.next();

				// 驗證數字簽名
				if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder()
						.setProvider("BC").build(cert))) {
					verifyRet = true;
				} else {
					verifyRet = false;
				}
			}

		} catch (Exception e) {
			verifyRet = false;
			e.printStackTrace();
			System.out.println("驗證數字簽名失敗");
		}
		return verifyRet;
	}

	/**
	 * 加密資料
	 * @param srcMsg 源資訊
	 * @param certPath 證書路徑
	 * @param charSet 字元編碼
	 * @return
	 * @throws Exception
	 */
	public String envelopeMessage(String srcMsg, String certPath, String charSet) throws Exception {
		CertificateFactory certificatefactory;
		X509Certificate cert;
		// 使用公鑰對對稱金鑰進行加密 //若此處不加引數 "BC" 會報異常:CertificateException -
		certificatefactory = CertificateFactory.getInstance("X.509", "BC");
		// 讀取.crt檔案;你可以讀取絕對路徑檔案下的crt,返回一個InputStream(或其子類)即可。
		InputStream bais = new FileInputStream(certPath);

		cert = (X509Certificate) certificatefactory.generateCertificate(bais);

		//新增數字信封
		CMSTypedData msg = new CMSProcessableByteArray(srcMsg.getBytes(charSet));

		CMSEnvelopedDataGenerator edGen = new CMSEnvelopedDataGenerator();

		edGen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(
				cert).setProvider("BC"));

		CMSEnvelopedData ed = edGen.generate(msg,
				new JceCMSContentEncryptorBuilder(PKCSObjectIdentifiers.rc4)
						.setProvider("BC").build());

		String rslt = new String(Base64.encode(ed.getEncoded()));

		System.out.println(rslt);
		return rslt;
	}

	/**
	 * 解密資料
	 * @param encode 加密後的密文
	 * @param certPath 證書路徑
	 * @param certPwd 證書密碼
	 * @param charSet 字元編碼
	 * @return
	 * @throws Exception
	 */
	public String openEnvelope(String encode, String certPath, String certPwd, String charSet) throws Exception {
		//獲取密文
		CMSEnvelopedData ed = new CMSEnvelopedData(Base64.decode(encode.getBytes()));

		RecipientInformationStore recipients = ed.getRecipientInfos();

		Collection c = recipients.getRecipients();
		Iterator it = c.iterator();

		// 載入證書
		KeyStore ks = KeyStore.getInstance(ksType);
		ks.load(new FileInputStream(certPath), certPwd.toCharArray());
		
		String priKeyName = null;
		if (ks.aliases().hasMoreElements()) {
			priKeyName = ks.aliases().nextElement();
		}
		
		// 獲取私鑰
		PrivateKey prikey = (PrivateKey) ks.getKey(priKeyName, certPwd.toCharArray());

		byte[] recData = null;
		//解密
		if (it.hasNext()) {
			RecipientInformation recipient = (RecipientInformation) it.next();

			recData = recipient.getContent(new JceKeyTransEnvelopedRecipient(
					prikey).setProvider("BC"));
		}

		return new String(recData, charSet);
	}

	public MessageUtil() {
		Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
	}


}