{"record":{"id":"46c9d956add5747c","repo":"MuntashirAkon/AppManager","slug":"decrypted-pass-is-empty-for-alias-alias","errorCode":null,"errorMessage":"Decrypted pass is empty for alias ${alias}","messagePattern":"Decrypted pass is empty for alias (.+?)","errorType":"exception","errorClass":"KeyStoreException","httpStatus":null,"severity":"error","filePath":"app/src/main/java/io/github/muntashirakon/AppManager/crypto/ks/KeyStoreManager.java","lineNumber":503,"sourceCode":"\n    /**\n     * @return Password for the given alias. {@link Utils#clearChars(char[])} must be called when done.\n     * @deprecated Kept for migratory purposes only, deprecated since v2.6.3. To be removed in v3.0.0.\n     */\n    @Deprecated\n    @CheckResult\n    @NonNull\n    private char[] getAliasPassword(@NonNull String alias) throws KeyStoreException {\n        char[] password;\n        String prefAlias = getPrefAlias(alias);\n        if (sSharedPreferences.contains(prefAlias)) {\n            String encryptedPass = sSharedPreferences.getString(prefAlias, null);\n            if (encryptedPass == null) {\n                throw new KeyStoreException(\"Stored pass is empty for alias \" + alias);\n            }\n            password = getDecryptedPassword(mContext, encryptedPass);\n            if (password == null) {\n                throw new KeyStoreException(\"Decrypted pass is empty for alias \" + alias);\n            }\n            return password;\n        } else {\n            IntentFilter filter = new IntentFilter(ACTION_KS_INTERACTION_BEGIN);\n            filter.addAction(ACTION_KS_INTERACTION_END);\n            ContextCompat.registerReceiver(mContext, mReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED);\n            Intent broadcastIntent = new Intent(ACTION_KS_INTERACTION_BEGIN);\n            broadcastIntent.setPackage(mContext.getPackageName());\n            mContext.sendBroadcast(broadcastIntent);\n            // Intent wrapper\n            Intent intent = new Intent(mContext, KeyStoreActivity.class);\n            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);\n            intent.putExtra(KeyStoreActivity.EXTRA_ALIAS, alias);\n            String ks = \"AM KeyStore\";\n            // We don't need a delete intent since the time will be expired anyway\n            NotificationCompat.Builder builder = NotificationUtils.getHighPriorityNotificationBuilder(mContext)\n                    .setAutoCancel(true)\n                    .setDefaults(Notification.DEFAULT_ALL)","sourceCodeStart":485,"sourceCodeEnd":521,"githubUrl":"https://github.com/MuntashirAkon/AppManager/blob/0152f468fc9463ee02dc2ca83f6fe4989a2c4ca5/app/src/main/java/io/github/muntashirakon/AppManager/crypto/ks/KeyStoreManager.java#L485-L521","documentation":"getAliasPassword successfully read the stored encrypted password string for the alias, but getDecryptedPassword returned null, so it throws KeyStoreException \"Decrypted pass is empty for alias ...\". The alias's password blob exists but cannot be decrypted with the current crypto setup.","triggerScenarios":"Calling getAliasPassword(alias) (via getKey) where the pref exists and is a valid String, but getDecryptedPassword(mContext, encryptedPass) yields null — Android Keystore key invalidated, wrong user authentication state, or corrupted ciphertext.","commonSituations":"Biometric/lock-screen change invalidated the decryption key; app restored from another device so ciphertext and key don't match; encrypted blob truncated or corrupted.","solutions":["Re-encrypt and re-save the alias password (delete the pref entry and prompt the user to re-enter it).","Verify the Android Keystore key used for decryption still exists and is usable; regenerate if invalidated.","If data was restored from a backup, re-import the keystore entry with the alias password instead of relying on the restored blob."],"exampleFix":"// before\npassword = getDecryptedPassword(mContext, encryptedPass);\nif (password == null) {\n    throw new KeyStoreException(\"Decrypted pass is empty for alias \" + alias);\n}\n// after\npassword = getDecryptedPassword(mContext, encryptedPass);\nif (password == null) {\n    sSharedPreferences.edit().remove(prefAlias).apply();\n    password = promptForAliasPassword(alias); // re-encrypt and store, then return\n    if (password == null) throw new KeyStoreException(\"Decrypted pass is empty for alias \" + alias);\n}","handlingStrategy":"try-catch","validationCode":"// ensure the decryption key is usable before attempting alias password reads\nKeyStore ks = KeyStore.getInstance(\"AndroidKeyStore\");\nks.load(null);\nif (!ks.containsAlias(cryptoAlias)) {\n    regenerateCryptoKey(); // decryption key was wiped/invalidated\n}","typeGuard":null,"tryCatchPattern":"// try\ntry {\n    char[] pass = keyStoreManager.getKey(alias);\n} catch (KeyStoreException e) {\n    if (e.getMessage().contains(\"Decrypted pass is empty\")) {\n        // blob present but undecryptable: re-key and re-store the password\n        reEncryptAliasPassword(alias);\n    }\n}","preventionTips":["Detect Android Keystore key invalidation after lock-screen/biometric changes and re-encrypt stored secrets.","Don't restore encrypted prefs from another device's backup.","Wrap getDecryptedPassword results with a non-null check and a recovery path in library usage.","Version the encrypted blob format so migrations can run before decryption."],"tags":["keystore","crypto","android","decryption-failed"],"backgroundTag":"decryption-failed","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"}