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()
}
}
Приведенный выше код работает безупречно на Android 16. Однако на Android 10 я получаю исключение, показанное в заголовке этого вопроса, при попытке вызвать cipher.init для шифра дешифрования.
Некоторые вещи, которые я заметил:
При вызове getInitializedCypherForEncryption , если я проверю cipher.iv сразу после cipher.init, я всегда получаю 12-байтовый массив;
Поскольку я использую BiometricPrompt и CryptoObject для дополнительной безопасности, если я снова проверю cipher.iv сразу после захвата проверенного зашифрованного объекта, IV изменится на 16-байтовый массив с другими значениями;
Теперь getInitializedCypherForDecryption выдает вышеупомянутое исключение прямо в cipher.init, поскольку IV изменился сам по себе;
Я попытался сохранить исходный 12-байтовый IV, чтобы повторно использовать его впоследствии, но полученные данные расшифровываются неправильно;
Я также попытался сохранить исходный зашифрованный объект вместо передачи проверенного через CryptoObject, но я, очевидно, получаю исключение android.security.KeyStoreException: ключевой пользователь не аутентифицирован;
Я также попытался сгенерировать свой собственный 12-байтовый IV и установил в getInitializedCypherForEncryption cipher.init. Та же проблема сохраняется, а также нарушает код для Android 16, поскольку установка IV вручную приводит к исключению.
Я пытаюсь реализовать шифрование и дешифрование данных с помощью Cipher. Это мой код: [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 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() } } [/code] Приведенный выше код работает безупречно на Android 16. Однако на Android 10 я получаю исключение, показанное в заголовке этого вопроса, при попытке вызвать cipher.init для шифра дешифрования. Некоторые вещи, которые я заметил: [list] [*]При вызове getInitializedCypherForEncryption , если я проверю cipher.iv сразу после cipher.init, я всегда получаю 12-байтовый массив;
[*]Поскольку я использую BiometricPrompt и CryptoObject для дополнительной безопасности, если я снова проверю cipher.iv сразу после захвата проверенного зашифрованного объекта, IV изменится на 16-байтовый массив с другими значениями;
[*]Теперь getInitializedCypherForDecryption выдает вышеупомянутое исключение прямо в cipher.init, поскольку IV изменился сам по себе;
[*]Я попытался сохранить исходный 12-байтовый IV, чтобы повторно использовать его впоследствии, но полученные данные расшифровываются неправильно;
[*]Я также попытался сохранить исходный зашифрованный объект вместо передачи проверенного через CryptoObject, но я, очевидно, получаю исключение android.security.KeyStoreException: ключевой пользователь не аутентифицирован;
[*]Я также попытался сгенерировать свой собственный 12-байтовый IV и установил в getInitializedCypherForEncryption cipher.init. Та же проблема сохраняется, а также нарушает код для Android 16, поскольку установка IV вручную приводит к исключению.