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

Android KeyStore加解密时罕见KeyStoreException问题排查

Android KeyStore加解密小众设备崩溃问题

我有一个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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 06:45:36