BouncyCastle PGP加密/解密:引起:java.io.EOFException:PartialInputStream中流的过早结束

时间:2022-04-22 21:35:26

When attempting to encrypt then decrypt a file I run into the following exception:

尝试加密然后解密文件时,我遇到以下异常:

com.example.common.crypto.CipherException: org.bouncycastle.openpgp.PGPException: Exception starting decryption
    at com.example.common.crypto.PGPFileCipher.decrypt(PGPFileCipher.java:151)
    at com.example.common.crypto.PGPFileCipherTest.testEncryptDecryptFile(PGPFileCipherTest.java:41)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:43)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:62)
    at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:140)
    at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:127)
    at org.apache.maven.surefire.Surefire.run(Surefire.java:177)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:345)
    at org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:1009)
Caused by: org.bouncycastle.openpgp.PGPException: Exception starting decryption
    at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
    at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
    at com.example.common.crypto.PGPFileCipher.decrypt(PGPFileCipher.java:128)
    ... 28 more
Caused by: java.io.EOFException: premature end of stream in PartialInputStream
    at org.bouncycastle.bcpg.BCPGInputStream$PartialInputStream.read(Unknown Source)
    at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
    at java.io.InputStream.read(InputStream.java:82)
    at javax.crypto.CipherInputStream.a(DashoA13*..)
    at javax.crypto.CipherInputStream.read(DashoA13*..)
    at org.bouncycastle.bcpg.BCPGInputStream.read(Unknown Source)
    ... 31 more

My PGPFileCipher class is:

我的PGPFileCipher类是:

package com.example.common.crypto;

public class PGPFileCipher implements FileCipher {

    private static final Logger logger = LoggerFactory.getLogger(PGPFileCipher.class);

    public File encryptFile(File inputFile, String encryptedFilePath, String publicKeyPath) throws CipherException {
        FileInputStream publicKeyStream = null;
        FileOutputStream encryptedFileOutputStream = null;
        try {
            publicKeyStream = new FileInputStream(new File(publicKeyPath));
            File encryptedFile = new File(encryptedFilePath);
            encryptedFileOutputStream = new FileOutputStream(encryptedFile);
            encryptedFileOutputStream.write(encrypt(getBytesFromFile(inputFile), publicKeyStream, ""));
            encryptedFileOutputStream.flush();
            return encryptedFile;
        } catch (Exception e) {
            throw new CipherException(e);
        } finally {
            IOUtils.closeQuietly(encryptedFileOutputStream);
            IOUtils.closeQuietly(publicKeyStream);
        }
    }

