Introduction to BouncyCastle with Java – 使用Java的BouncyCastle介绍

最后修改: 2017年 10月 28日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

BouncyCastle is a Java library that complements the default Java Cryptographic Extension (JCE).

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

In this introductory article, we’re going to show how to use BouncyCastle to perform cryptographic operations, such as encryption and signature.

在这篇介绍性文章中,我们将展示如何使用BouncyCastle来进行加密和签名等加密操作。

2. Maven Configuration

2.Maven配置

Before we start working with the library, we need to add the required dependencies to our pom.xml file:

在我们开始使用该库之前,我们需要在我们的pom.xml文件中添加所需的依赖项。

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk15on</artifactId>
    <version>1.58</version>
</dependency>

Note that we can always look up the latest dependencies versions in the Maven Central Repository.

注意,我们可以随时在Maven Central Repository中查找最新的依赖版本。

3. Setup Unlimited Strength Jurisdiction Policy Files

3.设置无限强度的管辖政策文件

The standard Java installation is limited in terms of strength for cryptographic functions, this is due to policies prohibiting the use of a key with a size that exceeds certain values e.g. 128 for AES.

标准的Java安装在加密功能的强度方面是有限的,这是由于政策禁止使用超过某些值的密钥,例如AES的128。

To overcome this limitation, we need to configure the unlimited strength jurisdiction policy files.

为了克服这一限制,我们需要配置无限强度管辖策略文件

In order to do that, we first need to download the package by following this link. Afterwards, we need to extract the zipped file into a directory of our choice – which contains two jar files:

为了做到这一点,我们首先需要按照这个链接下载该软件包。之后,我们需要将该压缩文件解压到我们选择的目录中–其中包含两个jar文件。

  • local_policy.jar
  • US_export_policy.jar

Finally, we need to look for the {JAVA_HOME}/lib/security folder and replace the existing policy files with the ones that we’ve extracted here.

最后,我们需要寻找{JAVA_HOME}/lib/security文件夹,用这里提取的文件替换现有的策略文件。

Note that in Java 9, we no longer need to download the policy files package, setting the crypto.policy property to unlimited is enough:

请注意,在Java 9中,我们不再需要下载策略文件包,将crypto.policy属性设置为unlimited即可。

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

Once done, we need to check that the configuration is working correctly:

一旦完成,我们需要检查配置是否正常工作。

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

As a result:

结果是。

Max Key Size for AES : 2147483647

Based on the maximum key size returned by the getMaxAllowedKeyLength() method, we can safely say that the unlimited strength policy files have been installed correctly.

根据getMaxAllowedKeyLength()方法返回的最大密钥大小,我们可以肯定地说,无限强度策略文件已经正确安装。

If the returned value is equal to 128, we need to make sure that we’ve installed the files into the JVM where we’re running the code.

如果返回值等于128,我们需要确保我们已经将文件安装到我们运行代码的JVM中。

4. Cryptographic Operations

4.加密操作

4.1. Preparing Certificate and Private Key

4.1.准备证书和私钥

Before we jump into the implementation of cryptographic functions, we first need to create a certificate and a private key.

在我们进入加密功能的实现之前,我们首先需要创建一个证书和一个私钥。

For test purposes, we can use these resources:

为了测试目的,我们可以使用这些资源。

Baeldung.cer is a digital certificate that uses the international X.509 public key infrastructure standard, while the Baeldung.p12 is a password-protected PKCS12 Keystore that contains a private key.

Baeldung.cer是一个使用国际X.509公钥基础设施标准的数字证书,而Baeldung.p12是一个受密码保护的PKCS12钥匙库,包含一个私钥。

Let’s see how these can be loaded in Java:

让我们看看这些如何在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);

First, we’ve added the BouncyCastleProvider as a security provider dynamically using the addProvider() method.

首先,我们使用addProvider()方法动态添加了BouncyCastleProvider作为安全提供者。

This can also be done statically by editing the {JAVA_HOME}/jre/lib/security/java.security file, and adding this line:

