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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:02:03