Android KeyStore加解密时罕见KeyStoreException问题排查
我有一个EncryptionManager类,用于通过Android KeyStore对数据进行加解密。该实现对99%的用户都能正常工作,但部分用户偶尔会出现难以排查的崩溃,例如:
崩溃日志1:Key not found
Fatal Exception: java.security.InvalidKeyException: Keystore operation failed at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1693) at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1750) at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54) at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89) at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265) at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109) at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2984) at javax.crypto.Cipher.tryCombinations(Cipher.java:2891) at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2796) at javax.crypto.Cipher.chooseProvider(Cipher.java:773) at javax.crypto.Cipher.init(Cipher.java:1143) at javax.crypto.Cipher.init(Cipher.java:1084) at com.app.encryption.implementation.EncryptionManager.encryptData(EncryptionManager.kt:39) Caused by android.security.KeyStoreException: Key not found at android.security.KeyStore.getKeyStoreException(KeyStore.java:1555) at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1750) at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54) at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89) at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:265) at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:109) at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:2984) at javax.crypto.Cipher.tryCombinations(Cipher.java:2891) at javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider(Cipher.java:2796) at javax.crypto.Cipher.chooseProvider(Cipher.java:773) at javax.crypto.Cipher.init(Cipher.java:1143) at javax.crypto.Cipher.init(Cipher.java:1084) at com.app.encryption.implementation.EncryptionManager.encryptData(EncryptionManager.kt:39)
崩溃日志2:BadPaddingException
Fatal Exception: javax.crypto.BadPaddingException at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:609) at javax.crypto.Cipher.doFinal(Cipher.java:2056) at com.app.encryption.implementation.EncryptionManager.decryptData(EncryptionManager.kt:68) Caused by android.security.KeyStoreException: Invalid argument at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:356) at android.security.KeyStoreOperation.handleExceptions(KeyStoreOperation.java:78) at android.security.KeyStoreOperation.finish(KeyStoreOperation.java:127) at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer$MainDataStream.finish(KeyStoreCryptoOperationChunkedStreamer.java:228) at android.security.keystore2.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:181) at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:603) at javax.crypto.Cipher.doFinal(Cipher.java:2056) at com.app.encryption.implementation.EncryptionManager.decryptData(EncryptionManager.kt:68)
这类问题仅出现在摩托罗拉Edge Plus、Moto G Pure、诺基亚X100等小众设备上,Pixel等主流设备未发现此类崩溃。
我的EncryptionManager实现如下:
class EncryptionManager { private val cipher by lazy { Cipher.getInstance(TRANSFORMATION_PATTERN) } private val charset by lazy { charset(CHARSET_NAME) } private val keyStore by lazy { KeyStore.getInstance(KEY_STORE_TYPE).apply { load(null) } } private val keyGenerator by lazy { KeyGenerator.getInstance(KEY_ALGORITHM_AES, KEY_STORE_TYPE) } fun encryptData(keyAlias: String, data: String): String { return cipher.run { init(Cipher.ENCRYPT_MODE, generateSecretKey(keyAlias)) val ivString = Base64.encodeToString(iv, Base64.DEFAULT) var stringToEncrypt = ivString + IV_SEPARATOR val bytes = doFinal(data.toByteArray(charset)) stringToEncrypt += Base64.encodeToString(bytes, Base64.DEFAULT) stringToEncrypt } } fun decryptData(keyAlias: String, encryptedData: String): String { return cipher.run { val split = encryptedData.split(IV_SEPARATOR.toRegex()) if (split.size != 2) { throw IllegalArgumentException(IV_ERROR_MESSAGE) } val ivString = split[0] val encryptedString = split[1] val ivSpec = IvParameterSpec(Base64.decode(ivString, Base64.DEFAULT)) init( Cipher.DECRYPT_MODE, getSecretKey(keyAlias), ivSpec, ) val decryptedData = doFinal(Base64.decode(encryptedString, Base64.DEFAULT)) String(decryptedData) } } fun deleteAllData() { val aliases = keyStore.aliases().iterator() aliases.forEach { try { keyStore.deleteEntry(it) } catch (e: Exception) { Timber.e(e) } } } private fun generateSecretKey(keyAlias: String): SecretKey { return keyGenerator.apply { init( KeyGenParameterSpec .Builder(keyAlias, PURPOSE_ENCRYPT or PURPOSE_DECRYPT) .setBlockModes(BLOCK_MODE_CBC) .setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7) .build(), ) }.generateKey() } private fun getSecretKey(keyAlias: String): SecretKey { return (keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry).secretKey } companion object { private const val TRANSFORMATION_PATTERN = "AES/CBC/PKCS7Padding" private const val KEY_STORE_TYPE = "AndroidKeyStore" private const val CHARSET_NAME = "UTF-8" private const val IV_SEPARATOR = "]" private const val IV_ERROR_MESSAGE = "Passed data is incorrect. There was no IV specified with it." } }
我过去一周一直在查找KeyStore相关问题,但未找到能解释为何仅小部分用户出现崩溃的原因。我的EncryptionManager实现是否存在问题?还有什么我遗漏的点吗?
1. Cipher实例线程不安全
Cipher类本身不是线程安全的,你用lazy初始化了单例实例,当多线程并发调用encryptData或decryptData时,会导致Cipher内部状态混乱,触发各种加密异常。小众设备的KeyStore实现对并发问题的兼容性更差,因此更容易崩溃。
修复:用ThreadLocal维护线程专属的Cipher实例,避免并发冲突:
// 替换原有的lazy cipher private val cipherThreadLocal = ThreadLocal<Cipher>() { Cipher.getInstance(TRANSFORMATION_PATTERN) } // 在加密方法中使用 fun encryptData(keyAlias: String, data: String): String { val cipher = cipherThreadLocal.get() return cipher.run { init(Cipher.ENCRYPT_MODE, generateSecretKey(keyAlias)) // 后续逻辑不变 } } // 解密方法同理 fun decryptData(keyAlias: String, encryptedData: String): String { val cipher = cipherThreadLocal.get() return cipher.run { // 后续逻辑不变 } }
2. 重复生成同名密钥
generateSecretKey每次调用都会重新生成密钥,无论该alias的密钥是否已存在。这会导致旧密钥被覆盖,之前加密的数据无法解密,部分设备的KeyStore还会因此抛出异常。
修复:生成前先检查密钥是否存在,存在则直接返回:
private fun generateSecretKey(keyAlias: String): SecretKey { if (keyStore.containsAlias(keyAlias)) { return getSecretKey(keyAlias) } return keyGenerator.apply { init( KeyGenParameterSpec .Builder(keyAlias, PURPOSE_ENCRYPT or PURPOSE_DECRYPT) .setBlockModes(BLOCK_MODE_CBC) .setEncryptionPaddings(ENCRYPTION_PADDING_PKCS7) .build(), ) }.generateKey() }
3. 密钥获取时缺乏异常处理
getSecretKey直接强制类型转换,若密钥不存在或类型错误,会抛出未捕获的异常,进而触发KeyStore相关崩溃。
修复:添加存在性检查和类型校验:
private fun getSecretKey(keyAlias: String): SecretKey { val entry = keyStore.getEntry(keyAlias, null) ?: throw IllegalStateException("Key not found for alias: $keyAlias") if (entry !is KeyStore.SecretKeyEntry) { throw IllegalStateException("Entry is not a SecretKeyEntry for alias: $keyAlias") } return entry.secretKey }
4. Base64编码含换行符
使用Base64.DEFAULT会在编码结果中添加换行符,若数据存储或传输时丢失换行符,解密时会导致Base64解码失败,引发BadPaddingException。
修复:改用Base64.NO_WRAP避免换行:
// 加密时 val ivString = Base64.encodeToString(iv, Base64.NO_WRAP) stringToEncrypt += Base64.encodeToString(bytes, Base64.NO_WRAP) // 解密时 val ivSpec = IvParameterSpec(Base64.decode(ivString, Base64.NO_WRAP)) val decryptedData = doFinal(Base64.decode(encryptedString, Base64.NO_WRAP))
5. KeyStore加载无异常处理
keyStore初始化时的load(null)未捕获异常,若KeyStore损坏会导致整个类初始化失败。
修复:添加异常捕获:
private val keyStore by lazy { try { KeyStore.getInstance(KEY_STORE_TYPE).apply { load(null) } } catch (e: Exception) { Timber.e(e, "Failed to load AndroidKeyStore") throw RuntimeException("KeyStore initialization failed", e) } }
内容的提问来源于stack exchange,提问作者Tommy Jackson

