You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用BouncyCastle解密文件出现额外字符:是否正常及如何去除

PGP加解密后文件末尾出现元数据的问题解决

现象是否正常?

这个现象是正常的。你在加密时使用PGPLiteralDataGenerator.open()方法传入了原文件对象,PGP规范中的Literal Data包会自动附加原文件名、文件创建时间等元数据。这些元数据会和文件内容一起被加密,解密时如果直接读取整个解密数据流,就会把这些元数据也写入输出文件,导致末尾出现额外字符(包含十六进制文件名)。

如何去除这些额外字符?

问题出在解密方法的实现上——你直接将解密后的完整数据流写入文件,没有正确解析PGP的Literal Data结构。需要修改decryptFile方法,解析解密后的PGP对象,只提取Literal Data中的实际文件内容部分。

修改后的完整PGPHelper类

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.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.security.Security;

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;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;

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());

            PGPPublicKey recipientPublicKey = selectRecipientPublicKey(publicKeyRingCollection);

            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 {
                    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) {
        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 JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(privateKeyPassword.toCharArray())
                );

                try (InputStream decryptedInputStream = encryptedData.getDataStream(
                        new JcePublicKeyDataDecryptorFactoryBuilder()
                                .setProvider("BC")
                                .build(privateKey))) {
                    // 解析解密后的PGP对象,提取Literal Data的内容流
                    JcaPGPObjectFactory plainFactory = new JcaPGPObjectFactory(decryptedInputStream);
                    Object nextObj;
                    while ((nextObj = plainFactory.nextObject()) != null) {
                        if (nextObj instanceof PGPCompressedData) {
                            PGPCompressedData compressedData = (PGPCompressedData) nextObj;
                            try (InputStream compressedStream = compressedData.getDataStream()) {
                                JcaPGPObjectFactory compressedFactory = new JcaPGPObjectFactory(compressedStream);
                                while ((nextObj = compressedFactory.nextObject()) != null) {
                                    if (nextObj instanceof PGPLiteralData) {
                                        PGPLiteralData literalData = (PGPLiteralData) nextObj;
                                        // 读取实际文件内容流,写入输出文件
                                        try (InputStream contentStream = literalData.getInputStream()) {
                                            byte[] buffer = new byte[4096];
                                            int bytesRead;
                                            while ((bytesRead = contentStream.read(buffer)) != -1) {
                                                outputFileOutputStream.write(buffer, 0, bytesRead);
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (nextObj instanceof PGPLiteralData) {
                            PGPLiteralData literalData = (PGPLiteralData) nextObj;
                            try (InputStream contentStream = literalData.getInputStream()) {
                                byte[] buffer = new byte[4096];
                                int bytesRead;
                                while ((bytesRead = contentStream.read(buffer)) != -1) {
                                    outputFileOutputStream.write(buffer, 0, bytesRead);
                                }
                            }
                        }
                    }
                } catch (IOException | PGPException e) {
                    e.printStackTrace();
                }
            }

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

关键修改说明

  1. 解析PGP结构:解密后不再直接读取整个数据流,而是用JcaPGPObjectFactory解析解密后的内容,区分压缩数据和Literal Data。
  2. 提取内容流:找到PGPLiteralData对象后,调用getInputStream()获取仅包含原文件内容的输入流,将其写入输出文件,跳过元数据部分。
  3. 兼容压缩场景:增加了对压缩数据的解析逻辑(即使当前加密用的是不压缩,也保证代码的通用性)。

调用代码(无修改)

加密调用:

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);

解密调用:

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);

内容的提问来源于stack exchange,提问作者Careau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:00:57