使用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(); } } }
关键修改说明
- 解析PGP结构:解密后不再直接读取整个数据流,而是用
JcaPGPObjectFactory解析解密后的内容,区分压缩数据和Literal Data。 - 提取内容流:找到
PGPLiteralData对象后,调用getInputStream()获取仅包含原文件内容的输入流,将其写入输出文件,跳过元数据部分。 - 兼容压缩场景:增加了对压缩数据的解析逻辑(即使当前加密用的是不压缩,也保证代码的通用性)。
调用代码(无修改)
加密调用:
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
相关产品推荐
相关产品推荐

