You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 12:55:03