简体   繁体   中英

Getting GPG Decryption To Work In Java (Bouncy Castle)

let me start by saying I'm extremely new to all of this. What I am trying to do is to use gpg from within Java in order to decrypt an encrypted file.

What I've done successfully:

  • Had a colleague encrypt a file using my public key and his private key and successfully decrypted it.

  • Went the other way

  • Had another colleague try to decrypt a file that wasn't for him: fail (as expected)

My key was generated like this...

(gpg --version tells me I'm using 1.4.5 and I'm using Bouncy Castle 1.47)

gpg --gen-ley

Select option "DSA and Elgamal (default)"

Fill in the other fields and generate a key.

The file was encrypted using my public key and another's secret key. I want to decrypt it. I've written the following Java code to accomplish this. I'm using several deprecated methods, but I can't figure out how to properly implement the factory methods required to use the non-deprecated versions, so if anyone has an idea on implementations of those that I should be using that would be a nice bonus.

    Security.addProvider(new BouncyCastleProvider());

        PGPSecretKeyRingCollection secretKeyRing = new PGPSecretKeyRingCollection(new FileInputStream(new File("test-files/secring.gpg")));
        PGPSecretKeyRing pgpSecretKeyRing = (PGPSecretKeyRing) secretKeyRing.getKeyRings().next();
        PGPSecretKey secretKey = pgpSecretKeyRing.getSecretKey();
        PGPPrivateKey privateKey = secretKey.extractPrivateKey("mypassword".toCharArray(), "BC");

        System.out.println(privateKey.getKey().getAlgorithm());
        System.out.println(privateKey.getKey().getFormat());

        PGPObjectFactory pgpF = new PGPObjectFactory(
    new FileInputStream(new File("test-files/test-file.txt.gpg")));
        Object pgpObj = pgpF.nextObject();
        PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) pgpObj;

        Iterator objectsIterator = encryptedDataList.getEncryptedDataObjects();

        PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) objectsIterator.next();
        InputStream inputStream = publicKeyEncryptedData.getDataStream(privateKey, "BC");

So when I run this code I learn that my algorithm and format are as follows for my secret key:

Algorithm: DSA Format: PKCS#8

And then it breaks on the last line:

Exception in thread "main" org.bouncycastle.openpgp.PGPException: error setting asymmetric cipher
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.decryptSessionData(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.access$000(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder$2.recoverSessionData(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at TestBouncyCastle.main(TestBouncyCastle.java:74)

Caused by: java.security.InvalidKeyException: unknown key type passed to ElGamal at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source) at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source) at javax.crypto.Cipher.init(DashoA13*..) at javax.crypto.Cipher.init(DashoA13*..) ... 8 more

I'm open to a lot of suggestions here, from "don't use gpg, use x instead" to "don't use bouncy castle, use x instead" to anything in between. Thanks!

If anyone is interested to know how to encrypt and decrypt gpg files using bouncy castle openPGP library, check the below java code:

The below are the 4 methods you going to need:

The below method will read and import your secret key from .asc file:

public static PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException {
    in = PGPUtil.getDecoderStream(in);
    PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator());

    PGPSecretKey key = pgpSec.getSecretKey(keyId);

    if (key == null) {
        throw new IllegalArgumentException("Can't find encryption key in key ring.");
    }
    return key;
}

The below method will read and import your public key from .asc file:

@SuppressWarnings("rawtypes")
    public static PGPPublicKey readPublicKeyFromCol(InputStream in) throws IOException, PGPException {
        in = PGPUtil.getDecoderStream(in);
        PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator());
        PGPPublicKey key = null;
        Iterator rIt = pgpPub.getKeyRings();
        while (key == null && rIt.hasNext()) {
            PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
            Iterator kIt = kRing.getPublicKeys();
            while (key == null && kIt.hasNext()) {
                PGPPublicKey k = (PGPPublicKey) kIt.next();
                if (k.isEncryptionKey()) {
                    key = k;
                }
            }
        }
        if (key == null) {
            throw new IllegalArgumentException("Can't find encryption key in key ring.");
        }
        return key;
    }

