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

华为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);
    }
}

方案说明

  1. 定向兼容:只针对华为Mate9设备捕获该NullPointerException,避免干扰其他设备的正常异常抛出逻辑。
  2. 手动清理:触发异常时手动删除对应别名的密钥条目,清理KeyStore的异常状态。
  3. 重试机制:最多重试3次,应对可能的临时状态不一致问题。

另外,也可以建议用户将华为Mate9的系统固件更新到最新版本,部分后期固件可能修复了这个KeyStore的底层bug,但代码层面的兼容是更可靠的解决方案。

内容的提问来源于stack exchange,提问作者Mansukh Ahir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 10:04:05