PGP Integration with JCE Provider and Bouncy Castle

PGP Key Management with Fortanix DSM

In Java-based setups, Bouncy Castles (BC) is a popularly used library that provides cryptography and PGP-related implementation ( BC is built using the Java Cryptographic Architecture (JCA) Provider model. This means it provides support for using crypto keys injected using other JCA providers.

Fortanix DSM supports the JCA architecture with a provider that can be easily installed. This allows for integration with the BC implementation of PGP, with private keys residing in DSM itself. See more on Fortanix DSM JCE provider: Clients: Java Cryptography Extension (JCE) Provider

This solution will automatically fetch the private key from Fortanix DSM and cache it in memory for the BC library to use for PGP operations. There is an API Key that needs to be set to provide authentication for fetching this key. This can be further restricted by adding IP-based rules to your production server use only.



  1. Download the Fortanix DSM JCE provider JAR. For PGP integration, the JCE provider required some enhancements, which have been released in version 4.8.2069 and above.
  2. Copy the above jar to a particular folder in your Java installation: $JAVA_HOME/jre/lib/ext.

Encrypt with New PGP Key

  1. Generate an RSA Key in Fortanix DSM using the UI and assign it to a group. Make sure the key is exportable. Note the name of the key (security object). CreatePGPKey.pngFigure 1: Create a PGP key
  2. Generate an App in the same group. This will provide an API key for authentication by your Java application. Copy the API key. CreatePGPApp.pngFigure 2: Create a PGP app

The following is a sample code that will use this PGP private key in Fortanix DSM for PGP file encryption.

import com.fortanix.sdkms.v1.model.SobjectDescriptor;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
public class Sample {
static {
Security.addProvider(new BouncyCastleProvider());
public static void main(String args[]) {
com.fortanix.sdkms.jce.provider.RSAPrivateKeyImpl privKey = new com.fortanix.sdkms.jce.provider.RSAPrivateKeyImpl(new SobjectDescriptor().name("PGP-RSA-Key")); // name of RSA Key in DSM
com.fortanix.sdkms.jce.provider.RSAPublicKeyImpl pubKey = new com.fortanix.sdkms.jce.provider.RSAPublicKeyImpl(new SobjectDescriptor().name("PGP-RSA-Key")); // name of same RSA Key in DSM
KeyPair kp = new KeyPair(pubKey, privKey);
PGPKeyPair pgpKp = new JcaPGPKeyPair(
PGPPublicKey.RSA_GENERAL, kp, new Date());
// continue using pgpKp for BC based PGP operations.

You must additionally set the following environment variables for Fortanix DSM authentication:

  • FORTANIX_API_KEY=<apikey> // copied in Step 2 above.

These can be provided as a configuration file or as a programmatic input to the code.

Encrypt with Existing PGP Key

For importing the existing public and private PGP keys from local to Fortanix DSM, you can use the Fortanix Java-based PGP Key importer JAR or the Fortanix sq-dsm tool.

For importing an existing file-based keystore to Fortanix DSM, if the existing keystore is a standard JKS or P12 keystore, the Java utility keytool can be used to import the keystore.

Here is a Linux example command which you can run in the CLI where the Fortanix JAR is already installed. This will work on Windows CMD too.

$ export FORTANIX_API_KEY=<apikey>
$ keytool -importkeystore -srckeystore pgp-ks.jks -destkeystore dsm-ks -deststoretype SDKMS -destProviderName sdkms-jce

The above command will import the private key into Fortanix DSM. Note the name of the key from the Fortanix DSM UI and proceed as explained in the previous section.


This section describes encryption and decryption using Bouncy Castle and the Fortanix JCE Provider.

The following code snippet exports the public and private keys from Fortanix DSM and stores them in memory. It performs encryption on plain text and decrypts the cipher to retrieve the plain text back.

The following Maven dependencies are required:

<!--bouncy castle jdk15on 1.69-->

<!--fortanix jce provider 4.8.2069-->


Register the provider in the Java Security as below

public static BouncyCastleProvider provider = new BouncyCastleProvider();

static {

Export the public and private keys from DSM

public static void main(String args[]) throws PGPException, IOException {

RSAPrivateKeyImpl privKey = new RSAPrivateKeyImpl(new SobjectDescriptor().name("PgpKeyPublic")); // name of the public RSA Key in DSM

RSAPublicKeyImpl pubKey = new RSAPublicKeyImpl(new SobjectDescriptor().name("PgpKeyPrivate")); // name of the private RSA Key in DSM

KeyPair kp = new KeyPair(pubKey, privKey);

PGPKeyPair pgpKp = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, kp, new Date());

// continue using pgpKp for BC based PGP operations.
byte[] byteArr = encrypt(PLAIN.getBytes(), pgpKp.getPublicKey());
decrypt(byteArr, privKey ,pgpKp.getPrivateKey());



public static byte[] encrypt(byte[] bytes, PGPPublicKey pgpPublicKey) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] compressedData = compress(bytes);

PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
.setSecureRandom(new SecureRandom())

encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(pgpPublicKey).setProvider(provider));

ArmoredOutputStream aOut = new ArmoredOutputStream(byteArrayOutputStream);
OutputStream cOut =, compressedData.length);

System.out.println("Encrypted message -> " + byteArrayOutputStream.toString());
return byteArrayOutputStream.toByteArray();
} catch (PGPException e) {
System.err.println("Exception encountered while encoding data. "+e);
return new byte[0];

private static byte[] compress(byte[] clearData) throws IOException {
try (ByteArrayOutputStream bOut = new ByteArrayOutputStream()) {
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
OutputStream cos =;
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut =,
new Date());
return bOut.toByteArray();


public static void decrypt(byte[] encrypted, RSAPrivateKeyImpl privKey, PGPPrivateKey pgpKey)
throws IOException, PGPException {
InputStream in = new ByteArrayInputStream(encrypted);

in = PGPUtil.getDecoderStream(in);
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
PGPEncryptedDataList enc;

Object o = pgpF.nextObject();
// the first object might be a PGP marker packet.
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();

PGPPublicKeyEncryptedData pbe = null;
Iterator<PGPEncryptedData> it = enc.getEncryptedDataObjects();

pbe = (PGPPublicKeyEncryptedData);

InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(provider).build(privKey));
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object message = plainFact.nextObject();
String content = null;
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
message = pgpFact.nextObject();
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
content = new String(IOUtils.toByteArray(ld.getInputStream()));
System.out.println("Decrypted message -> " + content);
} else if (message instanceof PGPOnePassSignatureList) {
throw new PGPException("encrypted message contains a signed message - not literal data.");
} else {
throw new PGPException("message is not a simple encrypted file - type unknown.");

if (pbe.isIntegrityProtected()) {
if (!pbe.verify()) {
System.err.println("message failed integrity check");
} else {
System.out.println("message integrity check passed");
} else {
System.out.println("no message integrity check");


Encrypted message -> -----BEGIN PGP MESSAGE-----
Version: BCPG v1.69


Decrypted message -> SDKMS JCE Encryption/Decryption Test



Please sign in to leave a comment.

Was this article helpful?
0 out of 0 found this helpful