Search code examples
javaencryptionbouncycastlegnupg

Stream symmetric encrypted data with java that can be decryptet by GPG


I am trying to stream output a large file that can be decrypted with the following command:

gpg --decrypt --cipher-algo AES256 --passphrase="password"

I do not know the size of the data before I start streaming.

I am able to encrypt using Java Cipher, but it appears then that it can only be decrypted using OpenSSL.

I have looked into Bouncy Castle and BouncyGPG but find it hard to find good examples using symmetric encryption and a passphrase only. I am able to encrypt using GPG and a passphrase in both ends, so I feel that it should be possible when encrypting with Java too.

The closest example I've found is https://github.com/bcgit/bc-java/blob/master/pg/src/main/java/org/bouncycastle/openpgp/examples/PBEFileProcessor.java

I have tried to adjust this example so that I can pass through a stream in a similar way to Java Cipher, but I've been unable to get it working, and I don't even know if I am on the right path.

UPDATE:

I got it to work now. Below is the code used:

    public OutputStream cOut;

    public OutputStream armoredOutputStream;

    public OutputStream encryptedOutputStream;

    public PGPEncrypter() {
        Security.addProvider(new BouncyCastleProvider());
    }

    public OutputStream encrypt(
        OutputStream encryptedOutputStream, String passPhrase
    ) throws IOException,
        PGPException {

        encryptedOutputStream = new ArmoredOutputStream(encryptedOutputStream);

        PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
            new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_256)
                .setWithIntegrityPacket(true)
                .setSecureRandom(new SecureRandom()).setProvider("BC"));

        encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator(passPhrase.toCharArray()).setProvider("BC"));

        OutputStream cOut = encGen.open(encryptedOutputStream, new byte[4096]);

        PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
        return lData.open(cOut, PGPLiteralData.BINARY, "", new Date(), new byte[4096]);
    }

Testing it with:

PGPEncrypter pgpEncrypter = new PGPEncrypter();
FileOutputStream fileOutputStream = new FileOutputStream("encrypted.txt");
OutputStream outputStream = pgpEncrypter.encrypt(fileOutputStream, "password");
outputStream.write("content".getBytes(StandardCharsets.UTF_8));
outputStream.close()
pgpEncrypter.cOut.close()
pgpEncrypter.armoredOutputStream.close()
pgpEncrypter.encryptedOutputStream.close()

And then I am able to decrypt using PGP and the command at the start of the post.

The next step will be to have the class extend OutputStream and automatically close the underlying streams in the correct order when close() is called.


Solution

  • You were right on the edge of it. If you look at the whole directory https://github.com/bcgit/bc-java/tree/master/pg/src/main/java/org/bouncycastle/openpgp/examples you see there are:

    • KeyBasedFileProcessor -- for the encrypt side, does:

      1. read the (whole) input file and create compressed-literal-data packet in memory

      2. create PGPEncryptedDataGenerator and give it a public-key encryption 'method' (for a selected key)

      3. open a stream on the generator and write the compressed-literal-data packet to it (which results in it being encrypted and written to the output file)

    • KeyBasedLargeFileProcessor -- ditto:

      1. don't read the whole input

      2. create the same generator as for non-Large

      3. open a stream on the generator

      4. use PGPCompressedDataGenerator to open a compressed-data stream on 3

      5. (in PGPUtil.writeFileToLiteralData) use PGPLiteralDataGenerator to open a literal-data stream on 4, and then copy from the input file to that stream, which results in it being put into PGP's literal-data format, compressed and encrypted and written to the output file)

    • PBEFileProcessor -- for encrypt is exactly the same as KeyBased except that it uses a method for PBE (with the given passphrase) instead of one for public-key encryption

    Now, what do you think would happen if you make the same changes to PBEFileProcessor as the KeyBasedLargeFileProcessor made to KeyBasedFileProcessor? Might you have a PBELargeFileProcessor that does password-based encryption of large files -- or with the obvious changes, anything you can access and read as an InputStream?

    Except that (all) the examples use CAST5; you want to change that to AES_256 (or whatever else).