The below 2 methods to decrypt and encrypt gpg files:

public void decryptFile(InputStream in, InputStream secKeyIn, InputStream pubKeyIn, char[] pass) throws IOException, PGPException, InvalidCipherTextException {
        Security.addProvider(new BouncyCastleProvider());

        PGPPublicKey pubKey = readPublicKeyFromCol(pubKeyIn);

        PGPSecretKey secKey = readSecretKeyFromCol(secKeyIn, pubKey.getKeyID());

        in = PGPUtil.getDecoderStream(in);

        JcaPGPObjectFactory pgpFact;


        PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator());

        Object o = pgpF.nextObject();
        PGPEncryptedDataList encList;

        if (o instanceof PGPEncryptedDataList) {

            encList = (PGPEncryptedDataList) o;

        } else {

            encList = (PGPEncryptedDataList) pgpF.nextObject();

        }

        Iterator<PGPPublicKeyEncryptedData> itt = encList.getEncryptedDataObjects();
        PGPPrivateKey sKey = null;
        PGPPublicKeyEncryptedData encP = null;
        while (sKey == null && itt.hasNext()) {
            encP = itt.next();
            secKey = readSecretKeyFromCol(new FileInputStream("PrivateKey.asc"), encP.getKeyID());
            sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
        }
        if (sKey == null) {
            throw new IllegalArgumentException("Secret key for message not found.");
        }

        InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));

        pgpFact = new JcaPGPObjectFactory(clear);

        PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject();

        pgpFact = new JcaPGPObjectFactory(c1.getDataStream());

        PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        InputStream inLd = ld.getDataStream();

        int ch;
        while ((ch = inLd.read()) >= 0) {
            bOut.write(ch);
        }

        //System.out.println(bOut.toString());

        bOut.writeTo(new FileOutputStream(ld.getFileName()));
        //return bOut;

    }

    public static void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey) throws IOException, NoSuchProviderException, PGPException {
        Security.addProvider(new BouncyCastleProvider());

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);

        PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName));

        comData.close();

        PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom()));

        cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey));

        byte[] bytes = bOut.toByteArray();

        OutputStream cOut = cPk.open(out, bytes.length);

        cOut.write(bytes);

        cOut.close();

        out.close();
    }

Now here is how to invoke/run the above:

try {
             decryptFile(new FileInputStream("encryptedFile.gpg"), new FileInputStream("PrivateKey.asc"), new FileInputStream("PublicKey.asc"), "yourKeyPassword".toCharArray());

            PGPPublicKey pubKey = readPublicKeyFromCol(new FileInputStream("PublicKey.asc"));

            encryptFile(new FileOutputStream("encryptedFileOutput.gpg"), "fileToEncrypt.txt", pubKey);




        } catch (PGPException e) {
            fail("exception: " + e.getMessage(), e.getUnderlyingException());
        }

To any one looking for an alternative solution, see https://stackoverflow.com/a/42176529/7550201

final InputStream plaintextStream = BouncyGPG
           .decryptAndVerifyStream()
           .withConfig(keyringConfig)
           .andRequireSignatureFromAllKeys("sender@example.com")
           .fromEncryptedInputStream(cipherTextStream)

Long story short: Bouncycastle is programming is often a lot of cargo cult programming and I wrote a library to change that.

I've decided to go with a much different approach, which is to forego the use of bouncy castle altogether and simply use a runtime process instead. For me this solution is working and completely removes the complexity surrounding bouncy castle:

String[] gpgCommands = new String[] {
        "gpg",
        "--passphrase",
        "password",
        "--decrypt",
        "test-files/accounts.txt.gpg"
};

Process gpgProcess = Runtime.getRuntime().exec(gpgCommands);
BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedReader gpgError = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));

