Course – LSS – NPI (cat=Security/Spring Security)
announcement - icon

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE

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-jdk18on</artifactId>
    <version>1.76</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. Generating Certificate and Keystore

In case we want to generate another certificate, we will have to generate another KeyStore that will be linked to that certificate.

To generate a certificate, we need to run the following commands:

  1. Generate a private key of 2048 length:
    openssl genrsa -out private-key.pem 2048
  2. Generate a certificate signed request with that private key:
    openssl req -new -sha256 -key private-key.pem -out certificate-signed-request.csr
  3. Generate a self-signed certificate suitable for web servers:
    openssl req -x509 -sha256 -days 365 -key private-key.pem -in certificate-signed-request.csr -out Baeldung.cer
  4. Generate Keystore in the format of PKCS12:
    openssl pkcs12 -export -name baeldung -out Baeldung.p12 -inkey private-key.pem -in Baeldung.cer

After successfully generating the certificate, add the certificate to the resource folder. Please ensure that the correct certificate and KeyStore name are referenced in the code.

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

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)
Comments are closed on this article!