华为Mate9设备Android KeyStore实现空指针异常问题
解决华为Mate9 Android KeyStore生成密钥对时的NullPointerException问题
首先明确你的核心问题:仅华为Mate9设备在调用Android KeyStore生成密钥对时抛出NullPointerException,其他设备运行完全正常,报错栈指向系统KeyStore内部的空指针调用。
报错日志
Fatal Exception: java.lang.NullPointerException: Attempt to invoke interface method 'int android.security.IKeystoreService.del(java.lang.String, int)' on a null object reference at android.security.KeyStore.delete(KeyStore.java:186) at android.security.Credentials.deletePrivateKeyTypeForAlias(Credentials.java:292) at android.security.Credentials.deleteAllTypesForAlias(Credentials.java:251) at android.security.keystore.AndroidKeyStoreKeyPairGeneratorSpi.generateKeyPair(AndroidKeyStoreKeyPairGeneratorSpi.java:464) at java.security.KeyPairGenerator$Delegate.generateKeyPair(KeyPairGenerator.java:699) at com.example.utility.crypto.KeyStoreHelper.createKeys(Unknown Source) at com.example.utility.crypto.KeyStoreHelper.createKeys(Unknown Source) at com.example.activities.register.SplashActivity$1.run(Unknown Source) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:255) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:776)
对应的KeyStoreHelper代码片段
public class KeyStoreHelper { public static final String TAG = "KeyStoreHelper"; /** * Creates a public and private key and stores it using the Android Key * Store, so that only this application will be able to access the keys. */ public static void createKeys(Context context, String alias) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { if (!isSigningKey(alias)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { createKeysM(alias, false); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { createKeysJBMR2(context, alias); } } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) static void createKeysJBMR2(Context context, String alias) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { Calendar start = new GregorianCalendar(); Calendar end = new GregorianCalendar(); end.add(Calendar.YEAR, 30); KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) .setAlias(alias) .setSubject(new X500Principal("CN=" + alias)) .setSerialNumber(BigInteger.valueOf(Math.abs(alias.hashCode()))) .setStartDate(start.getTime()).setEndDate(end.getTime()) .build(); KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance( SecurityConstants.TYPE_RSA, SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); kpGenerator.initialize(spec); KeyPair kp = kpGenerator.generateKeyPair(); Log.d(TAG, "Public Key is: " + kp.getPublic().toString()); } @TargetApi(Build.VERSION_CODES.M) static void createKeysM(String alias, boolean requireAuth) { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER_ANDROID_KEYSTORE); keyPairGenerator.initialize( new KeyGenParameterSpec.Builder( alias,KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(1024, F4)) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512) .setUserAuthenticationRequired(requireAuth) .build()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Log.d(TAG, "Public Key is: " + keyPair.getPublic().toString()); } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) { throw new RuntimeException(e); } } /** * JBMR2+ If Key with the default alias exists, returns true, else false. * on pre-JBMR2 returns true always. */ public static boolean isSigningKey(String alias) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { try { KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE); keyStore.load(null); return keyStore.containsAlias(alias); } catch (Exception e) { Log.e(TAG, e.getMessage(), e); return false; } } else { return false; } } /** * Returns the private key signature on JBMR2+ or else null. */ public static String getSigningKey(String alias) throws CertificateEncodingException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { Certificate cert = getPrivateKeyEntry(alias).getCertificate(); if (cert == null) { return null; } return Base64.encodeToString(cert.getEncoded(), Base64.NO_WRAP); } else { return null; } } private static KeyStore.PrivateKeyEntry getPrivateKeyEntry(String alias) { try { KeyStore ks = KeyStore.getInstance(KEYSTORE_PROVIDER_ANDROID_KEYSTORE); // 原代码片段截断,此处省略剩余内容 } catch (Exception e) { Log.e(TAG, "Failed to get private key entry", e); return null; } } }
问题分析
这是华为Mate9设备的Android KeyStore实现专属bug:当尝试生成已存在别名的密钥对时,系统内部在删除旧密钥的流程中,出现了IKeystoreService对象为null的情况。虽然你的代码已经通过isSigningKey()检查了别名是否存在,但华为的KeyStore可能存在内部状态不一致或者竞态条件,导致检查通过后,生成密钥时仍触发了异常的删除逻辑,进而抛出空指针。
解决方案
我们可以通过定向异常捕获+手动清理密钥+重试机制来绕过这个bug,具体修改如下:
修改后的createKeys方法及新增辅助方法
public static void createKeys(Context context, String alias) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { int maxRetry = 3; int retryCount = 0; boolean operationSuccess = false; while (!operationSuccess && retryCount < maxRetry) { try { if (!isSigningKey(alias)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { createKeysM(alias, false); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { createKeysJBMR2(context, alias); } } operationSuccess = true; } catch (NullPointerException e) { // 仅针对华为Mate9设备处理该空指针异常 if ("huawei".equalsIgnoreCase(Build.MANUFACTURER) && "mate 9".equalsIgnoreCase(Build.MODEL)) { retryCount++; Log.w(TAG, "Huawei Mate9 KeyStore NPE encountered, retrying (" + retryCount + "/" + maxRetry + ")", e); // 手动删除对应别名的密钥,清理异常状态 deleteKeyEntry(alias); } else { // 其他设备抛出原异常,不做兼容 throw e; } } } if (!operationSuccess) { throw new RuntimeException("Failed to create keys after " + maxRetry + " retries on Huawei Mate9"); } } /** * 手动删除KeyStore中指定别名的密钥条目 */ private static void deleteKeyEntry(String alias) { try { KeyStore keyStore = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE); keyStore.load(null); if (keyStore.containsAlias(alias)) { keyStore.deleteEntry(alias); Log.d(TAG, "Successfully deleted existing key entry for alias: " + alias); } } catch (Exception e) { Log.e(TAG, "Failed to delete key entry for alias: " + alias, e); } }
方案说明
- 定向兼容:只针对华为Mate9设备捕获该NullPointerException,避免干扰其他设备的正常异常抛出逻辑。
- 手动清理:触发异常时手动删除对应别名的密钥条目,清理KeyStore的异常状态。
- 重试机制:最多重试3次,应对可能的临时状态不一致问题。
另外,也可以建议用户将华为Mate9的系统固件更新到最新版本,部分后期固件可能修复了这个KeyStore的底层bug,但代码层面的兼容是更可靠的解决方案。
内容的提问来源于stack exchange,提问作者Mansukh Ahir
相关产品推荐
相关产品推荐