After doing that you need to remember to drain your input stream as your process is execing or your program will probably hang depending on how much you're outputing. See my answer in this thread (and also that of Cameron Skinner and Matthew Wilson who got me on the proper path) for a bit more context: Calling GnuPG in Java via a Runtime Process to encrypt and decrypt files - Decrypt always hangs

The first Google result is this . It looks like you are trying to decrypt ElGamal data, but you are not passing in an ElGamal key.

There are two easy possibilities:

  • Your keyring collection has multiple keyrings.
  • Your keyring has subkeys.

You've picked DSA with ElGamal encryption, so I suspect at least the latter: Subkeys are signed by the master key; ElGamal is not a signing algorithm (I don't know if DSA and ElGamal can use the same key, but it's generally seen as a good idea to use different keys for different purposes).

I think you want something like this (also, secretKeyRing should probably be renamed to secretKeyRingCollection ):

PGPSecretKey secretKey = secretKeyRing.getSecretKey(publicKeyEncryptedData.getKeyID());

Using the standard modern Java Core classes that have the JCE included (eg Java 1.8_303+), BouncyCastle Core, and Bouncy Castle provider, I have developed a Spring-based Service that can handle PGP encryption and decryption from public/private keys contained within Resource files. If you are not using Spring, you can strip out the Spring specific code and just leverage the public encrypt/decrypt methods along with the private support methods:

package com.your.organization.impl;

import com.your.organization.exception.EncryptionException; // Your own Exception class
import com.your.organization.service.PgpEncryptionService; // Your own Interface class
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Iterator;

@Service("pgpEncryptionService")
public final class PgpEncryptionServiceImpl implements PgpEncryptionService {

    @PostConstruct
    public void initializeSecurityProviders() {

        // Add the Bouncy Castle security Provider to the JVM
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * Encrypts a cleared message {@link String} using the classpath PGPPublicKey using
     * {@link ArmoredOutputStream} to further protect the encrypted message.
     *
     * @param message {@link String}
     * @return Encrypted String with, or without, armoring
     * @throws EncryptionException is thrown if the {@link PGPEncryptedDataGenerator} could not be initialized
     *                             from the provided PGPPublicKey or if the encoded message {@link OutputStream}
     *                             could not be opened
     */
    public String encrypt(String message) throws EncryptionException {

        /*
         * Initialize an OutputStream or ArmoredOutputStream for the encrypted message based on the armor
         * function input
         */
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        OutputStream armoredOutputStream = byteArrayOutputStream;
        armoredOutputStream = new ArmoredOutputStream(armoredOutputStream);

        // Initialize and configure the encryption generator using the provided PGPPublicKey
        PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator(
                new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256)
                        .setSecureRandom(new SecureRandom())
                        .setProvider("BC"));

        pgpEncryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(getPublicKey())
                .setProvider("BC"));

