From c22fa4d863ec3b7fdb114e76bded5e46b97a88a6 Mon Sep 17 00:00:00 2001
From: zHd4 <38856321+zHd4@users.noreply.github.com>
Date: Thu, 16 Jul 2026 16:13:47 +0200
Subject: [PATCH 1/5] Add key_setup string to strings.xml
---
app/src/main/res/values/strings.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index da303f2d..f6baaf31 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -23,6 +23,7 @@
Enter your private key hereā¦
Blocked!
Key Recovery
+ Key Setup
Apply
Wrong key!
Invalid key
From 0bd7dbc54c3e784251f52ffa4187042bdbcb26b3 Mon Sep 17 00:00:00 2001
From: zHd4 <38856321+zHd4@users.noreply.github.com>
Date: Thu, 16 Jul 2026 16:17:59 +0200
Subject: [PATCH 2/5] Refactor security actions and update menu handling in
NotesListActivity
---
.../activity/note/list/NotesListActivity.java | 64 +++++++++++--------
.../security/ChangePasswordOnClick.java | 23 -------
.../security/GenerateNewKeyAction.java | 47 ++++++++++++++
.../security/GenerateNewKeyOnClick.java | 60 -----------------
.../{LockOnClick.java => LockAction.java} | 24 ++++---
5 files changed, 100 insertions(+), 118 deletions(-)
delete mode 100644 app/src/main/java/app/notesr/activity/security/ChangePasswordOnClick.java
create mode 100644 app/src/main/java/app/notesr/activity/security/GenerateNewKeyAction.java
delete mode 100644 app/src/main/java/app/notesr/activity/security/GenerateNewKeyOnClick.java
rename app/src/main/java/app/notesr/activity/security/{LockOnClick.java => LockAction.java} (66%)
diff --git a/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java b/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java
index 0bd6537b..d344ba7b 100644
--- a/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java
+++ b/app/src/main/java/app/notesr/activity/note/list/NotesListActivity.java
@@ -14,6 +14,7 @@
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
+import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -36,9 +37,11 @@
import app.notesr.activity.exporter.ExportActivity;
import app.notesr.activity.importer.ImportActivity;
import app.notesr.activity.note.editor.OpenNoteActivity;
-import app.notesr.activity.security.ChangePasswordOnClick;
-import app.notesr.activity.security.GenerateNewKeyOnClick;
-import app.notesr.activity.security.LockOnClick;
+import app.notesr.activity.security.AuthActivity;
+import app.notesr.activity.security.GenerateNewKeyAction;
+import app.notesr.activity.security.LockAction;
+import app.notesr.core.security.crypto.CryptoManager;
+import app.notesr.core.security.crypto.CryptoManagerProvider;
import app.notesr.data.AppDatabase;
import app.notesr.data.DatabaseProvider;
import app.notesr.data.model.Note;
@@ -47,17 +50,18 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
public final class NotesListActivity extends ActivityBase {
private static final int SEARCH_DELAY = 300;
- private final Map> menuItemsMap = new HashMap<>();
+ private final SparseArray menuActions = new SparseArray<>();
private final Map notesIdsMap = new HashMap<>();
private final Handler searchHandler = new Handler(Looper.getMainLooper());
private ActivityResultLauncher noteEditorLauncher;
+ private LockAction lockAction;
+ private GenerateNewKeyAction generateNewKeyAction;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -70,18 +74,16 @@ protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_note_list);
applyInsets(findViewById(R.id.main));
- getOnBackPressedDispatcher()
- .addCallback(this, new OnBackPressedCallback(true) {
- @Override
- public void handleOnBackPressed() {
- LockOnClick lock = new LockOnClick();
- lock.accept(NotesListActivity.this);
- }
- });
+ CryptoManager cryptoManager = CryptoManagerProvider.getInstance(getApplicationContext());
+
+ lockAction = new LockAction(this, cryptoManager);
+ generateNewKeyAction = new GenerateNewKeyAction(this, cryptoManager);
noteEditorLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), getOpenNoteResultCallback());
+ getOnBackPressedDispatcher().addCallback(this, getOnBackPressedCallback());
+
ListView notesView = findViewById(R.id.notesListView);
FloatingActionButton newNoteButton = findViewById(R.id.addNoteButton);
@@ -100,20 +102,15 @@ public void handleOnBackPressed() {
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
- Intent importActivityIntent = new Intent(this, ImportActivity.class);
- Intent exportActivityIntent = new Intent(this, ExportActivity.class);
-
getMenuInflater().inflate(R.menu.menu_notes_list, menu);
- menuItemsMap.put(R.id.lockAppButton, new LockOnClick());
- menuItemsMap.put(R.id.changePasswordMenuItem, new ChangePasswordOnClick());
- menuItemsMap.put(R.id.generateNewKeyMenuItem, new GenerateNewKeyOnClick());
-
- menuItemsMap.put(R.id.exportMenuItem, action ->
- startActivity(exportActivityIntent));
-
- menuItemsMap.put(R.id.importMenuItem, action ->
- startActivity(importActivityIntent));
+ menuActions.put(R.id.lockAppButton, lockAction::lock);
+ menuActions.put(R.id.changePasswordMenuItem, this::startChangePasswordActivity);
+ menuActions.put(R.id.generateNewKeyMenuItem, generateNewKeyAction::startActivity);
+ menuActions.put(R.id.exportMenuItem,
+ () -> startActivity(new Intent(this, ExportActivity.class)));
+ menuActions.put(R.id.importMenuItem,
+ () -> startActivity(new Intent(this, ImportActivity.class)));
SearchView searchView = (SearchView) menu.findItem(R.id.searchNotesButton).getActionView();
requireNonNull(searchView, "SearchView is null");
@@ -131,7 +128,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId != R.id.searchNotesButton) {
- requireNonNull(menuItemsMap.get(itemId)).accept(this);
+ requireNonNull(menuActions.get(itemId)).run();
}
return true;
@@ -250,4 +247,19 @@ private void openNote(String noteId) {
noteEditorLauncher.launch(intent);
}
+
+ private void startChangePasswordActivity() {
+ Intent intent = new Intent(getApplicationContext(), AuthActivity.class)
+ .putExtra(AuthActivity.EXTRA_MODE, AuthActivity.Mode.CHANGE_PASSWORD.toString());
+ startActivity(intent);
+ }
+
+ private OnBackPressedCallback getOnBackPressedCallback() {
+ return new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ lockAction.lock();
+ }
+ };
+ }
}
diff --git a/app/src/main/java/app/notesr/activity/security/ChangePasswordOnClick.java b/app/src/main/java/app/notesr/activity/security/ChangePasswordOnClick.java
deleted file mode 100644
index 4c1652e9..00000000
--- a/app/src/main/java/app/notesr/activity/security/ChangePasswordOnClick.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (c) 2026 zHd4
- * SPDX-License-Identifier: MIT
- */
-
-package app.notesr.activity.security;
-
-import android.content.Intent;
-
-import app.notesr.activity.ActivityBase;
-
-import java.util.function.Consumer;
-
-public final class ChangePasswordOnClick implements Consumer {
- @Override
- public void accept(ActivityBase activity) {
- Intent authActivityIntent = new Intent(activity.getApplicationContext(), AuthActivity.class)
- .putExtra(AuthActivity.EXTRA_MODE, AuthActivity.Mode.CHANGE_PASSWORD.toString());
-
- activity.startActivity(authActivityIntent);
- activity.finish();
- }
-}
diff --git a/app/src/main/java/app/notesr/activity/security/GenerateNewKeyAction.java b/app/src/main/java/app/notesr/activity/security/GenerateNewKeyAction.java
new file mode 100644
index 00000000..0af85e83
--- /dev/null
+++ b/app/src/main/java/app/notesr/activity/security/GenerateNewKeyAction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2026 zHd4
+ * SPDX-License-Identifier: MIT
+ */
+
+package app.notesr.activity.security;
+
+import static app.notesr.core.util.CharUtils.charsToBytes;
+
+import android.content.Context;
+import android.content.Intent;
+
+import app.notesr.activity.ActivityBase;
+import app.notesr.core.security.SecretCache;
+import app.notesr.core.security.crypto.CryptoManager;
+import app.notesr.core.security.dto.CryptoSecrets;
+import lombok.RequiredArgsConstructor;
+
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+@RequiredArgsConstructor
+public final class GenerateNewKeyAction {
+
+ private final ActivityBase activity;
+ private final CryptoManager cryptoManager;
+
+ public void startActivity() {
+ CryptoSecrets secrets = cryptoManager.getSecrets();
+ char[] passwordChars = Arrays.copyOf(secrets.getPassword(), secrets.getPassword().length);
+
+ try {
+ byte[] passwordBytes = charsToBytes(passwordChars, StandardCharsets.UTF_8);
+ SecretCache.put(SetupKeyActivity.CACHE_KEY_PASSWORD, passwordBytes);
+ } catch (CharacterCodingException e) {
+ throw new RuntimeException(e);
+ }
+
+ secrets.destroy();
+
+ Context context = activity.getApplicationContext();
+ var intent = new Intent(context, SetupKeyActivity.class)
+ .putExtra(SetupKeyActivity.EXTRA_MODE, KeySetupMode.REGENERATION.toString());
+ activity.startActivity(intent);
+ }
+}
diff --git a/app/src/main/java/app/notesr/activity/security/GenerateNewKeyOnClick.java b/app/src/main/java/app/notesr/activity/security/GenerateNewKeyOnClick.java
deleted file mode 100644
index e89f33c2..00000000
--- a/app/src/main/java/app/notesr/activity/security/GenerateNewKeyOnClick.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (c) 2026 zHd4
- * SPDX-License-Identifier: MIT
- */
-
-package app.notesr.activity.security;
-
-import static app.notesr.core.util.CharUtils.charsToBytes;
-
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-
-import app.notesr.activity.ActivityBase;
-import app.notesr.R;
-import app.notesr.activity.DialogFactory;
-import app.notesr.core.security.SecretCache;
-import app.notesr.core.security.crypto.CryptoManager;
-import app.notesr.core.security.crypto.CryptoManagerProvider;
-
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.StandardCharsets;
-import java.util.function.Consumer;
-
-public final class GenerateNewKeyOnClick implements Consumer {
- @Override
- public void accept(ActivityBase activity) {
- DialogInterface.OnClickListener buttonHandler = regenerateKeyDialogOnClick(activity);
- new DialogFactory(activity)
- .getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_warning)
- .setTitle(R.string.warning)
- .setPositiveButton(R.string.yes, buttonHandler)
- .setNegativeButton(R.string.no, buttonHandler)
- .create()
- .show();
- }
-
- private DialogInterface.OnClickListener regenerateKeyDialogOnClick(ActivityBase activity) {
- return (dialog, result) -> {
- if (result == DialogInterface.BUTTON_POSITIVE) {
- Context context = activity.getApplicationContext();
- Intent intent = new Intent(context, SetupKeyActivity.class);
-
- CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context);
- char[] password = cryptoManager.getSecrets().getPassword();
-
- intent.putExtra(SetupKeyActivity.EXTRA_MODE, KeySetupMode.REGENERATION.toString());
-
- try {
- byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8);
- SecretCache.put(SetupKeyActivity.PASSWORD, passwordBytes);
- } catch (CharacterCodingException e) {
- throw new RuntimeException(e);
- }
-
- activity.startActivity(intent);
- }
- };
- }
-}
diff --git a/app/src/main/java/app/notesr/activity/security/LockOnClick.java b/app/src/main/java/app/notesr/activity/security/LockAction.java
similarity index 66%
rename from app/src/main/java/app/notesr/activity/security/LockOnClick.java
rename to app/src/main/java/app/notesr/activity/security/LockAction.java
index a7d736e1..0b733e67 100644
--- a/app/src/main/java/app/notesr/activity/security/LockOnClick.java
+++ b/app/src/main/java/app/notesr/activity/security/LockAction.java
@@ -11,23 +11,29 @@
import app.notesr.activity.ActivityBase;
import app.notesr.core.security.SecretCache;
import app.notesr.core.security.crypto.CryptoManager;
-import app.notesr.core.security.crypto.CryptoManagerProvider;
+import app.notesr.data.DatabaseProvider;
+import lombok.RequiredArgsConstructor;
-import java.util.function.Consumer;
+@RequiredArgsConstructor
+public final class LockAction {
-public final class LockOnClick implements Consumer {
- @Override
- public void accept(ActivityBase activity) {
+ private final ActivityBase activity;
+ private final CryptoManager cryptoManager;
+
+ public void lock() {
Context context = activity.getApplicationContext();
Intent authActivityIntent = new Intent(context, AuthActivity.class)
.putExtra(AuthActivity.EXTRA_MODE, AuthActivity.Mode.AUTHORIZATION.toString());
- CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context);
- cryptoManager.destroySecrets();
-
- SecretCache.clear();
+ clearSecrets();
activity.startActivity(authActivityIntent);
activity.finish();
}
+
+ private void clearSecrets() {
+ DatabaseProvider.close();
+ cryptoManager.destroySecrets();
+ SecretCache.clear();
+ }
}
From a1d6ca76c42156aa56b075dffb0e31060cc76127 Mon Sep 17 00:00:00 2001
From: zHd4 <38856321+zHd4@users.noreply.github.com>
Date: Thu, 16 Jul 2026 16:18:18 +0200
Subject: [PATCH 3/5] Update theme in SetupKeyActivity to use AppTheme
---
app/src/main/AndroidManifest.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c28e6f26..dad8575d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -34,7 +34,7 @@
android:name=".activity.security.SetupKeyActivity"
android:exported="false"
android:screenOrientation="locked"
- android:theme="@style/AppTheme.NoActionBar" />
+ android:theme="@style/AppTheme" />
Date: Thu, 16 Jul 2026 16:20:42 +0200
Subject: [PATCH 4/5] Refactor key setup activities and improve security
handling
---
.../security/AuthActivityExtension.java | 16 ++--
.../activity/security/ImportKeyActivity.java | 78 ++++++++++++------
.../security/KeySetupCompletionHandler.java | 24 ++++--
.../activity/security/SetupKeyActivity.java | 79 ++++++++++++++-----
4 files changed, 140 insertions(+), 57 deletions(-)
diff --git a/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java b/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java
index c1ddc9e3..913cc4e1 100644
--- a/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java
+++ b/app/src/main/java/app/notesr/activity/security/AuthActivityExtension.java
@@ -71,7 +71,7 @@ public void authorize() {
}
public void createPassword() {
- var password = proceedPasswordSetting();
+ char[] password = proceedPasswordSetting();
if (password != null) {
var context = activity.getApplicationContext();
@@ -80,7 +80,7 @@ public void createPassword() {
try {
var passwordBytes = charsToBytes(password, StandardCharsets.UTF_8);
- SecretCache.put(SetupKeyActivity.PASSWORD, passwordBytes);
+ SecretCache.put(SetupKeyActivity.CACHE_KEY_PASSWORD, passwordBytes);
} catch (CharacterCodingException e) {
throw new RuntimeException(e);
}
@@ -90,11 +90,11 @@ public void createPassword() {
}
public void recoverKey() {
- var password = proceedPasswordSetting();
+ char[] password = proceedPasswordSetting();
if (password != null) {
try {
- var hexKey = bytesToChars(SecretCache.take(HEX_KEY),
+ char[] hexKey = bytesToChars(SecretCache.take(HEX_KEY),
StandardCharsets.UTF_8);
if (hexKey == null) {
@@ -118,19 +118,19 @@ public void recoverKey() {
}
public void changePassword() {
- var password = proceedPasswordSetting();
+ char[] password = proceedPasswordSetting();
if (password != null) {
try {
- var context = activity.getApplicationContext();
- var secrets = cryptoManager.getSecrets();
+ Context context = activity.getApplicationContext();
+ CryptoSecrets secrets = cryptoManager.getSecrets();
secrets.setPassword(password);
cryptoManager.setSecrets(context, secrets);
secrets.destroy();
showToastMessage(R.string.updated);
- activity.startActivity(new Intent(context, NotesListActivity.class));
+ activity.startActivity(new Intent(context, NotesListActivity.class));
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java b/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java
index 3a536f40..3d231dd6 100644
--- a/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java
+++ b/app/src/main/java/app/notesr/activity/security/ImportKeyActivity.java
@@ -26,6 +26,7 @@
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import app.notesr.R;
import app.notesr.activity.ActivityBase;
@@ -37,12 +38,14 @@
public final class ImportKeyActivity extends ActivityBase {
- public static final String PASSWORD = "password";
+ public static final String CACHE_KEY_PASSWORD = "password";
public static final String EXTRA_MODE = "mode";
private static final String TAG = ImportKeyActivity.class.getCanonicalName();
private KeySetupMode mode;
private EditText keyField;
+ private char[] hexKey;
+ private char[] password;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -56,6 +59,7 @@ protected void onCreate(Bundle savedInstanceState) {
actionBar.setTitle(getResources().getString(R.string.import_key));
mode = KeySetupMode.valueOf(requireNonNull(getIntent().getStringExtra(EXTRA_MODE)));
+ password = getPasswordFromCache();
keyField = findViewById(R.id.importKeyField);
keyField.setImeOptions(IME_FLAG_NO_PERSONALIZED_LEARNING);
@@ -65,50 +69,72 @@ protected void onCreate(Bundle savedInstanceState) {
}
@Override
- public boolean onSupportNavigateUp() {
- super.onBackPressed();
- return true;
+ protected boolean requiresSession() {
+ return false;
}
@Override
- protected boolean requiresSession() {
- return false;
+ public void finish() {
+ wipeSecrets();
+ super.finish();
}
private View.OnClickListener importKeyButtonOnClick() {
return view -> {
Editable hexKeyEditable = keyField.getText();
- char[] hexKey = new char[hexKeyEditable.length()];
+ hexKey = new char[hexKeyEditable.length()];
hexKeyEditable.getChars(0, hexKeyEditable.length(), hexKey, 0);
- hexKeyEditable.replace(0, hexKeyEditable.length(), "");
- keyField.setText("");
if (hexKey.length > 0) {
- try {
- Context context = getApplicationContext();
+ Context context = getApplicationContext();
+ CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context);
- char[] password = bytesToChars(SecretCache.take(PASSWORD),
- StandardCharsets.UTF_8);
+ CryptoSecrets cryptoSecrets;
- CryptoSecrets cryptoSecrets = getSecretsFromHex(hexKey, password);
- CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context);
-
- SecretsSetupService keySetupService = new SecretsSetupService(
- getApplicationContext(),
- cryptoManager,
- cryptoSecrets
- );
-
- new KeySetupCompletionHandler(this, keySetupService, mode).handle();
- } catch (IllegalArgumentException e) {
+ try {
+ cryptoSecrets = getSecretsFromHex(hexKey, password);
+ cryptoSecrets.validate();
+ } catch (IllegalArgumentException | IllegalStateException e) {
Log.e(TAG, "Invalid key", e);
showToastMessage(this, getString(R.string.invalid_key),
Toast.LENGTH_SHORT);
- } catch (CharacterCodingException e) {
- throw new RuntimeException(e);
+
+ return;
}
+
+ SecretsSetupService secretsSetupService = new SecretsSetupService(
+ getApplicationContext(),
+ cryptoManager,
+ cryptoSecrets
+ );
+
+ new KeySetupCompletionHandler(this, secretsSetupService, mode).handle();
}
};
}
+
+ private char[] getPasswordFromCache() {
+ try {
+ byte[] passwordBytes = requireNonNull(SecretCache.take(CACHE_KEY_PASSWORD),
+ "Password missing in secret cache");
+ return bytesToChars(passwordBytes, StandardCharsets.UTF_8);
+ } catch (CharacterCodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void wipeSecrets() {
+ // Clearing only key field, not password, because reference to password is stored in cache
+ // and still could be used by SetupKeyActivity. SetupKeyActivity as a parent activity
+ // is responsible to clear password from cache.
+ if (hexKey != null && hexKey.length > 0) {
+ Arrays.fill(hexKey, '\0');
+ }
+
+ Editable hexKeyEditable = keyField.getText();
+ hexKeyEditable.replace(0, hexKeyEditable.length(), "");
+
+ keyField.setText("");
+ }
}
diff --git a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java
index 194960ff..8e87ebf4 100644
--- a/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java
+++ b/app/src/main/java/app/notesr/activity/security/KeySetupCompletionHandler.java
@@ -7,7 +7,6 @@
import static app.notesr.core.util.CharUtils.charsToBytes;
-import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@@ -16,6 +15,9 @@
import java.util.Arrays;
import app.notesr.BuildConfig;
+import app.notesr.R;
+import app.notesr.activity.ActivityBase;
+import app.notesr.activity.DialogFactory;
import app.notesr.activity.migration.MigrationActivity;
import app.notesr.activity.note.list.NotesListActivity;
import app.notesr.core.security.SecretCache;
@@ -27,7 +29,7 @@
@RequiredArgsConstructor
public final class KeySetupCompletionHandler {
- private final Activity activity;
+ private final ActivityBase activity;
private final SecretsSetupService secretsSetupService;
private final KeySetupMode mode;
@@ -46,7 +48,7 @@ private void proceedFirstRun() {
Context context = activity.getApplicationContext();
Intent nextIntent = new Intent(context, NotesListActivity.class);
- DataVersionManager dataVersionManager = new DataVersionManager(context);
+ var dataVersionManager = new DataVersionManager(context);
int lastMigrationVersion = dataVersionManager.getCurrentVersion();
int currentDataSchemaVersion = BuildConfig.DATA_SCHEMA_VERSION;
@@ -65,11 +67,23 @@ private void proceedFirstRun() {
}
private void proceedRegeneration() {
+ new DialogFactory(activity)
+ .getThemedAlertDialogBuilder(R.layout.dialog_re_encryption_warning)
+ .setTitle(R.string.warning)
+ .setPositiveButton(R.string.yes,
+ (dialog, which) -> onRegenerationConfirmed())
+ .setNegativeButton(R.string.no, null)
+ .create()
+ .show();
+ }
+
+ private void onRegenerationConfirmed() {
CryptoSecrets secrets = secretsSetupService.getCryptoSecrets();
+ byte[] keyBytes = Arrays.copyOf(secrets.getKey(), secrets.getKey().length);
+ char[] password = Arrays.copyOf(secrets.getPassword(), secrets.getPassword().length);
+
try {
- byte[] keyBytes = Arrays.copyOf(secrets.getKey(), secrets.getKey().length);
- char[] password = Arrays.copyOf(secrets.getPassword(), secrets.getPassword().length);
byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8);
SecretCache.put(SecretsUpdateAndroidService.NEW_KEY, keyBytes);
diff --git a/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java b/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java
index fc9bfc32..8c679b2c 100644
--- a/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java
+++ b/app/src/main/java/app/notesr/activity/security/SetupKeyActivity.java
@@ -8,7 +8,6 @@
import static java.util.Objects.requireNonNull;
import static app.notesr.core.util.ActivityUtils.copyToClipboard;
-import static app.notesr.core.util.ActivityUtils.disableBackButton;
import static app.notesr.core.util.ActivityUtils.showToastMessage;
import static app.notesr.core.util.CharUtils.bytesToChars;
import static app.notesr.core.util.CharUtils.charsToBytes;
@@ -17,13 +16,18 @@
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
+import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.activity.OnBackPressedCallback;
+import androidx.appcompat.app.ActionBar;
+
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
import app.notesr.R;
import app.notesr.activity.ActivityBase;
@@ -37,7 +41,7 @@
@Getter
public final class SetupKeyActivity extends ActivityBase {
- public static final String PASSWORD = "password";
+ public static final String CACHE_KEY_PASSWORD = "password";
public static final String EXTRA_MODE = "mode";
private static final int LOW_SCREEN_HEIGHT = 800;
private static final float KEY_VIEW_TEXT_SIZE_FOR_LOW_SCREEN_HEIGHT = 16;
@@ -53,16 +57,12 @@ protected void onCreate(Bundle savedInstanceState) {
applyInsets(findViewById(R.id.main));
mode = KeySetupMode.valueOf(requireNonNull(getIntent().getStringExtra(EXTRA_MODE)));
+ ActionBar actionBar = requireNonNull(getSupportActionBar());
- if (mode == KeySetupMode.REGENERATION) {
- disableBackButton(this);
- }
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ actionBar.setTitle(R.string.key_setup);
- try {
- password = bytesToChars(SecretCache.take(PASSWORD), StandardCharsets.UTF_8);
- } catch (CharacterCodingException e) {
- throw new RuntimeException(e);
- }
+ password = getPasswordFromCache();
Context context = getApplicationContext();
CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context);
@@ -85,6 +85,8 @@ protected void onCreate(Bundle savedInstanceState) {
copyToClipboardButton.setOnClickListener(copyKeyButtonOnClick());
importButton.setOnClickListener(importKeyButtonOnClick());
nextButton.setOnClickListener(nextButtonOnClick());
+
+ getOnBackPressedDispatcher().addCallback(this, getOnBackPressedCallback());
}
@Override
@@ -92,6 +94,38 @@ protected boolean requiresSession() {
return false;
}
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ onBackPressedAction();
+ finish();
+
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void finish() {
+ wipeKeyView();
+ super.finish();
+ }
+
+ private OnBackPressedCallback getOnBackPressedCallback() {
+ return new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ onBackPressedAction();
+ finish();
+ }
+ };
+ }
+
+ private void onBackPressedAction() {
+ SecretCache.removeIfExists(CACHE_KEY_PASSWORD);
+ }
+
private void adaptKeyView() {
TextView keyView = findViewById(R.id.aesKeyHex);
@@ -111,25 +145,34 @@ private View.OnClickListener copyKeyButtonOnClick() {
private View.OnClickListener importKeyButtonOnClick() {
return view -> {
- Intent intent = new Intent(getApplicationContext(), ImportKeyActivity.class)
- .putExtra(ImportKeyActivity.EXTRA_MODE, mode.toString());
+ char[] passwordCopy = Arrays.copyOf(password, password.length);
try {
- byte[] passwordBytes = charsToBytes(password, StandardCharsets.UTF_8);
- SecretCache.put(ImportKeyActivity.PASSWORD, passwordBytes);
+ byte[] passwordBytes = charsToBytes(passwordCopy, StandardCharsets.UTF_8);
+ SecretCache.put(ImportKeyActivity.CACHE_KEY_PASSWORD, passwordBytes);
} catch (CharacterCodingException e) {
throw new RuntimeException(e);
}
+ Intent intent = new Intent(getApplicationContext(), ImportKeyActivity.class)
+ .putExtra(ImportKeyActivity.EXTRA_MODE, mode.toString());
startActivity(intent);
};
}
+ private char[] getPasswordFromCache() {
+ try {
+ byte[] passwordBytes = requireNonNull(SecretCache.take(CACHE_KEY_PASSWORD),
+ "Password missing in secret cache");
+ return bytesToChars(passwordBytes, StandardCharsets.UTF_8);
+ } catch (CharacterCodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private View.OnClickListener nextButtonOnClick() {
- return view -> {
- wipeKeyView();
- new KeySetupCompletionHandler(this, keySetupService, mode).handle();
- };
+ var handler = new KeySetupCompletionHandler(this, keySetupService, mode);
+ return view -> handler.handle();
}
private void wipeKeyView() {
From 00dc5b1da4d82430ff58894f3fd4d43d9edc65a6 Mon Sep 17 00:00:00 2001
From: zHd4 <38856321+zHd4@users.noreply.github.com>
Date: Thu, 16 Jul 2026 16:21:40 +0200
Subject: [PATCH 5/5] Refactor key recovery logic to improve key handling and
memory safety
---
.../app/notesr/activity/security/KeyRecoveryActivity.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java
index f9dbecaf..7c081083 100644
--- a/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java
+++ b/app/src/main/java/app/notesr/activity/security/KeyRecoveryActivity.java
@@ -91,13 +91,15 @@ private View.OnClickListener applyButtonOnClick(EditText hexKeyField) {
private void apply(EditText hexKeyField, char[] hexKey)
throws IOException, NoSuchAlgorithmException {
- byte[] keyBytes = getKeyBytesFromHex(Arrays.copyOf(hexKey, hexKey.length));
+ char[] hexKeyCopy = Arrays.copyOf(hexKey, hexKey.length);
+ byte[] keyBytes = getKeyBytesFromHex(hexKeyCopy);
Context context = getApplicationContext();
CryptoManager cryptoManager = CryptoManagerProvider.getInstance(context);
if (cryptoManager.verifyKey(context, keyBytes)) {
- SecretCache.put(AuthActivity.HEX_KEY, charsToBytes(hexKey, StandardCharsets.UTF_8));
+ byte[] hexKeyBytes = charsToBytes(hexKey, StandardCharsets.UTF_8);
+ SecretCache.put(AuthActivity.HEX_KEY, hexKeyBytes);
// The hex key has already been wiped by charsToBytes
wipeSecretData(keyBytes, hexKeyField);