SecureStorage.Default.GetAsync(key) throws on Android for a value that saved without complaint — typically after a backup was restored to a new phone, credentials were cleared, or the app's data came back from a device restore. The encrypted entry is still on disk; the key that can decrypt it is gone.

MAUI's Android SecureStorage is EncryptedSharedPreferences with a master key held in the Android Keystore. Auto Backup copies the preferences file but never the key, so restored values are undecryptable. MAUI clears the cases it recognizes, and Microsoft's docs tell you to catch the rest: wrap every read in try/catch, treat a failed read as "nothing stored", and call RemoveAll() so the stale entries stop throwing.

The error

There is no single message — the exception type depends on which layer fails. From the build log (abridged):

SecureStorage.GetAsync THROWS after a keystore reset — wrapped as "nothing
remembered" so Home can't crash on a restored phone.

In MAUI's own Android implementation, reading a value catches GeneralSecurityException and SecurityException; opening the encrypted store catches KeyStoreException, BadPaddingException and a corrupt-keyset exception. Anything outside those lists reaches you.

Why it happens

The preferences file is encrypted with a two-scheme approach — keys deterministically, values with AES-256-GCM — under a master key that lives in the hardware-backed Keystore. Android's Auto Backup (API 23+) restores the file to a reinstalled or new device, but Keystore keys are never part of a backup. The restored bytes are real and unreadable. MAUI's implementation deletes an entry it cannot decrypt and recreates the store on a bad keyset, but the docs warn that a read of a still-cached corrupted value can still throw.

For ClinicLive that value is the confirmation code — the one thing that is a credential — so the Home screen reads it on every launch. An unhandled throw there is a crash on the first screen of a restored phone.

The fix

// src/ClinicLive.Pocket/Services/AppStorage.cs
public async ValueTask<string?> GetSecureAsync(string key)
{
    try
    {
        return await SecureStorage.Default.GetAsync(key);
    }
    catch (Exception)
    {
        // A reset keystore (device restore, cleared credentials) throws on read.
        // Treat it as "nothing remembered" rather than crashing the Home screen.
        return null;
    }
}

The docs go one step further, and it is a good step: clear everything in the catch with SecureStorage.Default.RemoveAll();, so the next read does not throw again for the same reason. The alternative is to prevent the situation — exclude the secure preferences file from Auto Backup with an android:fullBackupContent rules file — so a restore never carries undecryptable values at all.

Where it bit us

Season three, Part 9 (tag pocket-09 in the repo), where IAppStorage got two tiers: Preferences for harmless things, SecureStorage for the code. The wrap went in as a known hazard while the offline work was verified with airplane mode; a keystore reset was not staged on the emulator, so the exact exception was never photographed. The lesson holds regardless: a read from secure storage can fail for reasons that have nothing to do with your code, and "nothing remembered" is the only safe answer.

Frequently asked

Why does MAUI SecureStorage throw after restoring my Android app from a backup?
SecureStorage on Android stores values in EncryptedSharedPreferences under a master key kept in the Android Keystore. Auto Backup restores the preferences file but Keystore keys are never backed up, so the restored values cannot be decrypted and a read can throw.
Should I call SecureStorage.RemoveAll() when GetAsync throws?
Yes, that is what Microsoft's documentation recommends. Catch the exception, call RemoveAll so the unreadable entries are cleared, and treat the value as missing so the user can enter it or the app can fetch it again.
How do I stop SecureStorage values from being backed up at all?
Add an android:fullBackupContent rules file to the Android manifest that excludes the file named your-package-id.microsoft.maui.essentials.preferences.xml from the sharedpref domain. Then a restore never carries values that the new device cannot decrypt.

More decoded errors in the Fixes category; the app this came from starts at From Prompt to Pocket, Part 1.