bc-java icon indicating copy to clipboard operation
bc-java copied to clipboard

Unable to decrypt PGP encrypted data using GraalVM Native Images

Open GuLinux opened this issue 1 year ago • 3 comments

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

GuLinux avatar Sep 03 '24 13:09 GuLinux

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.

GuLinux avatar Sep 05 '24 06:09 GuLinux

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.

dghgit avatar Sep 08 '24 02:09 dghgit

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.

tonywasher avatar Sep 12 '24 14:09 tonywasher

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,

ligefeiBouncycastle avatar Feb 25 '25 05:02 ligefeiBouncycastle