AndroidX数据存储-AES/CCB/PKCS7-javax.crypto.IllegalBlockSizeExce



我读了Mark Allison的博客文章,内容是将新的安卓数据存储与安卓密钥存储的加密相结合。

我在他的博客中使用了完全相同的SecretKey属性(AES/CCBC/PKCS7(和Encrypt/Decrypt。

class AesCipherProvider(
private val keyName: String,
private val keyStore: KeyStore,
private val keyStoreName: String
) : CipherProvider {
override val encryptCipher: Cipher
get() = Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.ENCRYPT_MODE, getOrCreateKey())
}
override fun decryptCipher(iv: ByteArray): Cipher =
Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.DECRYPT_MODE, getOrCreateKey(), IvParameterSpec(iv))
}
private fun getOrCreateKey(): SecretKey =
(keyStore.getEntry(keyName, null) as? KeyStore.SecretKeyEntry)?.secretKey
?: generateKey()
private fun generateKey(): SecretKey =
KeyGenerator.getInstance(ALGORITHM, keyStoreName)
.apply { init(keyGenParams) }
.generateKey()
private val keyGenParams =
KeyGenParameterSpec.Builder(
keyName,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setBlockModes(BLOCK_MODE)
setEncryptionPaddings(PADDING)
setUserAuthenticationRequired(false)
setRandomizedEncryptionRequired(true)
}.build()
private companion object {
const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING"
}
}
class CryptoImpl constructor(private val cipherProvider: CipherProvider) : Crypto {
override fun encrypt(rawBytes: ByteArray, outputStream: OutputStream) {
val cipher = cipherProvider.encryptCipher
val encryptedBytes = cipher.doFinal(rawBytes)
with(outputStream) {
write(cipher.iv.size)
write(cipher.iv)
write(encryptedBytes.size)
write(encryptedBytes)
}
}
override fun decrypt(inputStream: InputStream): ByteArray {
val ivSize = inputStream.read()
val iv = ByteArray(ivSize)
inputStream.read(iv)
val encryptedDataSize = inputStream.read()
val encryptedData = ByteArray(encryptedDataSize)
inputStream.read(encryptedData)
val cipher = cipherProvider.decryptCipher(iv)
return cipher.doFinal(encryptedData)
}
}

我使用的是下面这个超级简单的ProtocolBuffer,它只有一个String字段。

syntax = "proto3";
option java_package = "my.package.model";
message SimpleData {
string text = 1;
}

我使用以下代码来测试这个实现。

class SecureSimpleDataSerializer(private val crypto: Crypto) :
Serializer<SimpleData> {
override fun readFrom(input: InputStream): SimpleData {
return if (input.available() != 0) {
try {
SimpleData.ADAPTER.decode(crypto.decrypt(input))
} catch (exception: IOException) {
throw CorruptionException("Cannot read proto", exception)
}
} else {
SimpleData("")
}
}
override fun writeTo(t: SimpleData, output: OutputStream) {
crypto.encrypt(SimpleData.ADAPTER.encode(t), output)
}
override val defaultValue: SimpleData = SimpleData()
}
private val simpleDataStore = createDataStore(
fileName = "SimpleDataStoreTest.pb",
serializer = SecureSimpleDataSerializer(
CryptoImpl(
AesCipherProvider(
"SimpleDataKey",
KeyStore.getInstance("AndroidKeyStore").apply { load(null) },
"AndroidKeyStore"
)
)
)
)

当我尝试序列化和反序列化一个简单的String时,它的工作方式与预期的一样。

simpleDataStore.updateData { it.copy(text = "simple-string") }
println(simpleDataStore.data.first())
// "simple-string"

