AndroidKeyStore密钥异常:获取密钥时提示Key material is null
AndroidKeyStore密钥检索返回null问题修复
问题描述
我编写了一个Encryption类,加密时生成密钥并存入AndroidKeyStore,但尝试检索密钥时返回null,抛出java.lang.IllegalArgumentException: Key material is null异常。加密与解密函数在两个不同类中调用,用户输入的PIN被加密后存储在SharedPreferences中,后续打开应用输入PIN时会解密存储的PIN进行比对。
原代码
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 import android.util.Log import java.security.KeyStore import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec class Encryption() { // Alias for the key in the Keystore private val keyAlias = "yourKeyAlias" // Generate a key in the Keystore private fun generateKey() { try { val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") val keyGenParameterSpec = KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setKeySize(256) // Adjust the key size based on your requirements .build() keyGenerator.init(keyGenParameterSpec) keyGenerator.generateKey() Log.d("Encryption", "Key generation successful") } catch (e: Exception) { Log.e("Encryption", "Key generation failed: ${e.message}") } } private fun getKey(): SecretKeySpec { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) if (!keyStore.containsAlias(keyAlias)) { // If the key entry doesn't exist, generate the key Log.d("Encryption", "Key dont exist ") } val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry val secretKey = secretKeyEntry.secretKey // Log key information for debugging Log.d("Encryption", "Key Algorithm: ${secretKey.algorithm}") Log.d("Encryption", "Key Format: ${secretKey.format}") Log.d("Encryption", "Encoded Key Material: ${secretKey.encoded?.contentToString()}") // Ensure that the key material is non-null before creating SecretKeySpec requireNotNull(secretKey.encoded) { "Key material is null" } // Convert the SecretKey to SecretKeySpec return SecretKeySpec(secretKey.encoded, "AES") } fun encrypt(strToEncrypt: String): Pair<ByteArray, ByteArray> { generateKey() val key = getKey() val plainText = strToEncrypt.toByteArray(Charsets.UTF_8) val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") cipher.init(Cipher.ENCRYPT_MODE, key) val iv = cipher.iv val encryptedData = cipher.doFinal(plainText) return iv to encryptedData } fun decrypt(dataToDecrypt: String, encryptedIv: String): String { val key = getKey() val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") val iv = Base64.decode(encryptedIv, Base64.DEFAULT) val ivParameterSpec = IvParameterSpec(iv) cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec) val decryptedData = cipher.doFinal(Base64.decode(dataToDecrypt, Base64.DEFAULT)) return String(decryptedData, Charsets.UTF_8) } }
报错日志
D Key generation successful D Key Algorithm: AES D Key Format: null D Encoded Key Material: null E FATAL EXCEPTION: main Process: com.example.identitypal, PID: 8886 java.lang.IllegalArgumentException: Key material is null at com.example.identitypal.sign_login.Encryption.getKey(Encryption.kt:59) at com.example.identitypal.sign_login.Encryption.encrypt(Encryption.kt:67) at com.example.identitypal.sign_login.AppPasswordSetup.savePassword(AppPasswordSetup.kt:71) at com.example.identitypal.sign_login.AppPasswordSetup.onCreate$lambda$0(AppPasswordSetup.kt:56) at com.example.identitypal.sign_login.AppPasswordSetup.$r8$lambda$u6wUwfaPzmksbpwedB19ZIAllp8(Unknown Source:0) at com.example.identitypal.sign_login.AppPasswordSetup$$ExternalSyntheticLambda0.onClick(Unknown Source:2) at android.view.View.performClick(View.java:7792) at android.widget.TextView.performClick(TextView.java:16112) at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1213) at android.view.View.performClickInternal(View.java:7769) at android.view.View.access$3800(View.java:910) at android.view.View$PerformClick.run(View.java:30218) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8751) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
问题原因
AndroidKeyStore生成的密钥不允许导出原始密钥材料,这是系统的安全设计——密钥由系统托管,开发者无法直接获取原始密钥字节,因此secretKey.encoded会返回null。你试图将SecretKey转换为SecretKeySpec的操作完全多余,直接使用AndroidKeyStore返回的SecretKey实例即可完成加解密。
修复方案
1. 调整getKey()方法
去掉密钥转换逻辑,直接返回AndroidKeyStore中的SecretKey,并仅在密钥不存在时触发生成:
private fun getKey(): SecretKey { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) if (!keyStore.containsAlias(keyAlias)) { Log.d("Encryption", "密钥不存在,开始生成") generateKey() } val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry return secretKeyEntry.secretKey }
2. 修正加解密方法的Cipher初始化
直接使用SecretKey初始化Cipher,同时确保填充方式与密钥生成时一致(将PKCS5PADDING改为PKCS7Padding)。
3. 优化密钥生成逻辑
删除encrypt()方法中重复调用的generateKey(),避免每次加密都覆盖原有密钥,导致之前加密的数据无法解密。
修复后完整代码
import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import android.util.Base64 import android.util.Log import java.security.KeyStore import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.SecretKey import javax.crypto.spec.IvParameterSpec class Encryption() { private val keyAlias = "yourKeyAlias" private fun generateKey() { try { val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") val keyGenParameterSpec = KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setKeySize(256) .build() keyGenerator.init(keyGenParameterSpec) keyGenerator.generateKey() Log.d("Encryption", "密钥生成成功") } catch (e: Exception) { Log.e("Encryption", "密钥生成失败: ${e.message}") } } private fun getKey(): SecretKey { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) if (!keyStore.containsAlias(keyAlias)) { Log.d("Encryption", "密钥不存在,开始生成") generateKey() } val secretKeyEntry = keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry return secretKeyEntry.secretKey } fun encrypt(strToEncrypt: String): Pair<ByteArray, ByteArray> { val key = getKey() val plainText = strToEncrypt.toByteArray(Charsets.UTF_8) val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") cipher.init(Cipher.ENCRYPT_MODE, key) val iv = cipher.iv val encryptedData = cipher.doFinal(plainText) return iv to encryptedData } fun decrypt(dataToDecrypt: String, encryptedIv: String): String { val key = getKey() val cipher = Cipher.getInstance("AES/CBC/PKCS7Padding") val iv = Base64.decode(encryptedIv, Base64.DEFAULT) val ivParameterSpec = IvParameterSpec(iv) cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec) val decryptedData = cipher.doFinal(Base64.decode(dataToDecrypt, Base64.DEFAULT)) return String(decryptedData, Charsets.UTF_8) } }
额外注意事项
- 加解密时的填充方式必须与密钥生成时设置的
KeyProperties.ENCRYPTION_PADDING_PKCS7完全匹配,否则会导致解密失败 - 不要重复生成密钥,否则会覆盖原有密钥,导致历史加密数据无法解密
- AndroidKeyStore的密钥与应用绑定,卸载应用后密钥会被自动删除,需提前规划数据恢复策略
内容的提问来源于stack exchange,提问作者Bertrandho
相关产品推荐
相关产品推荐

