From d039cacde92df4c3aa91552213c9bed0405a4aff Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Mar 2026 10:51:12 +0000 Subject: [PATCH] Migrate PIN storage from plain SharedPreferences to encrypted DataStore The PIN was stored as plaintext in SharedPreferences despite the function names suggesting encryption. This migrates it to the existing DataStore infrastructure with AES-GCM encryption via SecureCryptoHelper, using an app-level DataStore file ("app_datastore"). A one-time migration reads the legacy plaintext PIN from SharedPreferences, writes it encrypted to the DataStore, and removes the old entry. https://claude.ai/code/session_01TeTHfGJL42J7eHCDdhsDnP --- .../nostrsigner/DataStoreAccess.kt | 29 +++++++++++++++++++ .../nostrsigner/LocalPreferences.kt | 18 +++++------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt b/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt index 74e4f778..6575f027 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/DataStoreAccess.kt @@ -26,8 +26,21 @@ object DataStoreAccess { ) } + @Volatile + private var appDataStore: DataStore? = null + + private fun getAppDataStore(context: Context): DataStore = appDataStore ?: synchronized(this) { + appDataStore ?: PreferenceDataStoreFactory.create( + scope = Amber.instance.applicationIOScope, + produceFile = { + context.applicationContext.preferencesDataStoreFile("app_datastore") + }, + ).also { appDataStore = it } + } + val NOSTR_PRIVKEY = stringPreferencesKey("nostr_privkey") val SEED_WORDS = stringPreferencesKey("seed_words") + val PIN = stringPreferencesKey("pin") suspend fun saveEncryptedKey(context: Context, npub: String, key: Preferences.Key, value: String) { val encrypted = SecureCryptoHelper.encrypt(value) @@ -52,4 +65,20 @@ object DataStoreAccess { prefs.clear() } } + + suspend fun savePin(context: Context, pin: String?) { + getAppDataStore(context).edit { prefs -> + if (pin == null) { + prefs.remove(PIN) + } else { + prefs[PIN] = SecureCryptoHelper.encrypt(pin) + } + } + } + + suspend fun loadPin(context: Context): String? { + val prefs = getAppDataStore(context).data.first() + val encrypted = prefs[PIN] ?: return null + return SecureCryptoHelper.decrypt(encrypted) + } } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt index 58c48e4f..dc6cbc2f 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/LocalPreferences.kt @@ -159,20 +159,18 @@ object LocalPreferences { fun loadPinFromEncryptedStorage(): String? { val context = Amber.instance - return sharedPrefs(context).getString(SettingsKeys.PIN.key, null) + // Migration: move PIN from plain SharedPreferences to encrypted DataStore + val legacyPin = sharedPrefs(context).getString(SettingsKeys.PIN.key, null) + if (legacyPin != null) { + runBlocking { DataStoreAccess.savePin(context, legacyPin) } + sharedPrefs(context).edit { remove(SettingsKeys.PIN.key) } + } + return runBlocking { DataStoreAccess.loadPin(context) } } fun savePinToEncryptedStorage(pin: String?) { val context = Amber.instance - sharedPrefs(context).edit { - apply { - if (pin == null) { - remove(SettingsKeys.PIN.key) - } else { - putString(SettingsKeys.PIN.key, pin) - } - } - } + runBlocking { DataStoreAccess.savePin(context, pin) } } suspend fun reloadApp() {