ARTICLE AD BOX
I'm trying to implement data encryption and decryption using Cipher. This is my code:
class CryptographyManagerImpl : CryptographyManager { private val KEY_SIZE_BITS: Int = 256 private val GCM_TAG_BITS: Int = 128 private val ANDROID_KEYSTORE = "AndroidKeyStore" private val KEY_NAME = "MySecretKey" private val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM private val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE private val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES @Throws(BiometricEncryptionException::class) override fun getInitializedCypherForEncryption(): Cipher { try { val cipher = getCipher() val secretKey = getOrCreateSecretKey(KEY_NAME) cipher.init(Cipher.ENCRYPT_MODE, secretKey) return cipher } catch (e: Exception) { throw BiometricEncryptionException() } } @Throws(BiometricEncryptionException::class) @OptIn(ExperimentalEncodingApi::class) override fun getInitializedCypherForDecryption( encryptedData: String ): Cipher { try { val cipher = getCipher() val secretKey = getOrCreateSecretKey(KEY_NAME) val parts = encryptedData.split(":") val iv = Base64.decode(parts[1], android.util.Base64.DEFAULT) cipher.init( Cipher.DECRYPT_MODE, secretKey, GCMParameterSpec(GCM_TAG_BITS, iv) ) return cipher } catch (e: Exception) { throw BiometricEncryptionException() } } @Throws(BiometricEncryptionException::class) @OptIn(ExperimentalEncodingApi::class) override fun encryptData( data: String, cipher: Cipher ): String { try { val byteArray = data.toByteArray(StandardCharsets.UTF_8) val encryptedText = cipher.doFinal(byteArray) val base64Text = Base64.encode(encryptedText, android.util.Base64.DEFAULT) + ":" + Base64.encode(cipher.iv, android.util.Base64.DEFAULT) return base64Text } catch (e: Exception) { throw BiometricEncryptionException() } } @Throws(BiometricEncryptionException::class) @OptIn(ExperimentalEncodingApi::class) override fun decryptData( encryptedData: String, cipher: Cipher ): String { try { val parts = encryptedData.split(":") val encryptedText = Base64.decode(parts[0], android.util.Base64.DEFAULT) val byteArray = cipher.doFinal(encryptedText) val data = String(byteArray, StandardCharsets.UTF_8) return data } catch (e: Exception) { throw BiometricEncryptionException() } } private fun getCipher(): Cipher { val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING" return Cipher.getInstance(transformation) } private fun getOrCreateSecretKey(keyName: String): SecretKey { val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) } keyStore.getKey(keyName, null)?.let { return it as SecretKey } val paramsBuilder = KeyGenParameterSpec.Builder( keyName, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ).apply { setBlockModes(ENCRYPTION_BLOCK_MODE) setEncryptionPaddings(ENCRYPTION_PADDING) setKeySize(KEY_SIZE_BITS) setUserAuthenticationRequired(true) setInvalidatedByBiometricEnrollment(true) } val keyGenParams = paramsBuilder.build() val keyGenerator = KeyGenerator.getInstance( ENCRYPTION_ALGORITHM, ANDROID_KEYSTORE ) keyGenerator.init(keyGenParams) return keyGenerator.generateKey() } }The above code works flawlessly on Android 16. However, on Android 10 I get the exception shown in this question's title when trying to call cipher.init for the decryption cipher.
Some things that I've observed:
When calling getInitializedCypherForEncryption , if I check cipher.iv just after cipher.init, I always get a 12 byte array;
Since I'm using BiometricPrompt and CryptoObject for additional security, if I check again cipher.iv right after I capture the verified cipher object, the IV has changed into a 16 byte array with different values;
Now, getInitializedCypherForDecryption throws the aforementioned exception right on cipher.init since the IV has changed on its own;
I attempted to keep the original 12 byte IV to reuse it afterwards, but the resulting data is incorrectly decrypted;
I also attempted to keep the original cipher object, instead of passing along the verified one through CryptoObject, but I obviously get a android.security.KeyStoreException: Key user not authenticated;
I also attempted to generate my own 12 byte IV, and I set in getInitializedCypherForEncryption's cipher.init. The same issue persists, and also it breaks the code for Android 16, since manually setting an IV results in an exception.
Am I missing something? Thank you in advance.
