{"record":{"id":"53f44abb776ac903","repo":"MuntashirAkon/AppManager","slug":"could-not-load-aes-local-protection-key-from-keystore","errorCode":null,"errorMessage":"Could not load AES local protection key from keystore","messagePattern":"Could not load AES local protection key from keystore","errorType":"exception","errorClass":"KeyStoreException","httpStatus":null,"severity":"critical","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/crypto/ks/CompatUtil.java","lineNumber":98,"sourceCode":"            throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,\n            NoSuchProviderException, InvalidAlgorithmParameterException, NoSuchPaddingException,\n            InvalidKeyException, IllegalBlockSizeException, UnrecoverableKeyException {\n        KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);\n        keyStore.load(null);\n\n        Log.i(TAG, \"Loading local protection key\");\n        SharedPreferences sharedPreferences = context.getSharedPreferences(\"keystore\", Context.MODE_PRIVATE);\n        // Get the version of Android when the key has been generated, default to the current version of the system.\n        // In the latter case, the key will be generated.\n        int androidVersionWhenTheKeyHasBeenGenerated = sharedPreferences.getInt(\n                SHARED_KEY_ANDROID_VERSION_WHEN_KEY_HAS_BEEN_GENERATED, Build.VERSION.SDK_INT);\n\n        // Check if there's a key in the Android keystore (M and later)\n        if (keyStore.containsAlias(AES_LOCAL_PROTECTION_KEY_ALIAS)) {\n            Log.i(TAG, \"AES local protection key found in keystore\");\n            SecretKey secretKey = (SecretKey) keyStore.getKey(AES_LOCAL_PROTECTION_KEY_ALIAS, null);\n            if (secretKey == null) {\n                throw new KeyStoreException(\"Could not load AES local protection key from keystore\");\n            }\n            return new SecretKeyAndVersion(secretKey, androidVersionWhenTheKeyHasBeenGenerated);\n        }\n\n        // Check if a key has been created on version < M (such as, in case of an OS upgrade)\n        SecretKey secretKey = readKeyApiL(sharedPreferences, keyStore);\n        if (secretKey != null) {\n            return new SecretKeyAndVersion(secretKey, androidVersionWhenTheKeyHasBeenGenerated);\n        }\n\n        // Otherwise generate key\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            Log.i(TAG, \"Generating AES key with keystore\");\n            KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES,\n                    ANDROID_KEY_STORE_PROVIDER);\n            generator.init(new KeyGenParameterSpec.Builder(AES_LOCAL_PROTECTION_KEY_ALIAS,\n                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)\n                    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/crypto/ks/CompatUtil.java#L80-L116","documentation":"getAesGcmLocalProtectionKey loads the app's AES-GCM local protection key from the Android keystore under a fixed alias. If the keystore reports the alias exists but KeyStore.getKey returns null, the key cannot be recovered and a KeyStoreException is thrown. This usually indicates keystore corruption or an entry the app cannot access.","triggerScenarios":"keyStore.containsAlias(AES_LOCAL_PROTECTION_KEY_ALIAS) is true but keyStore.getKey(alias, null) returns null — e.g. hardware-backed key destroyed by system update, keystore entry corrupted, or key generated with different user credentials.","commonSituations":"OS upgrade or device restore that invalidated hardware-backed keys; keystore database corruption; work-profile/unlock-credential changes invalidating keys.","solutions":["Delete the stale keystore alias (keyStore.deleteEntry) and regenerate the key via the createKey path, accepting that previously protected data cannot be decrypted","Clear the app's keystore-backed data / reinstall the app to reset keystore state","Check for device credential changes (settings reset) and inform the user data is unrecoverable"],"exampleFix":"// before\nSecretKey secretKey = (SecretKey) keyStore.getKey(AES_LOCAL_PROTECTION_KEY_ALIAS, null);\nif (secretKey == null) {\n    throw new KeyStoreException(\"Could not load AES local protection key from keystore\");\n}\n// after\nSecretKey secretKey = (SecretKey) keyStore.getKey(AES_LOCAL_PROTECTION_KEY_ALIAS, null);\nif (secretKey == null) {\n    Log.w(TAG, \"Stale keystore alias; regenerating key\");\n    keyStore.deleteEntry(AES_LOCAL_PROTECTION_KEY_ALIAS);\n    return generateAndStoreNewKey(); // falls through to creation path\n}","handlingStrategy":"try-catch","validationCode":"if (!keyStore.containsAlias(AES_LOCAL_PROTECTION_KEY_ALIAS)) {\n    // key missing entirely: go to creation path\n    return createNewKey();\n}","typeGuard":null,"tryCatchPattern":"try {\n    SecretKey key = CompatUtil.getAesGcmLocalProtectionKey();\n} catch (KeyStoreException e) {\n    // keystore entry unrecoverable; reset crypto state or inform user data is lost\n    resetCryptoState();\n}","preventionTips":["Never assume hardware-backed keys survive OS upgrades or credential resets","Design for key loss: keep an explicit re-key/reset path","Test on devices with keystore-destroying updates and backup/restore flows"],"tags":["android","keystore","crypto","aes"],"backgroundTag":"keystore-key-unavailable","analyzedSha":"0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5","analyzedAt":"2026-09-12T14:03:37.243Z","contentChangedAt":"2026-09-12T14:03:37.243Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}