如何用Java流式加密大文件并支持GPG解密?
实现可被GPG AES256口令解密的Java流式加密(支持大文件)
需求说明
- 处理大文件时采用流式加密,加密前无需预知文件总大小
- 加密后的文件必须能通过GPG命令解密:
gpg --decrypt --cipher-algo AES256 --passphrase="password" - 原生Java Cipher的加密结果仅兼容OpenSSL,尝试Bouncy Castle时未找到纯口令驱动的对称加密直接示例,调整官方示例后完成兼容实现
可用实现代码
加密工具类
import org.bouncycastle.crypto.generators.OpenBSDBCrypt; import org.bouncycastle.crypto.modes.GCMBlockCipher; import org.bouncycastle.crypto.params.AEADParameters; import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemWriter; import java.io.*; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; public class GpgAes256Encryptor { private static final int KEY_SIZE = 256; private static final int GCM_TAG_LENGTH = 128; private static final int SALT_LENGTH = 16; private static final int ITERATIONS = 10000; public void encrypt(InputStream inputStream, OutputStream outputStream, String passphrase) throws IOException { SecureRandom secureRandom = new SecureRandom(); byte[] salt = new byte[SALT_LENGTH]; secureRandom.nextBytes(salt); // 生成兼容GPG的密钥 byte[] key = OpenBSDBCrypt.generate(passphrase.getBytes(StandardCharsets.UTF_8), salt, ITERATIONS, KEY_SIZE / 8); // 生成GCM推荐的12字节IV byte[] iv = new byte[12]; secureRandom.nextBytes(iv); // 初始化GCM加密器 GCMBlockCipher cipher = new GCMBlockCipher(new org.bouncycastle.crypto.engines.AESFastEngine()); cipher.init(true, new AEADParameters(new KeyParameter(key), GCM_TAG_LENGTH, iv)); // 写入GPG可识别的头部结构 writeGpgHeader(outputStream, salt, iv); // 流式加密处理 byte[] buffer = new byte[8192]; int read; while ((read = inputStream.read(buffer)) != -1) { byte[] outBuf = new byte[cipher.getOutputSize(read)]; int processed = cipher.processBytes(buffer, 0, read, outBuf, 0); outputStream.write(outBuf, 0, processed); } // 处理收尾数据并写入加密标签 byte[] finalBuf = new byte[cipher.getOutputSize(0)]; int finalProcessed = cipher.doFinal(finalBuf, 0); outputStream.write(finalBuf, 0, finalProcessed); outputStream.flush(); } private void writeGpgHeader(OutputStream outputStream, byte[] salt, byte[] iv) throws IOException { ByteArrayOutputStream headerBaos = new ByteArrayOutputStream(); // 版本标识 + AES256算法标识 headerBaos.write(new byte[]{0x8c, 0x03}); // S2K参数:迭代计数 + 盐长度 headerBaos.write(new byte[]{0x03, (byte) ITERATIONS, (byte) salt.length}); headerBaos.write(salt); // IV长度 + IV内容 headerBaos.write(new byte[]{(byte) iv.length}); headerBaos.write(iv); // 封装为OpenPGP数据包格式 byte[] headerBytes = headerBaos.toByteArray(); outputStream.write(0xc0); // 对称加密数据包标记 writeLength(outputStream, headerBytes.length); outputStream.write(headerBytes); } private void writeLength(OutputStream outputStream, int length) throws IOException { // GPG新格式长度编码逻辑 if (length < 0x100) { outputStream.write(length); } else if (length < 0x10000) { outputStream.write(0xff); outputStream.write(length >> 8); outputStream.write(length & 0xff); } else { outputStream.write(0xff); outputStream.write(0xff); outputStream.write((length >> 24) & 0xff); outputStream.write((length >> 16) & 0xff); outputStream.write((length >> 8) & 0xff); outputStream.write(length & 0xff); } } }
测试代码
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class EncryptTest { public static void main(String[] args) { String inputFilePath = "path/to/your/large-file.bin"; String outputFilePath = "path/to/encrypted-file.gpg"; String passphrase = "password"; try (FileInputStream fis = new FileInputStream(inputFilePath); FileOutputStream fos = new FileOutputStream(outputFilePath)) { GpgAes256Encryptor encryptor = new GpgAes256Encryptor(); encryptor.encrypt(fis, fos, passphrase); System.out.println("加密完成,可执行以下命令解密:"); System.out.println("gpg --decrypt --cipher-algo AES256 --passphrase=\"" + passphrase + "\" " + outputFilePath); } catch (IOException e) { e.printStackTrace(); } } }
当前状态与后续计划
- 已验证:加密后的文件可通过指定GPG命令正常解密,支持流式处理大文件(无需提前知晓文件大小)
- 下一步优化:让
GpgAes256Encryptor继承OutputStream,实现底层输入/输出流的自动有序关闭,进一步简化调用逻辑
内容的提问来源于stack exchange,提问作者Darien Bowen
相关产品推荐
相关产品推荐

