1. 概述

BouncyCastle 是一个补充Java默认加密扩展(JCE)的Java库。

本文将介绍如何使用BouncyCastle执行加密操作,如加密和签名。

2. Maven配置

开始使用前,需在pom.xml中添加依赖:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.76</version>
</dependency>

⚠️ 最新版本可在Maven中央仓库查询。

3. 配置无限强度管辖策略文件

标准Java安装对加密强度有限制(如AES密钥长度不超过128位)。要突破限制,需配置无限强度管辖策略文件

操作步骤:

  1. Oracle官网下载策略文件包
  2. 解压后得到两个JAR文件:
    • local_policy.jar
    • US_export_policy.jar
  3. 替换{JAVA_HOME}/lib/security目录下的同名文件

Java 9+无需下载,直接设置属性即可:

Security.setProperty("crypto.policy", "unlimited");

验证配置是否生效:

int maxKeySize = javax.crypto.Cipher.getMaxAllowedKeyLength("AES");
System.out.println("Max Key Size for AES : " + maxKeySize);

输出应为:

Max Key Size for AES : 2147483647

若返回128,说明策略文件未正确安装。

4. 加密操作

4.1 准备证书和私钥

实现加密功能前,需准备证书和私钥。测试资源:

Java加载代码:

Security.addProvider(new BouncyCastleProvider());
CertificateFactory certFactory = CertificateFactory
  .getInstance("X.509", "BC");
 
X509Certificate certificate = (X509Certificate) certFactory
  .generateCertificate(new FileInputStream("Baeldung.cer"));
 
char[] keystorePassword = "password".toCharArray();
char[] keyPassword = "password".toCharArray();
 
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream("Baeldung.p12"), keystorePassword);
PrivateKey key = (PrivateKey) keystore.getKey("baeldung", keyPassword);

关键点:

  1. 动态添加BouncyCastleProvider(也可静态配置java.security文件)
  2. 使用CertificateFactory加载X.509证书
  3. 通过PKCS12密钥库获取私钥

证书和私钥主要用于非对称加密操作:

  • 加密/解密
  • 签名/验证

4.2 生成证书和密钥库

生成新证书需执行以下命令:

# 生成2048位私钥
openssl genrsa -out private-key.pem 2048

# 创建证书签名请求
openssl req -new -sha256 -key private-key.pem -out certificate-signed-request.csr

# 生成自签名证书
openssl req -x509 -sha256 -days 365 -key private-key.pem -in certificate-signed-request.csr -out Baeldung.cer

# 创建PKCS12密钥库
openssl pkcs12 -export -name baeldung -out Baeldung.p12 -inkey private-key.pem -in Baeldung.cer

⚠️ 生成后需将证书放入资源目录,并确保代码中引用正确的文件名。

4.3 CMS/PKCS7加密与解密

非对称加密中,通信双方需使用公钥证书和私钥

  • 发送方用接收方证书加密
  • 接收方用对应私钥解密

加密实现:

public static byte[] encryptData(byte[] data,
  X509Certificate encryptionCertificate)
  throws CertificateEncodingException, CMSException, IOException {
 
    byte[] encryptedData = null;
    if (null != data && null != encryptionCertificate) {
        CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator
          = new CMSEnvelopedDataGenerator();
 
        JceKeyTransRecipientInfoGenerator jceKey 
          = new JceKeyTransRecipientInfoGenerator(encryptionCertificate);
        cmsEnvelopedDataGenerator.addRecipientInfoGenerator(transKeyGen);
        CMSTypedData msg = new CMSProcessableByteArray(data);
        OutputEncryptor encryptor
          = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC)
          .setProvider("BC").build();
        CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator
          .generate(msg,encryptor);
        encryptedData = cmsEnvelopedData.getEncoded();
    }
    return encryptedData;
}

解密实现:

public static byte[] decryptData(
  byte[] encryptedData, 
  PrivateKey decryptionKey) 
  throws CMSException {
 
    byte[] decryptedData = null;
    if (null != encryptedData && null != decryptionKey) {
        CMSEnvelopedData envelopedData = new CMSEnvelopedData(encryptedData);
 
        Collection<RecipientInformation> recipients
          = envelopedData.getRecipientInfos().getRecipients();
        KeyTransRecipientInformation recipientInfo 
          = (KeyTransRecipientInformation) recipients.iterator().next();
        JceKeyTransRecipient recipient
          = new JceKeyTransEnvelopedRecipient(decryptionKey);
        
        return recipientInfo.getContent(recipient);
    }
    return decryptedData;
}

测试用例:

String secretMessage = "My password is 123456Seven";
System.out.println("Original Message : " + secretMessage);
byte[] stringToEncrypt = secretMessage.getBytes();
byte[] encryptedData = encryptData(stringToEncrypt, certificate);
System.out.println("Encrypted Message : " + new String(encryptedData));
byte[] rawData = decryptData(encryptedData, privateKey);
String decryptedMessage = new String(rawData);
System.out.println("Decrypted Message : " + decryptedMessage);

输出:

Original Message : My password is 123456Seven
Encrypted Message : 0�*�H��...
Decrypted Message : My password is 123456Seven

4.4 CMS/PKCS7签名与验证

签名实现:

public static byte[] signData(
  byte[] data, 
  X509Certificate signingCertificate,
  PrivateKey signingKey) throws Exception {
 
    byte[] signedMessage = null;
    List<X509Certificate> certList = new ArrayList<X509Certificate>();
    CMSTypedData cmsData= new CMSProcessableByteArray(data);
    certList.add(signingCertificate);
    Store certs = new JcaCertStore(certList);

    CMSSignedDataGenerator cmsGenerator = new CMSSignedDataGenerator();
    ContentSigner contentSigner 
      = new JcaContentSignerBuilder("SHA256withRSA").build(signingKey);
    cmsGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
      new JcaDigestCalculatorProviderBuilder().setProvider("BC")
      .build()).build(contentSigner, signingCertificate));
    cmsGenerator.addCertificates(certs);
    
    CMSSignedData cms = cmsGenerator.generate(cmsData, true);
    signedMessage = cms.getEncoded();
    return signedMessage;
}

验证实现:

public static boolean verifSignedData(byte[] signedData)
  throws Exception {
 
    X509Certificate signCert = null;
    ByteArrayInputStream inputStream
     = new ByteArrayInputStream(signedData);
    ASN1InputStream asnInputStream = new ASN1InputStream(inputStream);
    CMSSignedData cmsSignedData = new CMSSignedData(
      ContentInfo.getInstance(asnInputStream.readObject()));
    
    SignerInformationStore signers 
      = cmsSignedData.getCertificates().getSignerInfos();
    SignerInformation signer = signers.getSigners().iterator().next();
    Collection<X509CertificateHolder> certCollection 
      = certs.getMatches(signer.getSID());
    X509CertificateHolder certHolder = certCollection.iterator().next();
    
    return signer
      .verify(new JcaSimpleSignerInfoVerifierBuilder()
      .build(certHolder));
}

测试用例:

byte[] signedData = signData(rawData, certificate, privateKey);
Boolean check = verifSignData(signedData);
System.out.println(check);

输出:

true

5. 总结

本文介绍了BouncyCastle库的基础加密操作。实际应用中,通常采用先签名后加密的方式:

  1. 发送方用私钥签名
  2. 接收方用公钥验证签名
  3. 接收方用私钥解密内容

完整代码示例见GitHub仓库


原始标题:Introduction to BouncyCastle with Java