Encrypt and decrypt anything (bytes/files/streams) with PGP using bouncy castle and java

Malkeith Singh
7 min readNov 12, 2022

--

bytes and file encryption and decryption using bouncy castle

PGP or Pretty Good Privacy is an encryption system that has been used widely across the internet with its main use case being encrypting emails and files. Its a type of asymmetric encryption, meaning there are two keys, one to lock and another one to open.

The one used to lock or encrypt the data can be shared freely with anyone and hence is called a public key. And the key used to unlock or decrypt the data is kept private and therefore is called a private key. Both the keys are generated together as pairs and are called asymmetric key pairs. Anyone who has the public key can encrypt the data and send it but only the person who has the associated private key can unlock it.

It is for this reason that it has become the defacto standard for email security and is widely adopted across the web. And hence, not suprisingly you have stumbled across a use case which has brought you to this blog.

I won’t be taking more about PGP, there is very good article on it here if you would like to dig into that. What I would be explaning though is how to implement a PGP encryption system in java using bouncy castle as the provider.

This system will allow you to PGP encrypt and decrypt anything, be it bytes, files, inputstreams or outputstreams. So whatever your use case might be, you can rest assure that it will be covered.

If you directly want to look at the code and skip the explanation, you can do that here: repo-link. I would suggest starting from the test class: PgpEncryptionTest , as it contains direct examples for bytes, files and inputstreams. Also, the readme.md file explains how to use the utility in a nutshell.

If you want to build this system from scratch and/or want a detailed explanation on the inner workings, then continue reading.

First thing we need to do when building any such system is isolate what’s common. If you look closely, bytes and files both can be represented as streams. So essentially, what we need to do is to build a mechanism that will allow us to encrypt and decrypt a stream. We can then build wrappers on top of it to handle our other use cases like bytes and files.

Digging into it a little deeper, we will need two streams, an inputstream (lets call it clearIn) to read the data from, and an outputstream (lets call it encryptOut) to write the encrypted output to, this is for encryption.

For decryption we just need to reverse the roles of the streams. The inputstream will represent the encrypted input and the outputstream with represent the unencrypted output.

Note:- If you would like to know more about inputstream, outputstream or java IO in particular I suggest reading this article.

Now lets actually get started but before that we need to do one last thing or first thing 🤔 depending on how you look at it, and that is adding the bouncy castle dependencies. I am using maven but if you are using any other build tool you can change the configuration accordingly.

Note:- The version here is 1.70 and I would recommend sticking with this as I have faced issues with other versions.

<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk15on</artifactId>
<version>1.70</version>
<scope>compile</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
<scope>compile</scope>
</dependency>

Now that we know our strategy and we have added the required dependencies, lets first start with encryption.

So we know that our method takes in an inputstream which represent the unencrypted data and an outputstream, to which we are going to write our encrypted output to. Apart from this we need two other things, length which tells us how much of the inputstream to read and lastly and very obviously the publicKey. Again, the format for the publicKey is inputstream simply because it then doesn’t matter if you have your public key as a file, as a string or you have to read it from a socket. You will be able to leverage the same method.

Below is the implementation for this “base method”.

Note:- Sorry for the screen-shot, pasting the actual code turns it into an unreadable mess but you can find the link to all of the encryption code here: PgpEncryptionUtil

pgp encrypting inputstream

Let me, in a nutshell explain what this method is doing:

We first create a PGPCompressedDataGenerator object and a PGPEncryptedDataGenerator object. These objects take some configuration parameters like which compression and symmetric key algorithm to use, “is integrity check required”, “is armour required”, buffer size etc. I have these parameters as instance variables which can be configured via the builder. Once these parameters are set and the instance is created, the same instance can be used to encrypted different data sources with different public Keys.

Once the above objects are instantiated we can then pass the public key referenced by publicKeyIn to the PGPEncryptedDataGenerator object. After that we need to open a stream from the PGPEncryptedDataGenerator object by calling it’s open method and passing in our original outputstream referenced by encryptOut.

The CommonUtis.copyAsLiteralData method then copies all the data from the unencrypted inputstream (clearIn) to the encrypted stream.

The method ends by closing all the resources in sequence.

Note:- Closing in sequence is important because only then its going to write the “end marker”, which denotes ending of encrypted data. Without the end marker you won’t be able to decrypt the data.

Now that we have our base method done lets see how we can use it to encrypt bytes:

pgp encrypting bytes

As you can see for bytes all we need to do is wrap the input bytes with a ByteArrayInputStream to convert it to an inputstream and for the output we can use a ByteArrayOutputStream directly. The ByteArrayOutputStream is backed by an internal byte array which we return at the end of the method.

And for Inputstream:

pgp encrypt inputstream

For encrypting an inputstream, i.e input is an inputstream and output is also an inputstream, we need to use a bit of a hack. What we can do is write the encrypted output to a temp file and return an inputstream to the temp file as the encrypted inputstream. This is exactly what I am doing above.

With that encryption implementation is done, lets have a look at the decryption implementation.

Note:- You can find the full decryption code here: PgpDecryptionUtil

pgp decrypt inputstream

As I had talked earlier we are just doing the reverse of encryption here, now our inputstream referenced by encryptedIn is encrypted and the outputstream referenced by clearOut represents the unencrypted data.

The method above reads the encrypted inputstream, strips it, i.e removes any armouring from it and extracts the encrypted data from it in the form of an object called PGPPublicKeyEncryptedData. This object represent the public key encoded data. Once we have this object and the associated PGPPrivateKey object we can then decrypted the data using the CommonUtils.decrypt method and write the output to the supplied outputstream.

Note:- I have intentionally not provided a more detailed explaination, simply because I think it goes beyond the scope of this article, but you can always go through the code and refer the bouncy castle docs if you are curious.

You must have noticed that the above method does not take a private key like the encryption method did with public key. This is because the private key needs to be passed to the constructor during object instantiation. Look at the constructor below.

I have designed it this way because a private key is bound to its associated public key. They are like sibling, they were generated together and the private key can only decrypt the data encrypted by its public counterpart. So it made sense to bind the private key to the decryption util object itself. Also this way we can avoid some operational overhead, the part of the code responsible for generating the PGPSecretRingCollection object is only executed once during object instantiation.

Decrypt bytes:

pgp decryption bytes

So there you have it, you now have a utility which will enable you to encrypt and decrypt anything with PGP. If you have any questions or suggestion let me know in the comments and I will get back to you.

Again, here is a link to the repo.

If you found this article helpful you can support me by following me on medium as it motivates me to write more. If you are feeling extra grateful you can always buy me a coffee here .

Also, let me know what would you like me to write about next.

That’s all folks, see you on the next one.

--

--

Malkeith Singh

Malkeith is a web developer with a stronger affinity towards backend development. He Loves designing and developing backend systems for the web.