Java 流与块大小对齐的密码更新 ()/doFinal()

Java Cipher update()/doFinal() with Streams aligned to block size

假设我们有 InputStream 读取数据,OutputStream 写入数据和 Cipher。 我们以这种方式加密数据:

int bs = 4096;
Cipher cipher = ...;
InputStream input = ...;
OutputStream output = ...;
int count = 0;
byte[] buffer = new byte[bs];
while (true)
{
  count = input.read(buffer, 0, bs);

  if (count < bs)
    break;

  byte[] encrypted = cipher.update(buffer, 0, count);
  output.write(encrypted, 0, encrypted.length);
}

if (count > 0)
{
  byte[] final = cipher.doFinal(buffer, 0, count);
  output.write(final, 0, final.length);
}

但是如果数据恰好是 4096 字节或整数倍呢?这样我们将调用 update 但下一次迭代我们从输入中得到 count = -1 因为什么都没有,所以我们跳过 doFinal() 部分。如何防止跳过 doFinal()?或者我们可以只调用长度为 0 的 doFinal(buffer, 0, 0) 吗?

如果输入缓冲区 returns 的数据少于缓冲区大小,则输入缓冲区不一定到达流的末尾,因此将其用作最终块的整个想法是错误的(它通常适用于文件, 但它可能不适用于其他流,并且 尤其是 CipherInputStream.

你需要做的就是写数据直到read方法returns -1:

void copy(InputStream source, OutputStream target) throws IOException {
    byte[] buf = new byte[4096];
    int length;
    while ((length = source.read(buf)) != -1) {
        target.write(buf, 0, length);
    }
}

在这里你使用关于 InputStream#read(byte[] b) 的这个事实:

If the length of b is zero, then no bytes are read and 0 is returned; otherwise, there is an attempt to read at least one byte. If no byte is available because the stream is at the end of the file, the value -1 is returned; otherwise, at least one byte is read and stored into b.

此外,您可以简单地使用 CipherOutputStream 来包装给定的 OutputStream。从 Java 9 开始,您还可以使用 InputStream#transferTo(OutputStream) 让生活变得更简单,无需搞乱缓冲读/写。

最后,强烈推荐使用try-with-resources。不要忘记关闭这些流,尤其是 CipherOutputStream,否则密文的最后部分可能不会写入输出流。


毫无疑问,在没有数据的情况下调用 doFinal 也可以。 doFinal 没有数据只是转换密码实现的内部缓冲区中剩余的任何字节。