    private PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass) throws CipherException {
        try {
            PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
            if (pgpSecKey == null) {
                return null;
            }
            return pgpSecKey.extractPrivateKey(pass, new BouncyCastleProvider());
        } catch (Exception e) {
            throw new CipherException(e);
        }
    }

    /**
     * decrypt the passed in message stream
     *
     * @param encrypted
     *            The message to be decrypted.
     * @param keyIn
     *            InputStream of the key
     *
     * @return Clear text as a byte array. I18N considerations are not handled
     *         by this routine
     * @exception CipherException
     */
    public byte[] decrypt(byte[] encrypted, InputStream keyIn, char[] password) throws CipherException {
        ByteArrayOutputStream out = null;
        InputStream clear = null;
        InputStream in = null;
        InputStream unc = null;
        try {
            in = new ByteArrayInputStream(encrypted);

            in = PGPUtil.getDecoderStream(in);

            PGPObjectFactory pgpF = new PGPObjectFactory(in);
            PGPEncryptedDataList enc = null;
            Object o = pgpF.nextObject();

            //
            // the first object might be a PGP marker packet.
            //
            if (o instanceof PGPEncryptedDataList) {
                enc = (PGPEncryptedDataList) o;
            } else {
                enc = (PGPEncryptedDataList) pgpF.nextObject();
            }

            //
            // find the secret key
            //
            Iterator it = enc.getEncryptedDataObjects();
            PGPPrivateKey sKey = null;
            PGPPublicKeyEncryptedData pbe = null;
            PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
                    PGPUtil.getDecoderStream(keyIn));

            while (sKey == null && it.hasNext()) {
                pbe = (PGPPublicKeyEncryptedData) it.next();

                sKey = findSecretKey(pgpSec, pbe.getKeyID(), password);
            }

            if (sKey == null) {
                throw new IllegalArgumentException(
                        "secret key for message not found.");
            }

            clear = pbe.getDataStream(sKey, new BouncyCastleProvider());

            PGPObjectFactory pgpFact = new PGPObjectFactory(clear);

            PGPCompressedData cData = (PGPCompressedData) pgpFact.nextObject();

            pgpFact = new PGPObjectFactory(cData.getDataStream());

            PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();

            unc = ld.getInputStream();

            out = new ByteArrayOutputStream();
            int ch;

            while ((ch = unc.read()) >= 0) {
                out.write(ch);

            }
            out.flush();
            byte[] returnBytes = out.toByteArray();
            return returnBytes;
        } catch (Exception e) {
            e.printStackTrace();
            throw new CipherException(e);
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(in);
            IOUtils.closeQuietly(clear);
            IOUtils.closeQuietly(unc);
        }
    }

    /**
     * Simple PGP encryptor between byte[].
     *
     * @param clearData
     *            The test to be encrypted
     * @param keyInputStream
     *            Input stream of the key
     * @param fileName
     *            File name. This is used in the Literal Data Packet (tag 11)
     *            which is really inly important if the data is to be related to
     *            a file to be recovered later. Because this routine does not
     *            know the source of the information, the caller can set
     *            something here for file name use that will be carried. If this
     *            routine is being used to encrypt SOAP MIME bodies, for
     *            example, use the file name from the MIME type, if applicable.
     *            Or anything else appropriate.
     *
     * @return encrypted data.
     * @exception CipherException
     */
    public byte[] encrypt(byte[] clearData, InputStream keyInputStream, String fileName)
            throws CipherException {
        OutputStream out = null;
        OutputStream cOut = null;
        PGPCompressedDataGenerator comData = null;
        PGPLiteralDataGenerator lData = null;
        ByteArrayOutputStream encOut = null;
        ByteArrayOutputStream bOut = null;
        OutputStream cos = null;
        OutputStream pOut = null;
        try {
            PGPPublicKey encKey = readPublicKey(keyInputStream);
            if (fileName == null || fileName.trim().length() < 1) {
                fileName = PGPLiteralData.CONSOLE;
            }

            encOut = new ByteArrayOutputStream();

            out = encOut;

            bOut = new ByteArrayOutputStream();

            comData = new PGPCompressedDataGenerator(
                    PGPCompressedDataGenerator.ZIP);
            cos = comData.open(bOut); // open it with the final
            // destination
            lData = new PGPLiteralDataGenerator();

            // we want to generate compressed data. This might be a user option
            // later,
            // in which case we would pass in bOut.
            pOut = lData.open(cos, // the compressed output stream
                    PGPLiteralData.BINARY, fileName, // "filename" to store
                    clearData.length, // length of clear data
                    new Date() // current time
            );
            pOut.write(clearData);
            pOut.flush();
            PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(
                    PGPEncryptedData.CAST5, false, new SecureRandom(),
                    new BouncyCastleProvider());

            cPk.addMethod(encKey);

            byte[] bytes = bOut.toByteArray();

            cOut = cPk.open(out, bytes.length);

            cOut.write(bytes); // obtain the actual bytes from the compressed stream
            cOut.flush();
            encOut.flush();
            return encOut.toByteArray();
        } catch (Exception e) {
            throw new CipherException(e);
        } finally {
            IOUtils.closeQuietly(out);
            IOUtils.closeQuietly(cOut);
            IOUtils.closeQuietly(encOut);
            IOUtils.closeQuietly(bOut);
            IOUtils.closeQuietly(cos);
            IOUtils.closeQuietly(pOut);
            try {
                if (lData != null) {
                    lData.close();
                }
                if (comData != null) {
                    comData.close();
                }
            } catch (IOException ignored) {}
        }
    }

    private PGPPublicKey readPublicKey(InputStream in)
            throws CipherException {
        try {
            in = PGPUtil.getDecoderStream(in);

            PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in);

            //
            // we just loop through the collection till we find a key suitable for
            // encryption, in the real
            // world you would probably want to be a bit smarter about this.
            //

            //
            // iterate through the key rings.
            //
            Iterator rIt = pgpPub.getKeyRings();

            while (rIt.hasNext()) {
                PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
                Iterator kIt = kRing.getPublicKeys();

                while (kIt.hasNext()) {
                    PGPPublicKey k = (PGPPublicKey) kIt.next();

                    if (k.isEncryptionKey()) {
                        return k;
                    }
                }
            }
            throw new IllegalArgumentException("Can't find encryption key in key ring.");
        } catch (Exception e) {
            throw new CipherException(e);
        }
    }

    private byte[] getBytesFromFile(File file) throws CipherException {
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            // Get the size of the file
            long length = file.length();

            if (length > Integer.MAX_VALUE) {
                throw new CipherException("File is too large: " + file.getName());
            }

            return IOUtils.toByteArray(is);
        } catch (IOException e) {
            throw new CipherException(e);
        } finally {
            IOUtils.closeQuietly(is);
        }
    }
}

