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 (bouncycastle.org). 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.
Setup
- 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. - Copy the above jar to a particular folder in your Java installation:
$JAVA_HOME/jre/lib/ext
.
Encrypt with New PGP Key
- 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).
Figure 1: Create a PGP key
- Generate an App in the same group. This will provide an API key for authentication by your Java application. Copy the API key.
Figure 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;
import java.security.*;
public class Sample {
static {
Security.addProvider(SdkmsJCE.getInstance());
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_ENDPOINT
=<DSM URL>
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_ENDPOINT=https://pkidsmtest.dhl.com
$ 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.
Examples
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:
<dependencies>
<!--bouncy castle jdk15on 1.69-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk15on</artifactId>
<version>1.69</version>
</dependency>
<!--fortanix jce provider 4.8.2069-->
<dependency>
<groupId>com.fortanix</groupId>
<artifactId>sdkms-jce-provider</artifactId>
<version>4.8.2069</version>
</dependency>
</dependencies>
Register the provider in the Java Security as below
public static BouncyCastleProvider provider = new BouncyCastleProvider();
static {
Security.addProvider(SdkmsJCE.getInstance());
Security.addProvider(provider);
}
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());
}
Encryption
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)
.setWithIntegrityPacket(true)
.setSecureRandom(new SecureRandom())
.setProvider(provider));
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(pgpPublicKey).setProvider(provider));
ArmoredOutputStream aOut = new ArmoredOutputStream(byteArrayOutputStream);
OutputStream cOut = encGen.open(aOut, compressedData.length);
cOut.write(compressedData);
cOut.close();
aOut.close();
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 = comData.open(bOut);
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(cos,
PGPLiteralData.BINARY,
PGPLiteralData.CONSOLE,
clearData.length,
new Date());
pOut.write(clearData);
pOut.close();
comData.close();
return bOut.toByteArray();
}
}
Decryption
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) it.next();
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");
}
}
Result
Encrypted message -> -----BEGIN PGP MESSAGE-----
Version: BCPG v1.69
hQEMA3LmYCeNaKUKAQgAm6Ip7AoYsxqRssXyTa2o8MF4E8UdXVoMIPAyD9wwafRh
4+1nr/RD8i1UAoGupJR3/lVC90QlJn3axvyFu2mrXvkBDWUTWCfHaitHy1/24bOa
O7jV3RvW6ZQoZqlFfbR8iu/zVxoSIZfCv1FQ+ZcylRR0pZIBtOwXe6tp7jC7t0eI
hGYue5pJXKLbeDfgg7Rrnw5TXuN5TPo9anlzt0RsRzQAaNwtZmaZobme7XkuhXij
90RM9Xpw44zsTSB30EZB8jNTqiXQXnxZsVzDXeLExF9kexMjSASmCAkdr7wYUaZn
JQAEtShubJBDZSwk6tTNy8eAgJvQmLX2PztqdYVvytJTAcEIxKXnzRP2AfL3PUSg
lOG3paOi3PonkRUTJN5nqoe7S0Vw7lHo21UQEVJlRW3bTcFz7V7kKZ1NGh90GoJ9
ikHzzoaD+iVICZNZP4Qds4USuII=
=h56K
-----END PGP MESSAGE-----
Decrypted message -> SDKMS JCE Encryption/Decryption Test
Comments
Please sign in to leave a comment.