Java 特定Android设备/操作系统上的增量加密问题
我正在使用增量加密,并结合Android密钥库提供程序Java 特定Android设备/操作系统上的增量加密问题,java,android,kotlin,encryption,cryptography,Java,Android,Kotlin,Encryption,Cryptography,我正在使用增量加密,并结合Android密钥库提供程序 val cipher = Cipher.getInstance(TRANSFORMATION) cipher.init(Cipher.ENCRYPT_MODE, getSecretKey()) val chunks = textToEncrypt.chunked(CHUNK_SIZE) val encryptedChunks: MutableList<ByteArray?> = mutableListOf() chunks.
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
val chunks = textToEncrypt.chunked(CHUNK_SIZE)
val encryptedChunks: MutableList<ByteArray?> = mutableListOf()
chunks.forEachIndexed { index, chunk ->
if (index == chunks.size - 1) {
encryptedChunks.add(cipher.doFinal(chunk.toByteArray(StandardCharsets.UTF_8)))
} else {
encryptedChunks.add(cipher.update(chunk.toByteArray(StandardCharsets.UTF_8)))
}
}
val result = encryptedChunks.filterNotNull().reduce { acc, item -> acc.plus(item) }
现在,这段代码已经在30多种不同的设备上进行了大量测试,除了一部手机(Xperia XA with Android 7.0)之外,从未出现过任何问题。对于这款手机,如果输入(textToEncrypt
)足够小,可以在单个数据块中对所有内容进行加密,那么就可以了,但是如果输入的数据块更大(通常在100KiB左右),需要更多的数据块,那么就无法对数据进行加密。这就是我得到的:
Caused by javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:491)
at javax.crypto.Cipher.doFinal(Cipher.java:2056)
Caused by android.security.KeyStoreException: Memory allocation failed
at android.security.KeyStore.getKeyStoreException(KeyStore.java:685)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.update(KeyStoreCryptoOperationChunkedStreamer.java:132)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineUpdate(AndroidKeyStoreCipherSpiBase.java:338)
at javax.crypto.Cipher.update(Cipher.java:1683)
注意:仅此设备的cipher.update()
在加密模式下返回null,这就是为什么在我的代码中我允许返回null,然后丢弃它们以形成加密数据。
这意味着cipher.doFinal
应该一次性返回整个加密数据
编辑:显然,只有这款手机的数据块大小不合适:它不能是32Kb,但8Kb可以正常工作我花了一些时间分析这个问题。我找不到真正的原因,但至少我找到了一些元素,它们就在这里
多部件加工
首先,您说您使用了32Kb的块大小,但事实并非如此。将字符串拆分为32Kc的块(32768个字符),然后将每个块转换为字节数组。由于字符的UTF-8表示形式可以从1到4字节不等,因此字节数组通常大于32Kb(除非只有ASCII字符)
您应该首先将字符串转换为字节数组,然后将其拆分为32Kb的块。只有这样才能保证传递给crypto API的缓冲区的大小
客户端错误
现在,关于你得到的stacktrace。与乍一看的情况相反,错误不会出现在doFinal()
中,而是出现在update()
中。当您调用update()
时,调用将委派给。有趣的是:
try {
flushAAD();
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}
它调用mMainDataStreamer.update()
,该操作失败并抛出带有代码的KeyStoreException
。但是异常被捕获,存储在mCachedException
中,并返回null
。这就是为什么调用update()
时会得到null
调用doFinal()
时,它调用:
该方法发现有一个缓存的异常并抛出它(包装在一个IllegalBlockSizeException
中,它与实际问题完全无关)
密钥库错误
现在,真正的问题。加密/解密的实际工作是由密钥存储服务执行的,这是一个用C++编写的单独进程。AES的相关部分在中
该文件中返回了许多KM\u错误\u内存\u分配\u失败的错误。顾名思义,该代码意味着内存分配失败。因此,出于某种原因,密钥库似乎无法分配缓冲区。很难理解为什么
结论
由于真正的原因是神秘的,我建议保持一个小的缓冲区大小,并按照开头所述更改拆分过程。继续讨论Olivier的答案。他已确定异常是由mMainDataStreamer.update()
引发的。如果您查看类,您将看到mMainDataStreamer
是类的一个实例。这里有一个有趣的部分:
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
// Thus, it's safer to use a much smaller upper bound.
private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
在我们的例子中,使用默认的最大块大小<代码>默认\u最大\u块大小
对块大小设置上限。如果将较大的块传递给cipher.update()
方法,它们将被切割成DEFAULT\u MAX\u CHUNK\u SIZE
的块。正如你所看到的,即使是Android的开发者也没有新的精确的安全块大小,他们不得不自己猜测(在你的例子中,没有成功)
但是,请注意,缓冲区用于将这些块传递到加密过程并从中返回结果。它的大小只有1MB左右
也许在这个特殊的设备上有一个非常小的活页夹缓冲区?你可以试着用这个答案来研究它:
在未来的设备上,您可以使用:
IBinder.getSuggestedMaxIpcSizeBytes()
您是否尝试过增加块大小?嗨@Mike,不,我没有,但我想先尝试一种确定性方法。比如发现真正的原因,然后应用修复。因为是的,我可以尝试所有不同的块大小,但我永远无法保证它能在每个设备上工作。它说内存分配失败,所以自然要做的是减小块大小。试着把它减少到32。“真正的原因”可能只是一个有缺陷的设备,甚至是一个有缺陷的模型。是的,它可能是一个有缺陷的设备。当前块已经很小了:32Kb。我不确定买一个小一点的有什么意义。我只是不想扔掉我们到目前为止所做的所有测试,改变块大小。@JohannesB所以结果是肯定的,对于特定的手机,使用特定的版本(7.0),块太大了。真不敢相信!非常感谢您的调查!:)块的大小与输入的大小完全相同,即使存在转换,因为输入只包含ASCII。但即使不是,我的32Kb也是任意的,因为找不到update()
方法支持的最大值或至少是推荐的值。
// Binder buffer is about 1MB, but it's shared between all active transactions of the process.
// Thus, it's safer to use a much smaller upper bound.
private static final int DEFAULT_MAX_CHUNK_SIZE = 64 * 1024;
IBinder.getSuggestedMaxIpcSizeBytes()