在 Java 中,“默认”AES/GCM 提供程序 SunJCE 将 - 在解密过程中 - 在内部缓冲 1)用作输入的加密字节或 2)作为结果生成的解密字节。执行解密的应用程序代码会注意到Cipher.update(byte[])
返回一个空字节数组并Cipher.update(ByteBuffer, ByteBuffer)
返回写入长度 0。然后当过程完成时,Cipher.doFinal()
将返回所有解码的字节。
第一个问题是:上面的数字 1 或数字 2 正在缓冲哪些字节?
我假设缓冲只发生在解密期间而不是加密期间,因为首先,这种缓冲产生的问题(简短描述)不会发生在我的 Java 客户端对从磁盘读取的文件进行加密时,它总是发生在服务器端,接收这些文件并进行解密。其次,这里是这么说的。仅根据我自己的经验判断,我无法确定,因为我的客户使用CipherOutputStream
. 客户端没有显式使用 Cipher 实例上的方法。因此我无法推断是否使用了内部缓冲,因为我看不到 update- 和 final 方法返回什么。
当我从客户端传输到服务器的加密文件变大时,我的真正问题就出现了。大我的意思是超过 100 MB。
然后发生的是 Cipher.update() 抛出一个OutOfMemoryError
. 显然是由于内部缓冲区越来越大。
此外,尽管有内部缓冲并且没有从 Cipher.update() 接收到结果字节,但Cipher.getOutputSize(int) 会持续报告不断增长的目标缓冲区长度。因此,我的应用程序代码被迫分配一个不断增长ByteBuffer
的输入 Cipher.update(ByteBuffer, ByteBuffer)。如果我尝试作弊并传入容量较小的字节缓冲区,则更新方法会抛出#1。知道我创建了无用的巨大字节缓冲区是非常令人沮丧的。ShortBufferException
鉴于内部缓冲是万恶之源,那么我在这里应用的明显解决方案是将文件分成块,每个块 1 MB - 我发送小文件从来没有问题,只有大文件。但是,我很难理解为什么首先会发生内部缓冲。
先前链接的SO 答案说 GCM:s 身份验证标签“添加在密文的末尾”,但它“不必放在末尾”,这种做法“搞乱了 GCM 的在线性质”解密”。
为什么将标签放在最后只会弄乱服务器的解密工作?
这就是我的推理方式。为了计算身份验证标签或 MAC(如果您愿意),客户端使用某种散列函数。显然,MessageDigest.update()
不使用不断增长的内部缓冲区。
那么在接收端,服务器不能做同样的事情吗?首先,他可以解密字节,尽管是未经身份验证的字节,将它们输入哈希算法的更新函数,当标签到达时,完成摘要并验证客户端发送的 MAC。
我不是一个加密人,所以请跟我说话,就好像我既愚蠢又疯狂,但足够爱关心一些人=)我衷心感谢您花时间阅读这个问题,甚至可能会有所启发!
更新#1
我不使用 AD(关联数据)。
更新#2
编写的软件演示了使用 Java 的 AES/GCM 加密,以及 Java EE 中的安全远程协议(SRP) 和二进制文件传输。前端客户端是用 JavaFX 编写的,可用于动态更改加密配置或使用块发送文件。在文件传输结束时,会显示一些有关传输文件所用时间和服务器解密时间的统计信息。该存储库还有一个文档,其中包含我自己的一些 GCM 和 Java 相关研究。
享受:https ://github.com/MartinanderssonDotcom/secure-login-file-transfer/
#1
有趣的是,如果进行解密的服务器自己不处理密码,而是使用 a CipherInputStream
,则不会抛出 OutOfMemoryError 。相反,客户端设法通过线路传输所有字节,但在解密期间的某个地方,请求线程无限期挂起,我可以看到一个 Java 线程(可能是同一个线程)充分利用了 CPU 内核,同时保持文件打开磁盘不可访问并且报告的文件大小为 0。然后在很长一段时间后,Closeable
源被关闭,我的 catch 子句设法捕获由以下原因引起的 IOException:“javax.crypto.AEADBadTagException:输入太短 - 需要标签” .
使这种情况变得奇怪的是,传输较小的文件可以使用完全相同的代码完美地工作 - 因此显然可以正确验证标签。问题的根本原因必须与显式使用密码时相同,即不断增长的内部缓冲区。我无法在服务器上跟踪成功读取/解密了多少字节,因为一旦开始读取密码输入流,编译器重新排序或其他 JIT 优化就会使我所有的日志记录语句化为乌有。他们[显然]根本没有被执行。
请注意,这个 GitHub 项目及其相关的博客文章说 CipherInputStream 已损坏。但是当我使用 Java 8u25 和 SunJCE 提供程序时,这个项目提供的测试对我来说并没有失败。正如已经说过的,只要我使用小文件,一切都对我有用。