我正在使用增量加密,结合安卓KeyStore提供商。
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) }以下是我使用的常量:
const val TRANSFORMATION = "AES/GCM/NoPadding"
const val CHUNK_SIZE = 32768 // 32KiB现在,这段代码已经在30多个不同的设备上进行了大量测试,除了一部手机(Xperia和Android7.0)之外,没有任何问题。对于这款手机来说,如果输入(textToEncrypt)足够小,可以在一个块中加密所有内容,那么就可以了,但是如果它更大(通常在100 not左右),那么它就无法加密数据。我得到的是:
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()用ENCRYPT_MODE返回null,这就是为什么在我的代码中我允许返回null,然后放弃它们来形成加密的数据。这意味着cipher.doFinal应该一次返回整个加密数据。
编辑:所以很显然只有这款手机的块大小不是很好:它不能是32 8Kb,但是8KB工作正常。
发布于 2020-04-16 22:27:09
扩展了奥利维尔的答案。他确定异常是由mMainDataStreamer.update()抛出的。如果您查看AndroidKeyStoreCipherSpiBase类,您将看到mMainDataStreamer是KeyStoreCryptoOperationChunkedStreamer类的一个实例。这里有一个有趣的部分:
// 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;在我们的示例中,使用了这个默认的最大块大小。DEFAULT_MAX_CHUNK_SIZE为块大小设置了上限。如果将较大的块传递给cipher.update()方法,它们将被切割成块的DEFAULT_MAX_CHUNK_SIZE。正如您所看到的,即使是Android的开发人员也没有新的精确的安全块大小,不得不猜测自己(在您的例子中,失败)。
但是,请注意,粘结剂缓冲区用于将这些块传递给加密过程并从中返回结果。它的尺寸只有1MB左右。
也许这个设备上有一个非常小的粘合剂缓冲器?你可以试着用这个答案来调查它:https://stackoverflow.com/a/25666064/3249257
在未来的设备上,您可以使用:
IBinder.getSuggestedMaxIpcSizeBytes()https://developer.android.com/reference/android/os/IBinder#getSuggestedMaxIpcSizeBytes()
发布于 2020-04-11 16:16:48
我花了一些时间分析这个问题。我找不到真正的原因,但至少我找到了一些元素,所以它们就在这里。
多部份加工
首先,您说您使用了32 it的块大小,但这并不是真的。将字符串拆分为32 to块(32768 字符),然后将每个块转换为字节数组。由于一个字符的UTF-8表示可以从1到4个字节,所以字节数组通常大于32 of (除非您只有ASCII字符)。
您应该首先将字符串转换为字节数组,然后将其拆分为32 it块。只有这样才能保证传递给密码API的缓冲区的大小。
客户端错误
现在,关于你得到的堆叠痕迹。与乍看之下的情况相反,错误并不发生在doFinal()中,而是发生在update()中。当您调用update()时,调用将被委托给AndroidKeyStoreCipherSpiBase.engineUpdate()。有趣的部分是:
try {
flushAAD();
output = mMainDataStreamer.update(input, inputOffset, inputLen);
} catch (KeyStoreException e) {
mCachedException = e;
return null;
}它调用mMainDataStreamer.update(),这将失败,并使用代码KM_ERROR_MEMORY_ALLOCATION_FAILED抛出一个KeyStoreException。但是异常会被捕获,存储在mCachedException中,并返回null。这就是为什么当你打电话给null时,你会得到update()。
当您调用doFinal()时,它调用AndroidKeyStoreCipherSpiBase.engineDoFinal()
protected final byte[] engineDoFinal(byte[] input, int inputOffset, int inputLen)
throws IllegalBlockSizeException, BadPaddingException {
if (mCachedException != null) {
throw (IllegalBlockSizeException)
new IllegalBlockSizeException().initCause(mCachedException);
}该方法看到存在缓存的异常并抛出它(包装在IllegalBlockSizeException中,这与实际问题完全无关)。
密钥存储错误
现在真正的问题是。加密/解密的实际工作由Keystore服务执行,Keystore服务是用C++编写的一个单独的进程。AES的相关部分是在operation.cpp中。
该文件中返回了许多KM_ERROR_MEMORY_ALLOCATION_FAILED错误。顾名思义,代码意味着内存分配失败。因此,由于某种原因,Keystore似乎无法分配缓冲区。很难理解为什么。
结论
由于真正的原因是神秘的,我建议保持一个小的缓冲区大小,并改变分裂程序,如前面所述。
https://stackoverflow.com/questions/61098717
复制相似问题