这也可以通过编辑{JAVA_HOME}/jre/lib/security/java.security文件,并添加这一行来静态完成。

security.provider.N = org.bouncycastle.jce.provider.BouncyCastleProvider

Once the provider is properly installed, we’ve created a CertificateFactory object using the getInstance() method.

一旦提供者被正确安装,我们已经使用getInstance()方法创建了一个CertificateFactory对象。

The getInstance() method takes two arguments; the certificate type “X.509”, and the security provider “BC”.

getInstance()方法需要两个参数;证书类型 “X.509″,以及安全提供者 “BC”。

The certFactory instance is subsequently used to generate an X509Certificate object, via the generateCertificate() method.

随后,certFactory实例通过generateCertificate()方法被用来生成X509Certificate对象。

In the same way, we’ve created a PKCS12 Keystore object, on which the load() method is called.

以同样的方式,我们创建了一个PKCS12 Keystore对象,并对其调用load()方法。

The getKey() method returns the private key associated with a given alias.

getKey()方法返回与给定别名相关的私钥。

Note that a PKCS12 Keystore contains a set of private keys, each private key can have a specific password, that’s why we need a global password to open the Keystore, and a specific one to retrieve the private key.

请注意,PKCS12钥匙库包含一组私钥,每个私钥可以有一个特定的密码,这就是为什么我们需要一个全局密码来打开钥匙库,以及一个特定密码来检索私钥。

The Certificate and the private key pair are mainly used in asymmetric cryptographic operations:

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

  • Encryption
  • Decryption
  • Signature
  • Verification

4.2. CMS/PKCS7 Encryption and Decryption

4.2.CMS/PKCS7加密和解密

In asymmetric encryption cryptography, each communication requires a public certificate and a private key.

在非对称加密密码学中,每次通信都需要一个公共证书和一个私人密钥。

The recipient is bound to a certificate, that is publicly shared between all senders.

收件人被绑定在一个证书上,该证书在所有发送者之间公开共享。

Simply put, the sender needs the recipient’s certificate to encrypt a message, while the recipient needs the associated private key to be able to decrypt it.

简单地说,发件人需要收件人的证书来加密信息,而收件人则需要相关的私钥来解密信息。

Let’s have a look at how to implement an encryptData() function, using an encryption certificate:

让我们看看如何使用加密证书来实现encryptData()函数。

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;
}

We’ve created a JceKeyTransRecipientInfoGenerator object using the recipient’s certificate.

我们已经使用收件人的证书创建了一个JceKeyTransRecipientInfoGenerator对象。

Then, we’ve created a new CMSEnvelopedDataGenerator object and added the recipient information generator into it.

然后,我们创建了一个新的CMSEnvelopedDataGenerator对象,并将收件人信息生成器加入其中。

After that, we’ve used the JceCMSContentEncryptorBuilder class to create an OutputEncrytor object, using the AES CBC algorithm.

之后,我们使用JceCMSContentEncryptorBuilder类来创建一个OutputEncrytor对象,使用AES CBC算法。

The encryptor is used later to generate a CMSEnvelopedData object that encapsulates the encrypted message.

加密器随后被用来生成一个CMSEnvelopedData对象,该对象封装了加密的消息。

Finally, the encoded representation of the envelope is returned as a byte array.

最后,信封的编码表示被作为一个字节数组返回。

Now, let’s see what the implementation of the decryptData() method looks like:

现在,让我们看看decryptData()方法的实现是什么样子。

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;
}

First, we’ve initialized a CMSEnvelopedData object using the encrypted data byte array, and then we’ve retrieved all the intended recipients of the message using the getRecipients() method.

首先,我们使用加密数据字节数组初始化了一个CMSEnvelopedData对象,然后我们使用getRecipients()方法检索了该消息的所有预期收件人。

Once done, we’ve created a new JceKeyTransRecipient object associated with the recipient’s private key.

一旦完成,我们就创建了一个新的JceKeyTransRecipient对象,与收件人的私钥相关。

