Part 5: Introduction to Encryption using Java and Bouncy Castle

In an era where data breaches and cyber threats dominate headlines, securing information has become more critical than ever. Cryptography, the art of protecting data through encryption and decryption, is at the heart of modern cybersecurity. This article introduces you to cryptography programming using Java, a versatile language known for its robust security features. Whether you're safeguarding sensitive data or building secure applications, Java’s cryptography tools provide a solid foundation to get started.

Feel free to read my previous blogs if you would like to get a basic understanding on cryptography and related terminology.

In this article, let us explore how to encrypt and decrypt the data using a third-party cryptography provider for java called “bouncy castle”.

Disclaimer: This coding project requires basic understanding of java and apache maven, if you are not familiar with the terms used in here please make sure to read my previous articles for a better understanding.

I am using jdk17 and apache maven ver.3.9.6 for this project, please find the below code to add the required dependencies:

  <dependencies>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcprov-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>
    </dependencies>

As described in my previous articles, to perform encryption/decryption we need a public-private key pair. let us generate a self-signed public-private key pair using the following commands.

1. Generating the private Key:
$ openssl genpkey -algorithm RSA -out private.key -pkeyopt rsa_keygen_bits:2048

2. Generating the public key
$ openssl rsa -pubout -in private.key -out public.key

3. Generating a csr
$ openssl req -new -key private.key -out request.csr

4. Creating the self-signed certificate
$ openssl x509 -req -days 365 -in request.csr -signkey private.key -out certificate.cer

The below code “Encryption.java“ performs encryption.

package org.cryptography;

import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.OutputEncryptor;

import java.io.FileInputStream;
import java.io.IOException;
import java.security.Security;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;

public class Encryption {

    private X509Certificate publicCertificate = null;
    private byte[] dataToBeEncrypted = null;
    public byte[] encryptedData = null;
    public String finalEncryptedData = null;
    public String certificateLocation = null;


    public void setDataToBeEncrypted(byte[] dataToBeEncrypted) {
        this.dataToBeEncrypted = dataToBeEncrypted;
    }

    public void setCertificateLocation(String certificateLocation) {
        this.certificateLocation = certificateLocation;
    }

    public String performEncryption() {
        //null checks before performing encryption.
        if (this.dataToBeEncrypted == null || certificateLocation == null) {
            return null;
        }
        //fetching the public certificate.
        this.publicCertificate = getX509Certificate(certificateLocation);
        if (publicCertificate == null) {
            return null;
        }
        Security.addProvider(new BouncyCastleProvider());
        CMSEnvelopedDataGenerator cmsEnvelopedDataGenerator = new CMSEnvelopedDataGenerator();
        try {
            JceKeyTransRecipientInfoGenerator bceKey = new JceKeyTransRecipientInfoGenerator(publicCertificate);
            cmsEnvelopedDataGenerator.addRecipientInfoGenerator(bceKey);
            CMSTypedData cmsTypedData = new CMSProcessableByteArray(dataToBeEncrypted);
            OutputEncryptor encryptor
                    = new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(new BouncyCastleProvider()).build();

            CMSEnvelopedData cmsEnvelopedData = cmsEnvelopedDataGenerator.generate(cmsTypedData,encryptor);
            encryptedData = cmsEnvelopedData.getEncoded();

            this.finalEncryptedData = Base64.getEncoder().encodeToString(encryptedData);
            String formattedData = formatEncryptedData(this.finalEncryptedData);

            this.finalEncryptedData = formattedData;

        } catch (CertificateEncodingException | CMSException | IOException e) {
            throw new RuntimeException(e);
        }


        return finalEncryptedData;

    }

    public X509Certificate getX509Certificate(String certificateLocation) {
        X509Certificate certificate = null;
        try {
            Security.addProvider(new BouncyCastleProvider());
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509", new BouncyCastleProvider());
            certificate = (X509Certificate) certFactory.generateCertificate(new FileInputStream(certificateLocation));
            if (certificate == null) {
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return certificate;
    }

    public String formatEncryptedData(String data) {
        StringBuilder formattedString = new StringBuilder();
        int length = data.length();
        int lineLength = 64;

        for (int i = 0; i < length; i += lineLength) {
            if (i + lineLength < length) {
                formattedString.append(data, i, i + lineLength).append(System.lineSeparator());
            } else {
                formattedString.append(data.substring(i)).append(System.lineSeparator());
            }
        }

        return formattedString.toString();
    }

}

Explanation:

Here’s a detailed explanation of the provided code:

  1. Class Declaration (Encryption): The Encryption class encapsulates the logic for encrypting data using a public certificate. It uses the Bouncy Castle library, a popular Java cryptographic library.

  2. Member Variables:

    • publicCertificate: Holds the public X.509 certificate for encryption.

    • dataToBeEncrypted: Stores the data that needs encryption.

    • encryptedData: Stores the raw encrypted byte array.

    • finalEncryptedData: Stores the Base64-encoded encrypted data as a formatted string.

    • certificateLocation: Stores the file path to the public certificate.

  3. Data Setter Methods:

    • setDataToBeEncrypted: Assigns the data to be encrypted.

    • setCertificateLocation: Assigns the location of the certificate file.

  4. Encryption Logic (performEncryption):

    • Validates that the data and certificate location are not null.

    • Fetches the public certificate using the getX509Certificate method.

    • Uses Bouncy Castle's CMSEnvelopedDataGenerator to generate encrypted data:

      • Adds recipient information using the public certificate.

      • Specifies AES-128-CBC as the encryption algorithm via JceCMSContentEncryptorBuilder.

      • Processes the input data (CMSProcessableByteArray) and generates the encrypted envelope.

    • Converts the encrypted byte array to Base64 and formats it into 64-character lines using the formatEncryptedData method.

  5. Certificate Fetching (getX509Certificate):

    • Loads the X.509 certificate from the specified file location using CertificateFactory.

    • Adds the Bouncy Castle provider to handle certificate generation.

    • Returns the parsed X509Certificate.

  6. Data Formatting (formatEncryptedData):

    • Takes the Base64-encoded encrypted string and splits it into lines of 64 characters.

    • Appends a system line separator (System.lineSeparator()) after each chunk for readability.

  7. Error Handling:

    • Wraps critical sections (certificate parsing and encryption) in try-catch blocks to handle exceptions like CertificateEncodingException, CMSException, and IOException.

    • Uses RuntimeException for unchecked error propagation in encryption logic.

  8. Bouncy Castle Integration:

    • Adds the BouncyCastleProvider explicitly to ensure compatibility with cryptographic operations and certificate handling.
  9. Encryption Algorithm:

    • The class uses AES-128-CBC (Cipher Block Chaining) for encrypting the data, ensuring secure and efficient encryption.
  10. Use Case:

    • This class is suitable for applications that need to encrypt sensitive data using a public key certificate. The formatted Base64 output is ideal for transferring or storing encrypted data in a readable format.

Please visit my git repository to download the code, and perform the encryption for your own data.

Code Repository Link: https://github.com/sai-keerthan/BouncyCastle

In my next article, let us get to know how to perform decryption using java and bouncy castle.

Until next time, please subscribe and follow me for such content!

Happy Reading,

Sai Keerthan, Junior Developer.