Java Cipher update()/doFinal()与流对齐块大小



假设我们有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,但下一次迭代我们从input得到count = -1表示没有剩余,所以我们跳过doFinal()部分。如何防止跳过doFinal()?或者我们可以直接调用长度为0的doFinal(buffer, 0,0) ?

如果输入缓冲区返回的数据小于缓冲区大小,则不一定到达流的末端,因此将其用作最后块的整个想法是错误的(它通常适用于文件,但可能不适用于其他流,特别是)。CipherInputStream.

你需要做的是写数据,直到read方法返回-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)的事实:

如果b的长度为0,则不读取任何字节并返回0;否则,将尝试读取至少一个字节。如果由于流位于文件末尾而没有可用的字节,则返回值-1;否则,至少读取一个字节并存储在b中。

此外,您可以简单地使用CipherOutputStream来包装给定的OutputStream。对于Java 9以后的版本,您还可以使用InputStream#transferTo(OutputStream)来使生活更简单,而不需要弄乱缓冲读/写。

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


毫无疑问,没有数据调用doFinal也可以工作。没有数据的doFinal只是转换内部缓冲区中剩余的任何字节,

最新更新