【发布时间】:2019-12-14 08:57:02
【问题描述】:
我正在尝试在 Android 上使用 javax.crypto.Cipher 来使用 AES-GCM 加密数据流。据我了解,可以多次使用 Cipher.update 进行多部分加密操作,并使用 Cipher.doFinal 完成。但是,当使用 AES/GCM/NoPadding 转换时,Cipher.update 拒绝将数据输出到提供的缓冲区,并返回写入的 0 个字节。缓冲区在密码内部建立,直到我调用 .doFinal。这似乎也发生在 CCM 中(我假设其他身份验证模式),但适用于其他模式,如 CBC。
我认为 GCM 可以在加密时计算身份验证标签,所以我不确定为什么不允许我使用 Cipher 中的缓冲区。
我做了一个例子,只调用 .update: (kotlin)
val secretKey = KeyGenerator.getInstance("AES").run {
init(256)
generateKey()
}
val iv = ByteArray(12)
SecureRandom().nextBytes(iv)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
// Pretend this is some file I want to read and encrypt
val inputBuffer = Random.nextBytes(1024000)
val outputBuffer = ByteArray(cipher.getOutputSize(512))
val read = cipher.update(inputBuffer, 0, 512, outputBuffer, 0)
// ^ at this point, read = 0 and outputBuffer is [0, 0, 0, ...]
// Future calls to cipher.update and cipher.getOutputSize indicate that
// the internal buffer is growing. But I would like to consume it through
// outputBuffer
// ...
cipher.doFinal(outputBuffer, 0)
// Now outputBuffer is populated
我想做的是从磁盘流式传输一个大文件,对其进行加密并逐块通过网络发送,而无需将整个文件数据加载到内存中。我尝试使用 CipherInputStream,但遇到了同样的问题。
这可以通过 AES/GCM 实现吗?
【问题讨论】:
-
我无法复制。在调用 cipher.update() 后立即打印
read的值会打印 512。将块写入 ByteArrayOutputStream 并打印 baos.toByteArray() 的长度也显示每次迭代的大小都在增加。我不得不删除 IvParameterSpec,因为它会导致 java.security.InvalidAlgorithmParameterException。 -
有趣...也许我应该澄清这是在 Android 上,如果这可能会影响它的实现方式。将更新我的问题以反映这一点。
-
这是 GCM 模式的“功能”。解密时,明文被“禁运”,直到调用
doFinal并且可以验证标签。原因很简单:如果update()一生成明文就将其交还给您,而最后标签无法验证,则您收到的明文将无效。doFinal()将无法召回有缺陷的明文,您将无法根据该有缺陷的明文召回您刚刚发射的核导弹。 -
不幸的是,这是图书馆设计师的选择。请参阅 answer 下的 Maarten 评论。如果您愿意,您可以划分和链接您的数据。
-
我将继续在conscrypt issues 上打开一个问题。几乎可以肯定它会被忽略。同时,我怀疑其他提供商(例如 bouncycastle)会产生预期的结果。但是,默认 Conscrypt 提供程序的总吞吐量可能仍然更快,因为它使用本机代码。这是您必须做出的权衡。
标签: java android encryption kotlin aes-gcm