The recipientInfo instance contains the decrypted/encapsulated message, but we can’t retrieve it unless we have the corresponding recipient’s key.

recipientInfo实例包含解密/封装的消息,但除非我们有相应收件人的密钥,否则我们无法检索到它。

Finally, given the recipient key as an argument, the getContent() method returns the raw byte array extracted from the EnvelopedData this recipient is associated with.

最后,给定收件人密钥作为参数,getContent()方法返回从EnvelopedData该收件人相关的原始字节数组。

Let’s write a simple test to make sure everything works exactly as it should:

让我们写一个简单的测试,以确保一切都能完全按照它应该的方式工作。

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);

As a result:

结果是。

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

4.3. CMS/PKCS7 Signature and Verification

4.3.CMS/PKCS7签名和验证

Signature and verification are cryptographic operations that validate the authenticity of data.

签名和验证是验证数据真实性的密码学操作。

Let’s see how to sign a secret message using a digital certificate:

让我们看看如何使用数字证书签署秘密信息。

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;
}

First, we’ve embedded the input into a CMSTypedData, then, we’ve created a new CMSSignedDataGenerator object.

首先,我们将输入嵌入到一个CMSTypedData,然后,我们创建了一个新的CMSSignedDataGenerator对象。

We’ve used SHA256withRSA as a signature algorithm, and our signing key to create a new ContentSigner object.

我们使用SHA256withRSA作为签名算法,以及我们的签名密钥来创建一个新的ContentSigner对象。

The contentSigner instance is used afterward, along with the signing certificate to create a SigningInfoGenerator object.

之后,contentSigner实例与签名证书一起被用来创建SigningInfoGenerator对象。

After adding the SignerInfoGenerator and the signing certificate to the CMSSignedDataGenerator instance, we finally use the generate() method to create a CMS signed-data object, which also carries a CMS signature.

在将SignerInfoGenerator和签名证书添加到CMSSignedDataGenerator实例后,我们最后使用generate()方法来创建一个CMS签名数据对象,它也带有CMS签名。

Now that we’ve seen how to sign data, let’s see how to verify signed data:

现在我们已经看到了如何对数据进行签名,让我们看看如何验证签名的数据。

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));
}

Again, we’ve created a CMSSignedData object based on our signed data byte array, then, we’ve retrieved all signers associated with the signatures using the getSignerInfos() method.

同样,我们根据我们的签名数据字节数组创建了一个CMSSignedData对象,然后,我们使用getSignerInfos()方法检索了与签名相关的所有签名者。

In this example, we’ve verified only one signer, but for generic use, it is mandatory to iterate over the collection of signers returned by the getSigners() method and check each one separately.

在这个例子中,我们只验证了一个签名人,但对于一般的使用,必须遍历getSigners()方法返回的签名人集合,并分别检查每个签名人。

Finally, we’ve created a SignerInformationVerifier object using the build() method and passed it to the verify() method.

最后,我们使用build()方法创建了一个SignerInformationVerifier对象,并将其传递给verify()方法。

The verify() method returns true if the given object can successfully verify the signature on the signer object.

如果给定的对象能够成功地验证签名者对象上的签名,则verify()方法返回true

Here’s a simple example:

这里有一个简单的例子。

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

As a result:

结果是。

true

5. Conclusion

5.结论

In this article, we’ve discovered how to use the BouncyCastle library to perform basic cryptographic operations, such as encryption and signature.

在这篇文章中,我们已经发现了如何使用BouncyCastle库来执行基本的加密操作,如加密和签名。

In a real-world situation, we often want to sign then encrypt our data, that way, only the recipient is able to decrypt it using the private key, and check its authenticity based on the digital signature.

在现实世界中,我们经常希望先签名后加密我们的数据,这样一来,只有接收者能够使用私钥解密,并根据数字签名检查其真实性。

The code snippets can be found, as always, over on GitHub.

像往常一样,可以在GitHub上找到代码片段,over on GitHub