And the test I'm attempting to run is:

我试图运行的测试是:

package com.example.common.crypto;

public class PGPFileCipherTest {

    private static final Logger logger = LoggerFactory.getLogger(PGPFileCipherTest.class);

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    @Test
    public void testEncryptDecryptFile() throws IOException, CipherException {
        FileOutputStream decryptedFileOutputStream = null;
        InputStream privateKeyStream = null;
        try {
            PGPFileCipher fileCipher = new PGPFileCipher();
            Resource inputFile = new ClassPathResource("testInputFile.txt");
            Resource publicKey = new ClassPathResource("PUBLIC.asc");
            Resource privateKey = new ClassPathResource("PRIVATE.asc");
            // Encrypt file
            File encryptedFile = fileCipher.encryptFile(inputFile.getFile(), folder.newFile("testInputFile_enc.txt").getPath(), publicKey.getFile().getPath());
            // Now decrypt the file
            File decryptedFile = folder.newFile("testInputFile_dec.txt");
            decryptedFileOutputStream = new FileOutputStream(decryptedFile);
            privateKeyStream = new FileInputStream(privateKey.getFile());
            decryptedFileOutputStream.write(fileCipher.decrypt(FileUtils.readFileToByteArray(encryptedFile), privateKeyStream, "".toCharArray()));
            decryptedFileOutputStream.flush();
        } finally {
            IOUtils.closeQuietly(decryptedFileOutputStream);
            IOUtils.closeQuietly(privateKeyStream);
        }
    }
}

Unfortunately I'm not all that familiar with the BouncyCastle and it would seem as if a stream is not properly being closed / flushed but I can't seem to track it down. This is a modified version of one of the BouncyCastle examples FYI. Thanks in advance for your help.

不幸的是,我并不熟悉BouncyCastle,看起来好像没有正确关闭/刷新流,但我似乎无法追踪它。这是一个BouncyCastle示例FYI的修改版本。在此先感谢您的帮助。

2 个解决方案

#1


2  

check the jdk version.. we were facing issue running encryption and both in jdk1.5. but jdk 1.6 it work fine. and yes the bc pro jars "bcpg-jdk16" and" bcprov-ext-jdk16" verison should be 1.46

检查jdk版本..我们在jdk1.5中遇到运行加密的问题。但是jdk 1.6它工作得很好。是的bc pro jars“bcpg-jdk16”和“bcprov-ext-jdk16”verison应该是1.46

#2


0  

Be careful when using new BouncyCastleProvider() as it will result in the provider being registered in JceSecurity each time you instantiate it. This will cause a memory leak.

使用新的BouncyCastleProvider()时要小心,因为每次实例化时都会导致提供程序在JceSecurity中注册。这将导致内存泄漏。

Consider doing something like this instead

考虑做这样的事情

private Provider getProvider() {
    Provider provider = Security.getProvider("BC");
    if (provider==null){
        provider = new BouncyCastleProvider();
        Security.addProvider(provider);
    }
    return provider;
}

#1


2  

check the jdk version.. we were facing issue running encryption and both in jdk1.5. but jdk 1.6 it work fine. and yes the bc pro jars "bcpg-jdk16" and" bcprov-ext-jdk16" verison should be 1.46

检查jdk版本..我们在jdk1.5中遇到运行加密的问题。但是jdk 1.6它工作得很好。是的bc pro jars“bcpg-jdk16”和“bcprov-ext-jdk16”verison应该是1.46

#2


0  

Be careful when using new BouncyCastleProvider() as it will result in the provider being registered in JceSecurity each time you instantiate it. This will cause a memory leak.

使用新的BouncyCastleProvider()时要小心,因为每次实例化时都会导致提供程序在JceSecurity中注册。这将导致内存泄漏。

Consider doing something like this instead

考虑做这样的事情

private Provider getProvider() {
    Provider provider = Security.getProvider("BC");
    if (provider==null){
        provider = new BouncyCastleProvider();
        Security.addProvider(provider);
    }
    return provider;
}