然而,当我尝试使用更长的String时(注意,小于Proto的最大大小(
保存有效,但在终止应用程序并重新启动应用程序以检索值时,它会崩溃。

simpleDataStore.updateData { it.copy(text = "eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQeyJhdWQiOiJ2cnRudS1zaXRlIiwic3ViIjoiNmRlNjg1MjctNGVjMi00MmUwLTg0YmEtNGU5ZjE3ZTQ4MmY2IiwiaXNzIjoiaHR0cHM6XC9cL2xvZ2luLnZydC5iZSIsInNjb3BlcyI6ImFkZHJlc3Msb3BlbmlkLHByb2ZpbGUsbGVnYWN5aWQsbWlkLGVtYWlsIiwiZXhwIjoxNjEwMjc4OTQ0LCJpYXQiOjE2MTAyNzUzNDQsImp0aSI6Ijc0MDk3MzFiLTg5OGUtNGVmNS1iNWMwLTEzODM2ZWZjN2ZjOCJ9kSkuI9Z0XLLBtfC0SpHA4wV0299ZOd6Xj99hNkemim7fRP1ooCD8YkqbM0hhBKiiYbvhqmfc1NSKYHAehA7Z9c6XluPTIpZkljHIBH7BLd0IGznraUEOMYDh0I2aQKZxxvwV6RlWetdCBUf3KtQuDO7snywbE5jmhzq75Y") }
println(simpleDataStore.data.first())
Process: com.stylingandroid.datastore, PID: 13706
javax.crypto.IllegalBlockSizeException
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:513)
at javax.crypto.Cipher.doFinal(Cipher.java:2055)
at com.stylingandroid.datastore.security.CryptoImpl.decrypt(Crypto.kt:33)
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:32)
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:26)
at androidx.datastore.core.SingleProcessDataStore.readData(SingleProcessDataStore.kt:249)
at androidx.datastore.core.SingleProcessDataStore.readDataOrHandleCorruption(SingleProcessDataStore.kt:227)
at androidx.datastore.core.SingleProcessDataStore.readAndInitOnce(SingleProcessDataStore.kt:190)
at androidx.datastore.core.SingleProcessDataStore$actor$1.invokeSuspend(SingleProcessDataStore.kt:154)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
Caused by: android.security.KeyStoreException: Invalid input length
at android.security.KeyStore.getKeyStoreException(KeyStore.java:1301)
at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:176)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:506)
at javax.crypto.Cipher.doFinal(Cipher.java:2055) 
at com.stylingandroid.datastore.security.CryptoImpl.decrypt(Crypto.kt:33) 
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:32) 
at com.stylingandroid.datastore.ui.MainActivity$SecureSimpleDataSerializer.readFrom(MainActivity.kt:26) 
at androidx.datastore.core.SingleProcessDataStore.readData(SingleProcessDataStore.kt:249) 
at androidx.datastore.core.SingleProcessDataStore.readDataOrHandleCorruption(SingleProcessDataStore.kt:227) 
at androidx.datastore.core.SingleProcessDataStore.readAndInitOnce(SingleProcessDataStore.kt:190) 
at androidx.datastore.core.SingleProcessDataStore$actor$1.invokeSuspend(SingleProcessDataStore.kt:154) 
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) 
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) 
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) 
2021-01-10 14:08:09.907 13706-13706/com.stylingandroid.datastore I/Process: Sending signal. PID: 13706 SIG: 9

有人知道吗
它是否特定于字符串的长度以及所选的加密算法
解密功能错误吗?

提前谢谢。

这个问题在我的机器上是可以重现的。当CCD_ 5中的加密数据CCD_。原因是从256字节开始的encryptedBytes.size不能存储在一个字节上,而方法int InputStream.read()void OutputStream.write(int)只能读取或写入一个字节。

因此,如果要写入密文的大小,则必须在CryptoImpl.encrypt中使用足够大的字节缓冲区,例如4字节:

with(outputStream) {
write(cipher.iv.size)
write(cipher.iv)
write(ByteBuffer.allocate(4).putInt(encryptedBytes.size).array())   // Convert Int to 4 bytes buffer
write(encryptedBytes)
}

以及用于在CryptoImpl.decrypt:中读取

val ivSize = inputStream.read()
val iv = ByteArray(ivSize)
inputStream.read(iv)
val encryptedDataSizeBytes = ByteArray(4)
inputStream.read(encryptedDataSizeBytes)
val encryptedDataSize = ByteBuffer.wrap(encryptedDataSizeBytes).int     // Convert 4 bytes buffer to Int
val encryptedData = ByteArray(encryptedDataSize)
inputStream.read(encryptedData)

但是,实际上不需要写入大小。IV的大小是已知的,它对应于块大小,即AES的16字节,从而定义了IV和密文的分离标准。因此,数据可以写入CryptoImpl.encrypt,如下所示:

with(outputStream) {
write(cipher.iv)                         // Write 16 bytes IV 
write(encryptedBytes)                    // Write ciphertext
}

用于在CryptoImpl.decrypt:中读取

val iv = ByteArray(16)
inputStream.read(iv)                         // Read IV (first 16 bytes) 
val encryptedData = inputStream.readBytes()  // Read ciphertext (remaining data)

相关内容

  • 没有找到相关文章

最新更新