Introduction to BouncyCastle with Java

1. Overview

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

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

2. Maven Configuration

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

<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
.

3. Setup Unlimited Strength Jurisdiction Policy Files

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.

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:

  • 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.

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

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.

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.

4. Cryptographic Operations

4.1. Preparing Certificate And Private Key

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.

Let’s see how these can be loaded in 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.

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

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

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

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

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

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

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

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.

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

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:

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.

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

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

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

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:

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.

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

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

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.

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.2 CMS/PKCS7 Signature And Verification

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.

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

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

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.

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.

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.

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

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

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

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

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
.

Leave a Reply

Your email address will not be published.