CipherInputStream只读取了16个字节(AES/Java)

9
我正在使用CipherInputStream和CipherOutputStream来使用AES加密文件。
`encrypt(...)` 看起来工作正常,但是我的 `decrypt(...)` 函数只能解密文件的前16个字节。
这是我的类:
public class AESFiles {

    private byte[] getKeyBytes(final byte[] key) throws Exception {
        byte[] keyBytes = new byte[16];
        System.arraycopy(key, 0, keyBytes, 0, Math.min(key.length, keyBytes.length));
        return keyBytes;
    }

    public Cipher getCipherEncrypt(final byte[] key) throws Exception {
        byte[] keyBytes = getKeyBytes(key);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(keyBytes);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
        return cipher;
    }

    public Cipher getCipherDecrypt(byte[] key) throws Exception {
        byte[] keyBytes = getKeyBytes(key);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
        IvParameterSpec ivParameterSpec = new IvParameterSpec(keyBytes);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
        return cipher;
    }

    public void encrypt(File inputFile, File outputFile, byte[] key) throws Exception {
        Cipher cipher = getCipherEncrypt(key);
        FileOutputStream fos = null;
        CipherOutputStream cos = null;
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(inputFile);
            fos = new FileOutputStream(outputFile);
            cos = new CipherOutputStream(fos, cipher);
            byte[] data = new byte[1024];
            int read = fis.read(data);
            while (read != -1) {
                cos.write(data, 0, read);
                read = fis.read(data);
                System.out.println(new String(data, "UTF-8").trim());
            }
            cos.flush();
        } finally {
            fos.close();
            cos.close();
            fis.close();
        }
    }

    public void decrypt(File inputFile, File outputFile, byte[] key) throws Exception {
        Cipher cipher = getCipherDecrypt(key);
        FileOutputStream fos = null;
        CipherInputStream cis = null;
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(inputFile);
            cis = new CipherInputStream(fis, cipher);
            fos = new FileOutputStream(outputFile);
            byte[] data = new byte[1024];
            int read = cis.read(data);
            while (read != -1) {
                fos.write(data, 0, read);
                read = cis.read(data);
                System.out.println(new String(data, "UTF-8").trim());
            }
        } finally {
            fos.close();
            cis.close();
            fis.close();
        }
    }

    public static void main(String args[]) throws Exception {
        byte[] key = "mykey".getBytes("UTF-8");
        new AESFiles().encrypt(new File("C:\\Tests\\secure.txt"), new File("C:\\Tests\\secure.txt.aes"), key);
        new AESFiles().decrypt(new File("C:\\Tests\\secure.txt.aes"), new File("C:\\Tests\\secure.txt.1"), key);
    }
}

所以我的问题是,为什么decrypt函数只读取前16个字节?

你怎么知道它只解密了16个字节?你能编辑你的帖子展示你遇到的异常吗? - Gray
我没有收到任何异常。文件“secure.txt.1”(解密后的文件)的大小仅为16个字节,而源文件有41个字节。 - Joshua
请注意使用CipherInputStream非常棘手,因为它会将异常隐藏起来(这是一句谚语,意思是它掩盖了异常)。我不会自己使用它。 - Maarten Bodewes
1个回答

12

这是一个非常微妙的问题。你的问题在于你在你的cos之前关闭了你的fos。在你的encrypt(...)方法中,你正在执行以下操作:

} finally {
    fos.close();
    cos.close();
    fis.close();
}

这会在 CipherOutputStream 中关闭 FileOutputStream,导致最后一个填充的加密输出块没有写入输出文件。如果您在关闭 cos 后关闭 fos,则您的代码应该可以正常工作。

实际上,您应该考虑做以下操作:

    FileOutputStream fos = null;
    CipherOutputStream cos = null;
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(inputFile);
        fos = new FileOutputStream(outputFile);
        cos = new CipherOutputStream(fos, cipher);
        // once cos wraps the fos, you should set it to null
        fos = null;
        ...
    } finally {
        if (cos != null) {
           cos.close();
        }
        if (fos != null) {
           fos.close();
        }
        if (fis != null) {
           fis.close();
        }
    }

提醒一下:org.apache.commons.io.IOUtils有一个很棒的closeQuietly(...)方法,它会处理null检查并替你捕获异常。


2
现在可以通过使用try-with-resources来处理这个了。请注意,您可以在try语句中指定多个流,它们将自动关闭,而且顺序是正确的(反向顺序)。 - Maarten Bodewes
拯救了我的生命,但你能解释一下为什么会发生这种情况吗? - narancs
1
就像答案中@Karoly所说的那样,“因此,加密输出的最终填充块永远不会被写入输出文件”。在CipherOutputStream完成操作之前,您关闭了该文件。 - Gray
谢谢大家,我很困惑。由于某种原因,我犯了一些愚蠢的错误,加密文件和原始文件之间有1个字节的差异。我不知道为什么!我会在周一发布一个问题,并可能请求你们男孩的帮助。谢谢。 - narancs
@Gray:你能帮忙看一下这个吗?https://stackoverflow.com/questions/45420936/different-bytes-are-returned-when-decrypt-files-content - narancs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接