javaencryptionbouncycastlepgp

Extra characters when decrypt a file using bouncycastle


I need to create in java a class to encrypt and decrypt files. I Use bouncy castle library since it looks like a the most used for java.

I'm able to encrypt and decrypt the file. But the decrypted file have some extra characters at the end. Kind of metadata since the original filename is in it embeded in hexadecimal characters.

Is it normal ? How to skip that ?

Since I know nothing about it pgp and this kind of stuff, I asked chatpgt tu generate some code. Worked after a couple if iteration but I got these extra charaters ...

my code

package myPackage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
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;


public class PGPHelper {

    public static void encryptFile(String inputFilePath, String publicKeyFilePath, String outputFilePath, boolean armor, boolean withIntegrityCheck) {
        Security.addProvider(new BouncyCastleProvider());

        try (InputStream publicKeyInputStream = new BufferedInputStream(new FileInputStream(publicKeyFilePath));
             OutputStream encryptedFileOutputStream = new BufferedOutputStream(new FileOutputStream(outputFilePath))) {

            PGPPublicKeyRingCollection publicKeyRingCollection = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyInputStream),
                    new JcaKeyFingerprintCalculator());

            // Sélectionnez la clé publique du destinataire (vous pouvez ajuster cette logique selon vos besoins)
            PGPPublicKey recipientPublicKey = selectRecipientPublicKey(publicKeyRingCollection);

            // Initialiser le chiffrement PGP
            PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(
                    new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_256)
                            .setWithIntegrityPacket(withIntegrityCheck)
                            .setSecureRandom(new SecureRandom())
                            .setProvider("BC")
            );

            encryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(recipientPublicKey).setProvider("BC"));

            try (OutputStream encryptedOutputStream = encryptedDataGenerator.open(encryptedFileOutputStream, new byte[4096])) {
                PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.UNCOMPRESSED);
                OutputStream compressedOutputStream = compressedDataGenerator.open(encryptedOutputStream);

                PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
                OutputStream literalOutputStream = literalDataGenerator.open(compressedOutputStream, PGPLiteralData.BINARY, new File(inputFilePath));

                try (InputStream inputFileStream = new BufferedInputStream(new FileInputStream(inputFilePath))) {
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = inputFileStream.read(buffer)) != -1) {
                        literalOutputStream.write(buffer, 0, bytesRead);
                    }
                } finally {
                    // Fermez explicitement les ressources PGPLiteralDataGenerator et PGPCompressedDataGenerator ici
                    literalOutputStream.close();
                    literalDataGenerator.close();
                    compressedOutputStream.close();
                    compressedDataGenerator.close();
                }
            } catch (IOException | PGPException e) {
                e.printStackTrace();
            }


        } catch (IOException | PGPException e) {
            e.printStackTrace();
        }
    }
    
    
    private static PGPPublicKey selectRecipientPublicKey(PGPPublicKeyRingCollection publicKeyRingCollection) {
        // Dans un cas réel, vous devrez implémenter la logique pour sélectionner la clé publique du destinataire
        // Dans cet exemple, je prends simplement la première clé publique disponible
        return publicKeyRingCollection.getKeyRings().next().getPublicKeys().next();
    }
    
   
    
/**********************************
 * 
 */
    public static void decryptFile(String encryptedFilePath, String privateKeyFilePath, String privateKeyPassword, String outputFilePath) {
        Security.addProvider(new BouncyCastleProvider());

        try (InputStream encryptedFileInputStream = new BufferedInputStream(new FileInputStream(encryptedFilePath));
             InputStream privateKeyInputStream = new BufferedInputStream(new FileInputStream(privateKeyFilePath));
             OutputStream outputFileOutputStream = new BufferedOutputStream(new FileOutputStream(outputFilePath))) {

            PGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(encryptedFileInputStream));
            PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) pgpObjectFactory.nextObject();

            PGPPublicKeyEncryptedData encryptedData = (PGPPublicKeyEncryptedData) encryptedDataList.get(0);

            try (InputStream privateKeyStream = PGPUtil.getDecoderStream(privateKeyInputStream)) {
                PGPSecretKeyRingCollection secretKeys = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(privateKeyStream),
                        new JcaKeyFingerprintCalculator());

                PGPSecretKey secretKey = secretKeys.getSecretKey(encryptedData.getKeyID());
                
                PGPPrivateKey privateKey = secretKey.extractPrivateKey(
                        new org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(privateKeyPassword.toCharArray())
                );
                
                try (InputStream decryptedInputStream = encryptedData.getDataStream(
                        new JcePublicKeyDataDecryptorFactoryBuilder()
                        .setProvider("BC")
                        .build(privateKey));
                        
                        Reader reader = new InputStreamReader(decryptedInputStream, StandardCharsets.UTF_8);
                        Writer writer = new FileWriter(outputFilePath))
                {

                       char[] buffer = new char[4096];
                       int charsRead;
                       while ((charsRead = reader.read(buffer)) != -1) {
                           writer.write(buffer, 0, charsRead);
                       }
                   } catch (IOException | PGPException e) {
                       e.printStackTrace();
                   }
            }

        } catch (IOException | PGPException e) {
            e.printStackTrace();
        }
    }

 
}

