Use a generated password when creating wallet
What changed, and why it matters
This commit fixes a security weakness where the Monero wallet was created and opened with a hardcoded password, 'pass'. Now the app generates a random 16-byte password for each wallet and stores it in the device's secure storage (like a keychain). It also disables Android's automatic cloud backup of app data. This makes it much harder for someone who gets access to the wallet file to unlock it, because the password is no longer a public, guessable word.
Treat this commit as a security hardening fix. Verify that flutter_secure_storage is configured correctly on each platform (e.g., Keychain on iOS/macOS, Keystore on Android, TPM/DPAPI where available on Windows), and confirm that existing wallets created with the old hardcoded password are migrated or no longer supported. Review whether 16 random bytes (32 hex chars) meets the project's threat model, and ensure the secure-storage key is not included in backups despite allowBackup='false'.
Security signals we found
Hardcoded password 'pass' replaced with per-wallet generated random password
Wallet password persisted using flutter_secure_storage keychain/keystore-backed storage
Android auto-backup disabled via android:allowBackup='false'
Empty-password guard added for non-dummy wallet creation
openExisting() now fails closed when the stored password cannot be retrieved
Evidence from the diff
The patch removes the hardcoded wallet password ‘pass’ from wallet creation and opening paths in lib/models/wallet_model.dart. It introduces lib/util/wallet_password.dart, which provides genWalletPassword() (16 random bytes rendered as 32 hex characters), storeWalletPassword(), and getWalletPassword() backed by flutter_secure_storage. The wallet creation flow now generates a per-wallet random password, persists it via secure storage, and uses it when opening the wallet later. Dummy-wallet checks still use an empty password but are guarded by an explicit non-empty check for real wallets. AndroidManifest.xml sets android:allowBackup=’false’ to reduce exposure of local data via backups. The change is a hardening fix for a static-credential issue, not a full cryptographic audit.
Changed components
lib/models/wallet_model.dartlib/util/wallet_password.dartlib/consts.dartandroid/app/src/main/AndroidManifest.xmlflutter_secure_storage plugin integration (Android/iOS/Linux/macOS/Windows)Inspect captured patch +154 / −19
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 2aaf3b0..58a2220 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -6,7 +6,9 @@
<application
android:label="monero_light_wallet"
android:name="${applicationName}"
- android:icon="@mipmap/ic_launcher">
+ android:icon="@mipmap/ic_launcher"
+ android:allowBackup="false">
+
<meta-data
android:name="com.google.firebase.messaging.default_notification_channel_id"
diff --git a/lib/consts.dart b/lib/consts.dart
index 057a2d5..84d9338 100644
--- a/lib/consts.dart
+++ b/lib/consts.dart
@@ -3,3 +3,4 @@ const String torDataDirName = 'tor';
const int txDirectionIncoming = 0;
const int txDirectionOutgoing = 1;
const supportedFiatCurrencies = ['USD', 'EUR'];
+const walletPasswordStorageKey = 'walletPassword';
diff --git a/lib/models/wallet_model.dart b/lib/models/wallet_model.dart
index e02b672..158ac79 100644
--- a/lib/models/wallet_model.dart
+++ b/lib/models/wallet_model.dart
@@ -21,6 +21,7 @@ import 'package:monero_light_wallet/util/formatting.dart';
import 'package:monero_light_wallet/util/height.dart';
import 'package:monero_light_wallet/util/logging.dart';
import 'package:monero_light_wallet/util/wallet.dart';
+import 'package:monero_light_wallet/util/wallet_password.dart';
String generateHexString(int length) {
final Random random = Random.secure();
@@ -438,11 +439,16 @@ class WalletModel with ChangeNotifier {
});
}
- Future<MoneroWallet> _getWalletFromLegacySeed(
- String mnemonic,
- int restoreHeight, {
+ Future<MoneroWallet> _getWalletFromLegacySeed({
+ required String mnemonic,
+ required int restoreHeight,
+ required String password,
bool isDummy = false,
}) async {
+ if (!isDummy && password == '') {
+ throw Exception('Password should not be empty.');
+ }
+
final wmFfiAddr = _w2WalletManager.ffiAddress();
final walletPath = await getWalletPath();
@@ -453,7 +459,7 @@ class WalletModel with ChangeNotifier {
mnemonic: mnemonic,
seedOffset: '',
restoreHeight: restoreHeight,
- password: 'pass',
+ password: password,
path: isDummy ? '' : walletPath,
).address;
});
@@ -461,11 +467,16 @@ class WalletModel with ChangeNotifier {
return MoneroWallet(Pointer<Void>.fromAddress(walletFfiAddr));
}
- Future<MoneroWallet> _getWalletFromPolyseed(
- String mnemonic,
- int restoreHeight, {
+ Future<MoneroWallet> _getWalletFromPolyseed({
+ required String mnemonic,
+ required int restoreHeight,
+ required String password,
bool isDummy = false,
}) async {
+ if (!isDummy && password == '') {
+ throw Exception('Password should not be empty.');
+ }
+
final wmFfiAddr = _w2WalletManager.ffiAddress();
final walletPath = await getWalletPath();
@@ -477,7 +488,7 @@ class WalletModel with ChangeNotifier {
seedOffset: '',
restoreHeight: restoreHeight,
path: isDummy ? '' : walletPath,
- password: 'pass',
+ password: password,
newWallet: true,
kdfRounds: 1,
).address;
@@ -492,44 +503,69 @@ class WalletModel with ChangeNotifier {
String passphrase = '',
]) async {
final legacyWallet = await _getWalletFromLegacySeed(
- mnemonic,
- restoreHeight,
+ mnemonic: mnemonic,
+ restoreHeight: restoreHeight,
+ password: '',
isDummy: true,
);
final polyseedWallet = await _getWalletFromPolyseed(
- mnemonic,
- restoreHeight,
+ mnemonic: mnemonic,
+ restoreHeight: restoreHeight,
+ password: '',
isDummy: true,
);
final legacyError = legacyWallet.errorString();
final polyseedError = polyseedWallet.errorString();
+ final walletPassword = genWalletPassword();
+
if (!legacyError.contains('word list failed verification')) {
- _w2Wallet = await _getWalletFromLegacySeed(mnemonic, restoreHeight);
+ _w2Wallet = await _getWalletFromLegacySeed(
+ mnemonic: mnemonic,
+ restoreHeight: restoreHeight,
+ password: walletPassword,
+ );
} else if (polyseedError != 'Failed polyseed decode') {
- _w2Wallet = await _getWalletFromPolyseed(mnemonic, restoreHeight);
+ _w2Wallet = await _getWalletFromPolyseed(
+ mnemonic: mnemonic,
+ restoreHeight: restoreHeight,
+ password: walletPassword,
+ );
}
if (_w2Wallet == null) {
throw Exception("Something went wrong when generating seed");
}
+ await storeWalletPassword(walletPassword);
+
_w2TxHistory = _w2Wallet!.history();
- store();
+ await store();
notifyListeners();
}
- Future openExisting() async {
+ Future<void> openExisting() async {
final path = await getWalletPath();
- _w2Wallet = _w2WalletManager.openWallet(path: path, password: 'pass');
+ final password = await getWalletPassword();
+
+ if (password == null) {
+ final errorMsg =
+ 'Failed to open existing wallet: could not get password.';
+ log(LogLevel.error, errorMsg);
+ throw Exception(errorMsg);
+ }
+
+ _w2Wallet = _w2WalletManager.openWallet(path: path, password: password);
final errorString = _w2WalletManager.errorString();
if (errorString != '') {
- log(LogLevel.error, 'Failed to open existing wallet: $errorString');
+ final errorMsg = 'Failed to open existing wallet: $errorString';
+ log(LogLevel.error, errorMsg);
+ throw Exception(errorMsg);
}
_w2TxHistory = _w2Wallet!.history();
diff --git a/lib/util/wallet_password.dart b/lib/util/wallet_password.dart
new file mode 100644
index 0000000..f25442b
--- /dev/null
+++ b/lib/util/wallet_password.dart
@@ -0,0 +1,28 @@
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
+import 'package:monero_light_wallet/consts.dart';
+
+String genWalletPassword() {
+ final byteLength = 16;
+ final rand = Random.secure();
+ final bytes = Uint8List.fromList(
+ List<int>.generate(byteLength, (_) => rand.nextInt(256)),
+ );
+ final sb = StringBuffer();
+ for (final b in bytes) {
+ sb.write(b.toRadixString(16).padLeft(2, '0'));
+ }
+ return sb.toString();
+}
+
+Future<void> storeWalletPassword(String password) async {
+ final storage = FlutterSecureStorage();
+ await storage.write(key: walletPasswordStorageKey, value: password);
+}
+
+Future<String?> getWalletPassword() async {
+ final storage = FlutterSecureStorage();
+ return storage.read(key: walletPasswordStorageKey);
+}
diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc
index f6f23bf..38dd0bc 100644
--- a/linux/flutter/generated_plugin_registrant.cc
+++ b/linux/flutter/generated_plugin_registrant.cc
@@ -6,9 +6,13 @@
#include "generated_plugin_registrant.h"
+#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
+ g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
+ flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake
index 1e0aea0..dc0bcf2 100644
--- a/linux/flutter/generated_plugins.cmake
+++ b/linux/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ flutter_secure_storage_linux
url_launcher_linux
)
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 64d86c2..c2f0a8e 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation
import flutter_local_notifications
+import flutter_secure_storage_macos
import mobile_scanner
import path_provider_foundation
import screen_brightness_macos
@@ -14,6 +15,7 @@ import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
+ FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenBrightnessMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenBrightnessMacosPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index 6caf489..0fa8f4b 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -179,6 +179,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.1"
+ flutter_secure_storage:
+ dependency: "direct main"
+ description:
+ name: flutter_secure_storage
+ sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "9.2.4"
+ flutter_secure_storage_linux:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_linux
+ sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.3"
+ flutter_secure_storage_macos:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_macos
+ sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
+ flutter_secure_storage_platform_interface:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_platform_interface
+ sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.2"
+ flutter_secure_storage_web:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_web
+ sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ flutter_secure_storage_windows:
+ dependency: transitive
+ description:
+ name: flutter_secure_storage_windows
+ sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
flutter_svg:
dependency: "direct main"
description:
@@ -221,6 +269,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.20.2"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.7"
leak_tracker:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 50099a2..2de61fb 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -64,6 +64,7 @@ dependencies:
permission_handler: ^12.0.1
flutter_svg: ^2.2.1
skeletonizer: ^2.1.0+1
+ flutter_secure_storage: ^9.2.4
dev_dependencies:
flutter_test:
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index f0270da..89ba8c2 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -6,12 +6,15 @@
#include "generated_plugin_registrant.h"
+#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
+ FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
ScreenBrightnessWindowsPluginRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 3b3429f..9a83522 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
+ flutter_secure_storage_windows
permission_handler_windows
screen_brightness_windows
share_plus
Why this scored 74/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.