Unable to decrypt PGP encrypted data using GraalVM Native Images
Hi, We're using BouncyCastle in some of our projects, where at startup we need to decrypt some sensitive configuration variables for the app to startup properly.
We've written a decryption wrapper based around this Bouncycastle example that works properly in most of them, however one of our applications is running as a GraalVM Native image, and we can't seem to find a way to make it work:
Exception in thread "main" org.bouncycastle.openpgp.PGPException: exception on setup: java.security.NoSuchAlgorithmException: no such algorithm: SHA-256 for provider BC
at org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder$1.get(Unknown Source)
at org.bouncycastle.openpgp.operator.PGPUtil.makeKeyFromPassPhrase(Unknown Source)
at org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory.makeKeyFromPassPhrase(Unknown Source)
at org.bouncycastle.openpgp.PGPPBEEncryptedData.getSessionKey(Unknown Source)
at org.bouncycastle.openpgp.PGPPBEEncryptedData.getDataStream(Unknown Source)
at org.example.ByteArrayHandler.decrypt(ByteArrayHandler.java:86)
at org.example.Main.main(Main.java:36)
at [email protected]/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.security.NoSuchAlgorithmException: no such algorithm: SHA-256 for provider BC
at [email protected]/sun.security.jca.GetInstance.getService(GetInstance.java:87)
at [email protected]/sun.security.jca.GetInstance.getInstance(GetInstance.java:206)
at [email protected]/java.security.MessageDigest.getInstance(MessageDigest.java:248)
at org.bouncycastle.jcajce.util.NamedJcaJceHelper.createMessageDigest(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.OperatorHelper.createDigest(Unknown Source)
... 8 more
We've tried different things, such as manually creating some reachability metadata, get the Agent to configure them automatically (and/or in addition to the manual ones), and add some extra parameters to initialise some classes at build time, but none of these actually improved the situation.
Attached you will find a small example project that reproduces the issue: graalvm-native-bouncycastle.tar.gz
Example:
- Run as a regular java application
$ ./gradlew runJar
> Task :runJar
Available security providers:
......
BC - BouncyCastle Security Provider v1.78.1
Decryption test:
String 'myEncryptedVar' decrypted successfully with passphrase 'myPassphrase'
- Run as a native application
$ ./gradlew clean nativeCompile && ./build/native/nativeCompile/GraalVM-Native-bouncycastle
<compillation output>
BUILD SUCCESSFUL in 1m 26s
6 actionable tasks: 6 executed
Available security providers:
.....
BC - BouncyCastle Security Provider v1.78.1
Decryption test:
Exception in thread "main" org.bouncycastle.openpgp.PGPException: exception on setup: java.security.NoSuchAlgorithmException: no such algorithm: SHA-256 for provider BC
at org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder$1.get(Unknown Source)
at org.bouncycastle.openpgp.operator.PGPUtil.makeKeyFromPassPhrase(Unknown Source)
at org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory.makeKeyFromPassPhrase(Unknown Source)
at org.bouncycastle.openpgp.PGPPBEEncryptedData.getSessionKey(Unknown Source)
at org.bouncycastle.openpgp.PGPPBEEncryptedData.getDataStream(Unknown Source)
at org.example.ByteArrayHandler.decrypt(ByteArrayHandler.java:86)
at org.example.Main.main(Main.java:36)
at [email protected]/java.lang.invoke.LambdaForm$DMH/sa346b79c.invokeStaticInit(LambdaForm$DMH)
Caused by: java.security.NoSuchAlgorithmException: no such algorithm: SHA-256 for provider BC
at [email protected]/sun.security.jca.GetInstance.getService(GetInstance.java:87)
at [email protected]/sun.security.jca.GetInstance.getInstance(GetInstance.java:206)
at [email protected]/java.security.Message
Digest.getInstance(MessageDigest.java:248)
at org.bouncycastle.jcajce.util.NamedJcaJceHelper.createMessageDigest(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.OperatorHelper.createDigest(Unknown Source)
... 8 more
I got this working eventually, by using the PGPainless library which still uses BouncyCastle underneath, but apparently in a more "compatible" way.
I've changed the code in my example to
String decrypted = pgPainlessDecrypt(base64DecodedVariable, passphrase);
and
private static String pgPainlessDecrypt(byte[] encryptedVariable, String passphrase) throws PGPException, IOException {
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedVariable);
ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream();
try (DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(encryptedStream)
.withOptions(
new ConsumerOptions()
.addDecryptionPassphrase(Passphrase.fromPassword(passphrase)))) {
Streams.pipeAll(decryptionStream, decryptedOutput);
decryptionStream.close();
MessageMetadata metadata = decryptionStream.getMetadata();
System.out.println(metadata);
}
return decryptedOutput.toString();
}
I'm keeping this issue open rather than closing it, as I believe it would still be a good idea to find out what's not working in GraalVM Native with the official BouncyCastle example, but that's of course a decision for the maintainers.
I'd suspect the VM has another version of Bouncy Castle installed on it. I think the above would have to be due to a class path issue "SHA-256" definitely exists as an alias, as does the class it references.
You should note that the problem could be explained by the BouncyCastle provider being improperly registered.
In your example project you are dynamically registering the BouncyCastle at runtime using Security.addProvider(). In the GraalVM documentation it states
New security providers cannot be registered at run time; all providers must be statically configured at executable build time
This implies that you must statically configure BouncyCastle in the java.security file for a GraalVM image to work using the standard getInstance("SHA-256, "BC") methods.
The PGPainless library works in GraalVM because it bypasses the registration of the BouncyCastle provider and directly references a privately loaded copy.
I hope this solution is not too late. As the issue is similar to #2001, here's a solution that worked for us:
Step 1: Create a Native Image Initializer
package org.example;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
public class BCInitializer {
static {
// Force provider registration during image build
Security.addProvider(new BouncyCastleProvider());
}
}
Step 2: update build.gradle file
Add the following build arguments to your native image configuration in your build.gradle file:
buildArgs.add('--initialize-at-build-time=org.example.BCInitializer')
buildArgs.add("--initialize-at-runtime=org.bouncycastle.jcajce.provider.drbg.DRBG\$Default,org.bouncycastle.jcajce.provider.drbg.DRBG\$NonceAndIV")
I hope this solution helps. Please let me know if you have any further questions or need additional guidance.
Best Regards,