Android 11中android.security.KeyStore.getKeyStoreException解决方法
Android 11设备上RSA解密推送通知频繁崩溃问题
设备信息
- 设备:Galaxy A52 5G
- Android版本:11
更新后的堆栈跟踪
android.security.KeyStore.getKeyStoreException KeyStore.java:1441 android.security.KeyStore.getInvalidKeyException KeyStore.java:1548 android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit KeyStoreCryptoOperationUtils.java:54 android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit KeyStoreCryptoOperationUtils.java:89 android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized AndroidKeyStoreCipherSpiBase.java:265 android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit AndroidKeyStoreCipherSpiBase.java:109 javax.crypto.Cipher.tryTransformWithProvider Cipher.java:2984 javax.crypto.Cipher.tryCombinations Cipher.java:2891 javax.crypto.Cipher$SpiAndProviderUpdater.updateAndGetSpiAndProvider Cipher.java:2796 javax.crypto.Cipher.chooseProvider Cipher.java:773 javax.crypto.Cipher.init Cipher.java:1143 javax.crypto.Cipher.init Cipher.java:1084 au.com.gridstone.pscore.hkpf.data.providers.PushNotificationAlertEncryptionProvider.decrypt PushNotificationAlertEncryptionProvider.java:17 au.******package******.providers.PushNotificationAlertEncryptionProvider.decrypt PushNotificationAlertEncryptionProvider.java au.c******package******.background.messaging.FirebaseCloudMessagingService.processMessage FirebaseCloudMessagingService.java:153 au.******package******.background.messaging.FirebaseCloudMessagingService.access$processMessage FirebaseCloudMessagingService.java au.******package******.hkpf.background.messaging.FirebaseCloudMessagingService$onMessageReceived$1.invokeSuspend FirebaseCloudMessagingService.java:32 kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith BaseContinuationImpl.java:9 kotlinx.coroutines.DispatchedTask.run DispatchedTask.java:129 kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely CoroutineScheduler.java:1 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask CoroutineScheduler.java:14 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker CoroutineScheduler.java:28 kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run CoroutineScheduler.java
背景
项目采用RSA加密机制实现Firebase推送通知:服务端使用客户端上传的公钥加密通知数据,客户端接收后用本地私钥解密,再展示通知内容。
相关代码
密钥生成/获取函数
fun retrieveKey(keyAlias: String): KeyPair? { val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) val privateKey: PrivateKey? = keyStore.getKey(keyAlias, null) as? PrivateKey val publicKey: PublicKey? = keyStore.getCertificate(keyAlias)?.publicKey // 密钥存在则直接返回 if (privateKey != null && publicKey != null) { return KeyPair(publicKey, privateKey) } // 清除无效密钥条目 keyStore.deleteEntry(keyAlias) // 初始化AndroidKeyStore密钥生成器 val generator: KeyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore") val keySpec: KeyGenParameterSpec = KeyGenParameterSpec .Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_ECB) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .setKeySize(1024) .build() generator.initialize(keySpec) // 生成密钥对 val keyPair: KeyPair? = try { generator.generateKeyPair() } catch (exception: Throwable) { Crashes.trackError(exception) null } return keyPair }
设备注册(上传公钥到服务端)
fun register(pushToken: String, userCredentials: UserCredentials): Result<Unit> { val keyPair: KeyPair? = keyProvider.retrieveKey(PUSH_NOTIFICATIONS_KEY_ALIAS) val publicKeySpecFromEncoded: X509EncodedKeySpec? = keyPair?.public?.encoded?.let { X509EncodedKeySpec(it) } val pushKey: String? = publicKeySpecFromEncoded?.encoded?.let { String(Base64.encode(it, Base64.DEFAULT)) } val encryptionInstructions: EncryptionInstructions? = pushKey?.let { EncryptionInstructions(key = it) } val registration = DeviceRegistration( pushKey = pushKey.orEmpty(), pushToken = pushToken, encryptionInstructions = encryptionInstructions, deviceUuid = userCredentials.deviceUuid ) return authWebServices.registerDevice(registration) }
通知解密函数
fun decrypt(data: String, privateKey: Key?): PushNotificationData? { val cipher: Cipher = Cipher.getInstance(ENCRYPTION_CIPHER) cipher.init(Cipher.DECRYPT_MODE, privateKey) val encrypted: ByteArray = Base64.decode(data, Base64.DEFAULT) // 只处理RSA密钥 if (privateKey !is RSAKey) return null // 按块解密(适配RSA块大小限制) val blockSize: Int = DECRYPTION_BLOCK_SIZE val chunks: List<ByteArray> = encrypted.chunked(blockSize) // 解密处理 val decrypted: ByteArray = try { chunks.fold(ByteArray(encrypted.size)) { acc, byteArray -> acc + cipher.doFinal(byteArray) } } catch (t: Throwable) { Crashes.trackError(t) return null } // ...解析decrypted为PushNotificationData的逻辑 return pushNotificationData }
当前问题
已在解密逻辑中添加try-catch,但会导致通知静默失败;应用在Android 12及以上版本运行正常。
问题排查与修复方案
1. 补全Cipher初始化的异常捕获
堆栈显示崩溃发生在cipher.init阶段,但当前代码未对该步骤做异常处理,需将初始化逻辑移入try块:
fun decrypt(data: String, privateKey: Key?): PushNotificationData? { if (privateKey !is RSAKey) return null return try { val cipher: Cipher = Cipher.getInstance(ENCRYPTION_CIPHER) cipher.init(Cipher.DECRYPT_MODE, privateKey) val encrypted: ByteArray = Base64.decode(data, Base64.DEFAULT) val blockSize: Int = DECRYPTION_BLOCK_SIZE val chunks: List<ByteArray> = encrypted.chunked(blockSize) val decrypted: ByteArray = chunks.fold(ByteArray(0)) { acc, byteArray -> acc + cipher.doFinal(byteArray) } // 解析为PushNotificationData parsePushNotificationData(decrypted) } catch (t: Throwable) { Crashes.trackError(t) // 触发密钥重建+通知重试流程 rebuildKeyAndRetry() null } }
2. 修复密钥重建的原子性问题
原retrieveKey中删除旧密钥后,若新密钥生成失败会导致无可用密钥,调整逻辑为先生成新密钥再清理旧条目:
fun retrieveKey(keyAlias: String): KeyPair? { val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) val privateKey: PrivateKey? = keyStore.getKey(keyAlias, null) as? PrivateKey val publicKey: PublicKey? = keyStore.getCertificate(keyAlias)?.publicKey if (privateKey != null && publicKey != null) { return KeyPair(publicKey, privateKey) } // 先尝试生成新密钥 val generator: KeyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore") val keySpec: KeyGenParameterSpec = KeyGenParameterSpec .Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_ECB) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .setKeySize(2048) // 升级到2048位密钥,提升兼容性 .build() generator.initialize(keySpec) return try { val keyPair = generator.generateKeyPair() // 生成成功后清理旧条目 if (keyStore.containsAlias(keyAlias)) { keyStore.deleteEntry(keyAlias) } keyPair } catch (exception: Throwable) { Crashes.trackError(exception) // 生成失败,尝试恢复旧密钥 if (keyStore.containsAlias(keyAlias)) { val recoverPrivate = keyStore.getKey(keyAlias, null) as? PrivateKey val recoverPublic = keyStore.getCertificate(keyAlias)?.publicKey if (recoverPrivate != null && recoverPublic != null) { KeyPair(recoverPublic, recoverPrivate) } else null } else null } }
3. 增加密钥有效性前置检查
在解密前先验证密钥是否可用,避免无效密钥触发崩溃:
private fun isKeyValid(keyAlias: String): Boolean { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) return keyStore.containsAlias(keyAlias) && keyStore.getKey(keyAlias, null) != null }
内容的提问来源于stack exchange,提问作者Sultan
相关产品推荐
相关产品推荐

