Android Keystore读取已保存值时返回null问题排查
问题分析与解决
核心需求:在Android Keystore中存储小型字符串ID,且应用重装后仍可读取,但当前getStringFromKeyChain方法始终返回null。
核心问题点
你的代码存在两个关键问题,直接导致读取失败:
- Android Keystore不支持导入自定义SecretKey:你用
SecretKeySpec创建的密钥是外部生成的,Android Keystore不允许将这种密钥存入并持久化——它只允许存储由自身生成的密钥。所以setKeyEntry调用实际上没有成功将密钥存入Keystore,自然读取时返回null。 - 重装后可读取的需求未满足:即使密钥存储成功,默认的Android Keystore密钥在应用卸载后会被删除。要实现重装后仍可读取,需要使用具有备份能力的密钥,即通过
KeyGenParameterSpec.Builder设置setBackupEnabled(true)(Android 6.0+)。
修正后的代码
下面是实现需求的正确代码,通过Keystore生成可备份的密钥,加密字符串ID后存储到SharedPreferences,读取时解密:
import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Base64; import android.app.Application; import java.security.KeyStore; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; public class KeyChainHelper { private static final String ALIAS = "persistent_id_alias"; private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; private static final String TRANSFORMATION = KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7; // 生成支持备份的AES密钥,仅在密钥不存在时调用 private static void generateBackupKey() throws Exception { KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); keyStore.load(null); if (!keyStore.containsAlias(ALIAS)) { KeyGenerator keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER); KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockMode(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setBackupEnabled(true) // 允许密钥备份,实现重装后恢复 .setUserAuthenticationRequired(false) // 无需用户验证即可使用 .build(); keyGenerator.init(spec); keyGenerator.generateKey(); } } // 存储字符串ID:用Keystore生成的密钥加密后存入SharedPreferences public static void saveStringToKeyChain(String data) { try { generateBackupKey(); // 确保密钥存在 KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); keyStore.load(null); SecretKey secretKey = (SecretKey) keyStore.getKey(ALIAS, null); // 加密数据,生成IV向量 Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedData = cipher.doFinal(data.getBytes()); byte[] iv = cipher.getIV(); // 将IV和加密数据一起存储 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( Application.getApplication()); prefs.edit() .putString("encrypted_id", Base64.encodeToString(encryptedData, Base64.DEFAULT)) .putString("iv", Base64.encodeToString(iv, Base64.DEFAULT)) .apply(); } catch (Exception e) { e.printStackTrace(); } } // 读取字符串ID:从SharedPreferences取出数据,用Keystore密钥解密 public static String getStringFromKeyChain() { try { KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); keyStore.load(null); SecretKey secretKey = (SecretKey) keyStore.getKey(ALIAS, null); if (secretKey == null) return null; // 取出加密数据和IV SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences( Application.getApplication()); String encryptedStr = prefs.getString("encrypted_id", null); String ivStr = prefs.getString("iv", null); if (encryptedStr == null || ivStr == null) return null; byte[] encryptedData = Base64.decode(encryptedStr, Base64.DEFAULT); byte[] iv = Base64.decode(ivStr, Base64.DEFAULT); // 解密数据 Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); byte[] decryptedData = cipher.doFinal(encryptedData); return new String(decryptedData); } catch (Exception e) { e.printStackTrace(); return null; } } }
关键说明
- Android Keystore的设计定位:它是用于存储密钥的安全容器,而非直接存储数据。所以我们用Keystore生成的密钥加密字符串ID,再将加密后的数据存储到SharedPreferences。
- 重装后可读取的原理:通过
setBackupEnabled(true),密钥会被备份到Google Play服务的备份存储(需用户开启设备备份),应用重装后密钥会被恢复,从而可以解密之前存储的ID。 - 权限要求:需在AndroidManifest.xml中声明
android.permission.BACKUP权限。
内容的提问来源于stack exchange,提问作者danu
相关产品推荐
相关产品推荐

