Android Key Store解密密码失败问题求助
Android密码加解密解密失败问题排查
问题描述
正在学习Android加密技术,尝试实现用户密码的加解密功能。加密功能运行正常,但解密功能始终报错。曾尝试复用加密配置进行解密但无效,恳请帮忙排查问题。
CryptoManager类代码
package edu.xfoleks.komodoro.presentation.utils import android.security.keystore.KeyProperties import android.security.keystore.KeyProtection import android.util.Base64 import java.nio.charset.StandardCharsets import java.security.KeyStore import java.util.Calendar import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.spec.IvParameterSpec class CryptoManager { init { generateSecretKey() } private fun generateSecretKey() { val keyStore = KeyStore.getInstance(KEY_STORE).apply { load(null) } val keyGenerator = KeyGenerator.getInstance(ALGORITHM).apply { init(KEY_SIZE) } val secretKey = keyGenerator.generateKey() val startTime = Calendar.getInstance() val expireTime = Calendar.getInstance().apply { add(Calendar.YEAR, 2) } val entry = KeyStore.SecretKeyEntry(secretKey) val protectionParameter = KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setKeyValidityStart(startTime.time) .setKeyValidityEnd(expireTime.time) .setBlockModes(BLOCK_MODE) .setEncryptionPaddings(PADDING) .build() keyStore.setEntry(ALIAS, entry, protectionParameter) } fun encrypt(plainText: String): String { val keyStore = KeyStore.getInstance(KEY_STORE).apply { load(null) } val secretKey = keyStore.getKey(ALIAS, null) return try { val cipher = Cipher.getInstance(TRANSFORMATION) cipher.init(Cipher.ENCRYPT_MODE, secretKey) val cipherText = Base64.encodeToString(cipher.doFinal(plainText.toByteArray()), Base64.DEFAULT) val iv = Base64.encodeToString(cipher.iv, Base64.DEFAULT) "${cipherText}.$iv" } catch (ex: Exception) { ex.printStackTrace() "" } } fun decrypt(cipherText: String): String? { val keyStore = KeyStore.getInstance(KEY_STORE).apply { load(null) } val secretKey = keyStore.getKey(ALIAS, null) val array = cipherText.split(".") val cipherData = Base64.decode(array[0], Base64.DEFAULT) val iv = Base64.decode(array[1], Base64.DEFAULT) return try { val cipher = Cipher.getInstance(TRANSFORMATION) val spec = IvParameterSpec(iv) cipher.init(Cipher.DECRYPT_MODE, secretKey, spec) val clearText = cipher.doFinal(cipherData) val encoded = String(clearText, 0, clearText.size, StandardCharsets.UTF_8) encoded } catch (ex: Exception) { ex.printStackTrace() null } } companion object { private const val KEY_STORE = "AndroidKeyStore" private const val KEY_SIZE = 256 private const val ALIAS = "secret" private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7 private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING" } }
加解密相关ViewModel代码
LogInViewModel
class LogInViewModel @Inject constructor( private val userInteractor: UserInteractor ) : ViewModel() { private var _user = MutableLiveData<UserInfoItem>() private fun decryptPassword(password: String): String? { val cryptoManager = CryptoManager() return cryptoManager.decrypt(password) } fun logIn(userName: String, password: String): Boolean { val user = UserInfoItem(userName, password) _user.value = user val existingUser = userInteractor.getUser(user.toUser()).toUserInfoItem() val decryptedPassword = decryptPassword(existingUser.password) return user.userName.equals(existingUser.userName) && user.password.equals(decryptedPassword) } }
RegistrationViewModel
class RegistrationViewModel @Inject constructor( private val userInteractor: UserInteractor ) : ViewModel() { private var _user = MutableLiveData<UserInfoItem>() private fun encryptPassword(password: String): String { if (password.isEmpty()) { throw EmptyPasswordException() } val cryptoManager = CryptoManager() return cryptoManager.encrypt(password) } fun registerUser(userName: String, password: String) { if (userName.isEmpty() || password.isEmpty()) { throw UserNotCreatedException() } val encryptedPassword = encryptPassword(password) val user = UserInfoItem(userName, encryptedPassword) _user.value = user userInteractor.registerUser(user.toUser()) } }
错误日志(中文翻译)
javax.crypto.BadPaddingException at android.security.keystore2.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.java:609) at javax.crypto.Cipher.doFinal(Cipher.java:2056) at edu.xfoleks.komodoro.presentation.utils.CryptoManager.decrypt(CryptoManager.kt:83) at edu.xfoleks.komodoro.presentation.screens.viewmodels.LogInViewModel.decryptPassword(LogInViewModel.kt:22) at edu.xfoleks.komodoro.presentation.screens.viewmodels.LogInViewModel.logIn(LogInViewModel.kt:29) at edu.xfoleks.komodoro.presentation.screens.fragments.LogInFragment.onCreateView$lambda$5(LogInFragment.kt:47) at edu.xfoleks.komodoro.presentation.screens.fragments.LogInFragment.$r8$lambda$NaDtgxEMrBbrOBDtRje0oMjYmGs(Unknown Source:0) at edu.xfoleks.komodoro.presentation.screens.fragments.LogInFragment$$ExternalSyntheticLambda0.onClick(Unknown Source:2) at android.view.View.performClick(View.java:7506) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1211) at android.view.View.performClickInternal(View.java:7483) at android.view.View.-$$Nest$mperformClickInternal(Unknown Source:0) at android.view.View$PerformClick.run(View.java:29334) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Caused by: android.security.KeyStoreException: 参数无效(内部密钥库代码:-38 消息:在 KeystoreOperation::finish 中 原因: 0: 在 finish 中:KeyMint::finish 失败。 1: 错误::Km(ErrorCode(-38)))(公开错误代码:10 内部密钥库代码:-38) at android.security.KeyStore2.getKeyStoreException(KeyStore2.java:369) at android.security.KeyStoreOperation.handleExceptions(KeyStoreOperation.java:78) at android.security.KeyStoreOperation.finish(KeyStoreOperation.java:128) 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) ... 20 more
问题分析与解决方案
核心问题
每次实例化CryptoManager时,init方法都会调用generateSecretKey,而该方法会直接替换AndroidKeyStore中已有别名secret的密钥。也就是说:
- 注册时加密密码用的是A密钥
- 登录时解密密码用的是新生成的B密钥
密钥不匹配导致解密时出现BadPaddingException,同时触发KeyStore的参数无效错误。
解决方案
1. 修改密钥生成逻辑,仅在无密钥时生成
修改generateSecretKey方法,先检查KeyStore中是否存在目标别名的密钥,不存在才生成新密钥:
private fun generateSecretKey() { val keyStore = KeyStore.getInstance(KEY_STORE).apply { load(null) } // 检查是否已有密钥,存在则直接返回 if (keyStore.containsAlias(ALIAS)) { return } val keyGenerator = KeyGenerator.getInstance(ALGORITHM).apply { init(KEY_SIZE) } val secretKey = keyGenerator.generateKey() val startTime = Calendar.getInstance() val expireTime = Calendar.getInstance().apply { add(Calendar.YEAR, 2) } val entry = KeyStore.SecretKeyEntry(secretKey) val protectionParameter = KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setKeyValidityStart(startTime.time) .setKeyValidityEnd(expireTime.time) .setBlockModes(BLOCK_MODE) .setEncryptionPaddings(PADDING) .build() keyStore.setEntry(ALIAS, entry, protectionParameter) }
2. 将CryptoManager设计为单例
避免重复实例化导致的重复密钥生成问题,可以将CryptoManager改为单例模式:
class CryptoManager private constructor() { init { generateSecretKey() } companion object { val instance: CryptoManager by lazy { CryptoManager() } // 原companion中的常量保持不变 private const val KEY_STORE = "AndroidKeyStore" private const val KEY_SIZE = 256 private const val ALIAS = "secret" private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7 private const val TRANSFORMATION = "$ALGORITHM/$BLOCK_MODE/$PADDING" } // 原generateSecretKey、encrypt、decrypt方法保持不变 }
然后在ViewModel中使用单例实例:
// RegistrationViewModel中修改 private fun encryptPassword(password: String): String { if (password.isEmpty()) { throw EmptyPasswordException() } return CryptoManager.instance.encrypt(password) } // LogInViewModel中修改 private fun decryptPassword(password: String): String? { return CryptoManager.instance.decrypt(password) }
3. 可选:通过依赖注入提供单例
如果项目使用Dagger/Hilt等依赖注入框架,可以将CryptoManager标记为单例并注入到ViewModel中,避免手动管理单例。
验证方案
修改完成后,重新注册用户,然后登录,此时加密和解密使用的是同一密钥,即可正常解密密码。
内容的提问来源于stack exchange,提问作者Oleksandr Filonenko
相关产品推荐
相关产品推荐