how I call it

String inputFilePath = "C:/temp/source.txt";
String publicKeyFilePath = "C:/temp/public.key";
String outputFilePath = "C:/temp/source.pgp";
boolean armor = true;
boolean withIntegrityCheck = true;

PGPHelper.encryptFile(inputFilePath, publicKeyFilePath, outputFilePath, armor, withIntegrityCheck);

and

System.out.println("start decrypt");
String encryptedFilePath = "C:/temp/source.pgp";
String privateKeyFilePath = "C:/temp/secret.key";
String privateKeyPassword = "mypassword";
String outputFilePath = "C:/temp/dest2.txt";
  
PGPHelper.decryptFile(encryptedFilePath, privateKeyFilePath, privateKeyPassword, outputFilePath);

Solution

  • You have some steps missing in the decryption process.

    You missed:

    1. get the compressed data stream, it's not compressed, but it has some wrapper bytes stating that.
    2. get the literal data stream from it.

    Basically, the opposite of what you did in the compression part.

    Here you have a fixed version of your decryptFile that does that. Assuming the content is what is expected, you should add the instanceof checks of the casts, and react accordingly.

        public static void decryptFile(String encryptedFilePath, String privateKeyFilePath, String privateKeyPassword, String outputFilePath) {
            Security.addProvider(new BouncyCastleProvider());
    
            try (InputStream encryptedFileInputStream = new BufferedInputStream(new FileInputStream(encryptedFilePath));
                 InputStream privateKeyInputStream = new BufferedInputStream(new FileInputStream(privateKeyFilePath));
                 OutputStream outputFileOutputStream = new BufferedOutputStream(new FileOutputStream(outputFilePath))) {
    
                PGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(PGPUtil.getDecoderStream(encryptedFileInputStream));
                PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) pgpObjectFactory.nextObject();
    
                PGPPublicKeyEncryptedData encryptedData = (PGPPublicKeyEncryptedData) encryptedDataList.get(0);
    
                try (InputStream privateKeyStream = PGPUtil.getDecoderStream(privateKeyInputStream)) {
                    PGPSecretKeyRingCollection secretKeys = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(privateKeyStream),
                            new JcaKeyFingerprintCalculator());
    
                    PGPSecretKey secretKey = secretKeys.getSecretKey(encryptedData.getKeyID());
                    var a = 
                            new org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(privateKeyPassword.toCharArray());
                    System.out.println(a);
                    System.out.println(secretKey);
                    PGPPrivateKey privateKey = secretKey.extractPrivateKey(a);
                    JcaPGPObjectFactory pgpCompObjFac;
                    try (InputStream decryptedInputStream = encryptedData.getDataStream(
                            new JcePublicKeyDataDecryptorFactoryBuilder()
                                    .setProvider("BC")
                                    .build(privateKey))) {
                        PGPCompressedData pgpCompressedData = (PGPCompressedData) new JcaPGPObjectFactory(decryptedInputStream).nextObject();
                        InputStream compressedDataStream = new BufferedInputStream(pgpCompressedData.getDataStream());
                        pgpCompObjFac = new JcaPGPObjectFactory(compressedDataStream);
                    }
                    try (   InputStream is = ((PGPLiteralData)pgpCompObjFac.nextObject()).getInputStream();
                            Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8);
                            Writer writer = new FileWriter(outputFilePath))
                    {
    
                           char[] buffer = new char[4096];
                           int charsRead;
                           while ((charsRead = reader.read(buffer)) != -1) {
                               writer.write(buffer, 0, charsRead);
                           }
                       } catch (IOException e) {
                           e.printStackTrace();
                       }
                }
    
            } catch (IOException | PGPException e) {
                e.printStackTrace();
            }
        }