        // Convert message String to byte[] using standard UTF-8
        byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);

        // Open the PGPEncryptedDataGenerator from the ArmoredOutputStream initialized to the message body length
        OutputStream encryptedOutputStream;
        try {
            encryptedOutputStream = pgpEncryptedDataGenerator.open(armoredOutputStream, messageBytes.length);
        } catch (IOException | PGPException e) {
            throw new EncryptionException("Could not open an OutputStream from the PGPEncryptedDataGenerator " +
                    "using the provided message body", e);
        }

        // Write the encrypted message to the encryptedOutputStream
        try {
            encryptedOutputStream.write(messageBytes);
        } catch (IOException e) {
            throw new EncryptionException("Could not write the message body to the encrypted OutputStream", e);
        } finally {

            // Close the encrypted message OutputStream
            try {
                encryptedOutputStream.close();
            } catch (IOException e) {
                // TODO: Log this
            }

            // Close the ArmoredOutputStream
            try {
                armoredOutputStream.close();
            } catch (IOException e) {
                // TODO: Log this
            }
        }

        // Return the encrypted message OutputStream to a String
        return byteArrayOutputStream.toString();
    }

    /**
     * Decrypts an encrypted message {@link String} using the {@link PGPSecretKey} on the classpath and its
     * password {@link String}
     *
     * @param encryptedMessage {@link String}
     * @param password         {@link String}
     * @return String
     * @throws EncryptionException is thrown if an encrypted message InputStream cannot be initialized from the
     *                             encryptedMessage {@link String}, if the PGPEncryptedDataList from that stream
     *                             contains no data, or if the password {@link String} for the
     *                             {@link PGPSecretKey} is incorrect
     */
    public String decrypt(String encryptedMessage, String password) throws EncryptionException {

        // Convert the encrypted String into an InputStream
        InputStream encryptedStream = new ByteArrayInputStream(encryptedMessage.getBytes(StandardCharsets.UTF_8));
        try {
            encryptedStream = PGPUtil.getDecoderStream(encryptedStream);
        } catch (IOException e) {
            throw new EncryptionException("Could not initialize the DecoderStream", e);
        }

        // Retrieve the PGPEncryptedDataList from the encryptedStream
        JcaPGPObjectFactory jcaPGPObjectFactory = new JcaPGPObjectFactory(encryptedStream);
        PGPEncryptedDataList pgpEncryptedDataList;

        /*
         * Evaluate the first object for a leading PGP marker packet and then return the encrypted
         * message body as a PGPEncryptedDataList
         */
        try {
            Object nextDataObject = jcaPGPObjectFactory.nextObject();
            if (nextDataObject instanceof PGPEncryptedDataList) {
                pgpEncryptedDataList = (PGPEncryptedDataList) nextDataObject;
            } else {
                pgpEncryptedDataList = (PGPEncryptedDataList) jcaPGPObjectFactory.nextObject();
            }
        } catch (IOException e) {
            throw new EncryptionException("Could not retrieve the encrupted message body", e);
        }

        // Retrieve the public key encrypted data from the encrypted message body
        PGPPublicKeyEncryptedData pgpPublicKeyEncryptedData =
                (PGPPublicKeyEncryptedData) pgpEncryptedDataList.getEncryptedDataObjects().next();

        // Use the PGPPublicKeyEncryptedData and Secret Key password to decrypt the encoded message
        InputStream decryptedInputStream;
        try {
            decryptedInputStream =
                    pgpPublicKeyEncryptedData.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder()
                            .setProvider("BC")
                            .build(getPrivateKey(getSecretKey(), password)));
        } catch (PGPException e) {
            throw new EncryptionException("Could not decrypt the encoded message from the application " +
                    "Secret Key or the embedded Private Key", e);
        }

        // Convert the InputStream of the decrypted message to a String
        try {
            return IOUtils.toString(decryptedInputStream, StandardCharsets.UTF_8.name());
        } catch (IOException e) {
            throw new EncryptionException("Could not convert the decrypted InputStream to a UTF-8 String", e);
        }
    }

    /**
     * Helper method for retrieving the {@link PGPPublicKey} from the application classpath.
     *
     * @return PGPPublicKey
     * @throws EncryptionException is thrown in the event that the PGP Public Key file does not contain a
     *                             Public Key or if the Public Key cannot be located on the file system
     */
    private PGPPublicKey getPublicKey() throws EncryptionException {

        // Retrieve the application PGP public key file from the classpath
        File publicKeyFile;
        try {
            publicKeyFile = new ClassPathResource("keys/yourpublickey-pub.asc").getFile();
        } catch (IOException e) {
            throw new EncryptionException("Could not retrieve the PGP Public Key from the classpath", e);
        }

        // Read Public Key from the file
        FileInputStream pubKey;
        try {
            pubKey = new FileInputStream(publicKeyFile);
        } catch (FileNotFoundException e) {
            throw new EncryptionException("Could not retrieve the PGP Public Key from the file system", e);
        }

        // Load PGPPublicKey FileInputStream into the PGPPublicKeyRingCollection
        PGPPublicKeyRingCollection pgpPub;
        try {
            pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(pubKey),
                    new JcaKeyFingerprintCalculator());
        } catch (IOException | PGPException e) {
            throw new EncryptionException("Could not initialize the PGPPublicKeyRingCollection", e);
        }


        // Retrieve Public Key and evaluate if for the encryption key
        Iterator<PGPPublicKeyRing> keyRingIter = pgpPub.getKeyRings();
        while (keyRingIter.hasNext()) {
            PGPPublicKeyRing keyRing = keyRingIter.next();

            Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
            while (keyIter.hasNext()) {
                PGPPublicKey key = keyIter.next();

                if (key.isEncryptionKey()) {
                    return key;
                }
            }
        }

        throw new EncryptionException("The application PGPPublicKey is not an allowable encryption key");
    }

    /**
     * Helper method for retrieving the signing key {@link PGPSecretKey} from the classpath.
     *
     * @return Signing key {@link PGPSecretKey}
     * @throws EncryptionException is thrown if the Secret Key is not a signing key or if the Secret Key file
     *                             could not be located on the file system
     */
    private PGPSecretKey getSecretKey() throws EncryptionException {

        // Retrieve the application PGP secret key file from the classpath
        File secretKeyFile;
        try {
            secretKeyFile = new ClassPathResource("keys/yoursecretkey-sec.asc").getFile();
        } catch (IOException e) {
            throw new EncryptionException("Could not retrieve the PGP Secret Key from the classpath", e);
        }

        // Read Secret Key file and load it into a PGPPublicKeyRingCollection for evaluation
        FileInputStream secKey;
        try {
            secKey = new FileInputStream(secretKeyFile);
        } catch (FileNotFoundException e) {
            throw new EncryptionException("Could not retrieve the PGP Secret Key from the file system", e);
        }

        // Load PGPSecretKey FileInputStream into the PGPSecretKeyRingCollection
        PGPSecretKeyRingCollection pgpSec;
        try {
            pgpSec = new PGPSecretKeyRingCollection(
                    PGPUtil.getDecoderStream(secKey), new JcaKeyFingerprintCalculator());
        } catch (IOException | PGPException e) {
            throw new EncryptionException("Could not initialize the PGPSecretKeyRingCollection", e);
        }

        // Retrieve signing Secret Key
        Iterator<PGPSecretKeyRing> secretKeyRingIterator = pgpSec.getKeyRings();
        while (secretKeyRingIterator.hasNext()) {
            PGPSecretKeyRing keyRing = secretKeyRingIterator.next();

            Iterator<PGPSecretKey> keyIter = keyRing.getSecretKeys();
            while (keyIter.hasNext()) {
                PGPSecretKey key = keyIter.next();

                if (key.isSigningKey()) {
                    return key;
                }
            }
        }

        throw new EncryptionException("The application PGPSecretKey is not a signing key");
    }

    /**
     * Retrieves the {@link PGPPrivateKey} from the provided {@link PGPSecretKey} and its password.
     *
     * @param secretKey {@link PGPSecretKey}
     * @param password  {@link String}
     * @return PGPPrivateKey
     * @throws EncryptionException is thrown in the event that the password for the {@link PGPSecretKey}
     *                             is incorrect
     */
    private PGPPrivateKey getPrivateKey(PGPSecretKey secretKey, String password) throws EncryptionException {

        PBESecretKeyDecryptor decryptorFactory = new BcPBESecretKeyDecryptorBuilder(
                new BcPGPDigestCalculatorProvider()).build(password.toCharArray());

        try {
            return secretKey.extractPrivateKey(decryptorFactory);
        } catch (PGPException e) {
            throw new EncryptionException("Could not extract the Private Key from the application Secret Key", e);
        }
    }
}

The error message is difficult because it's not completely accurate. Besides the illegal key size or default parameters the exception doesn't says it could be failing because of crypto permission check fails. That means you haven't setup the JCE permissions properly. You'll need to install the JCE Unlimited Strength Policy .

You can see the debug messages by setting the system property on the jvm

java -Djava.security.debug=access ....

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM