This document describes the encrypted local database implementation for WiFi/BT GeoGrabber using SQLCipher for Android. The feature provides AES-256 encryption to protect sensitive scan data at rest.
| Feature | Description | Status |
|---|---|---|
| Database Encryption | AES-256 encryption using SQLCipher | β Implemented |
| Passphrase Management | Secure storage via Android Keystore | β Implemented |
| Auto-Unlock | In-memory passphrase caching during runtime | β Implemented |
| Database Migration | Convert unencrypted β encrypted seamlessly | β Implemented |
| Import/Export | Support for encrypted database files | β οΈ Needs UI integration |
| Change Passphrase | Re-encrypt with new passphrase | β Implemented |
| Disable Encryption | Convert encrypted β unencrypted (optional) | β Implemented |
EncryptionManager.java)Handles all encryption-related operations:
Key Methods:
boolean setupEncryption(char[] passphrase)
boolean unlockWithPassphrase(char[] passphrase)
String getCachedDatabaseKey()
boolean changePassphrase(char[] oldPassphrase, char[] newPassphrase)
void clearPassphrase()
boolean isEncryptionEnabled()
boolean isPassphraseSet()
DatabaseEncryptionHelper.java)Manages encrypted SQLite databases using SQLCipher:
Key Methods:
static boolean migrateToEncrypted(Context, String unencryptedPath, String encryptedPath, String passphrase)
static boolean changeEncryptionKey(String dbPath, String oldPassphrase, String newPassphrase)
static boolean decryptDatabase(Context, String encryptedPath, String unencryptedPath, String passphrase)
static boolean isDatabaseEncrypted(File dbFile)
SQLiteDatabase openEncryptedDatabase()
res/values/strings.xml)All UI strings for encryption dialogs, messages, and indicators (90+ strings added).
βββββββββββββββββββββββββββββββββββββββββββ
β User Passphrase (6+ characters) β
β β β
β SHA-256 + Salt β Database Key (64 hex) β
β β β
β Android Keystore (AES-256-GCM) β
β β β
β Encrypted Storage (SharedPreferences) β
βββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββ
β SQLCipher Database (AES-256-CBC) β
β β’ Page-level encryption β
β β’ HMAC integrity verification β
β β’ KDF: PBKDF2-HMAC-SHA512 (256k iter) β
βββββββββββββββββββββββββββββββββββββββββββ
char[] (not String)onPause() / onDestroy()App Launch
β
[Dialog] "Enable Database Encryption?"
ββ "Enable Now" β Setup Dialog
β β
β [Input] Enter passphrase (min 6 chars)
β [Input] Confirm passphrase
β [Button] "Set Passphrase"
β β
β [Progress] "Encrypting databaseβ¦"
β β
β [Success] "β Database encrypted!"
β β
β Database now encrypted (transparent to user)
β
ββ "Maybe Later" β Continue unencrypted
App Launch (with encrypted DB)
β
[Dialog] "Unlock Database"
[Input] Enter passphrase
[Button] "Unlock"
β
ββ Correct β Database unlocked (cached in memory)
ββ Wrong β "β Wrong passphrase. Access denied."
More Menu β "π Database Encryption" β "Change Passphrase"
β
[Input] Current passphrase
[Input] New passphrase
[Input] Confirm new passphrase
[Button] "Change"
β
[Progress] "Re-encrypting databaseβ¦"
β
[Success] "β Passphrase changed successfully"
More Menu β "π Database Encryption" β "Disable Encryption"
β
[Warning Dialog] "β οΈ WARNING: This will decrypt your database!"
ββ "Yes, Disable" β [Progress] "Decryptingβ¦"
β β
β [Success] "Database decrypted"
ββ "Cancel" β No change
Already added to app/build.gradle.kts:
implementation("net.zetetic:android-database-sqlcipher:4.5.4")
implementation("androidx.sqlite:sqlite:2.4.0")
Add these fields to MainActivity.java:
private EncryptionManager encryptionManager;
private DatabaseEncryptionHelper encryptedDbHelper;
private boolean isDatabaseEncrypted = false;
In onCreate() after super.onCreate(savedInstanceState):
// Initialize encryption manager
encryptionManager = new EncryptionManager(this);
// Check if encryption is enabled
if (encryptionManager.isEncryptionEnabled()) {
isDatabaseEncrypted = true;
// Check if passphrase is cached
if (!encryptionManager.isPassphraseCached()) {
// Show unlock dialog
showUnlockDialog();
} else {
// Open encrypted database
initializeEncryptedDatabase();
}
} else {
// First launch - offer encryption setup
if (!encryptionManager.isPassphraseSet()) {
showFirstLaunchEncryptionDialog();
}
// Use standard database
DatabaseHelper dbHelper = new DatabaseHelper(this);
database = dbHelper.getWritableDatabase();
}
Add these methods to MainActivity:
// Show first-launch encryption prompt
private void showFirstLaunchEncryptionDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.first_launch_encryption_title);
builder.setMessage(R.string.first_launch_encryption_message);
builder.setCancelable(false);
builder.setPositiveButton(R.string.enable_now, (dialog, which) -> {
showSetupEncryptionDialog();
});
builder.setNegativeButton(R.string.maybe_later, (dialog, which) -> {
// Continue with unencrypted database
});
builder.show();
}
// Show encryption setup dialog
private void showSetupEncryptionDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.setup_encryption_title);
builder.setMessage(R.string.setup_encryption_message);
// Create custom layout for passphrase input
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(50, 40, 50, 10);
final EditText passphraseInput = new EditText(this);
passphraseInput.setHint(R.string.passphrase_hint);
passphraseInput.setInputType(android.text.InputType.TYPE_CLASS_TEXT |
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
layout.addView(passphraseInput);
final EditText confirmInput = new EditText(this);
confirmInput.setHint(R.string.confirm_passphrase_hint);
confirmInput.setInputType(android.text.InputType.TYPE_CLASS_TEXT |
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
layout.addView(confirmInput);
CheckBox showPassphraseBox = new CheckBox(this);
showPassphraseBox.setText(R.string.show_passphrase);
showPassphraseBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
int inputType = isChecked ?
android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD :
android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
passphraseInput.setInputType(inputType);
confirmInput.setInputType(inputType);
});
layout.addView(showPassphraseBox);
builder.setView(layout);
builder.setPositiveButton(R.string.set_passphrase, (dialog, which) -> {
String passphrase = passphraseInput.getText().toString();
String confirm = confirmInput.getText().toString();
if (passphrase.length() < 6) {
Toast.makeText(this, R.string.passphrase_too_short, Toast.LENGTH_LONG).show();
showSetupEncryptionDialog(); // Show again
return;
}
if (!passphrase.equals(confirm)) {
Toast.makeText(this, R.string.passphrases_dont_match, Toast.LENGTH_LONG).show();
showSetupEncryptionDialog(); // Show again
return;
}
// Set up encryption
setupEncryption(passphrase.toCharArray());
});
builder.setNegativeButton(R.string.cancel, null);
builder.show();
}
// Set up encryption with passphrase
private void setupEncryption(char[] passphrase) {
new android.os.AsyncTask<Void, Void, Boolean>() {
android.app.ProgressDialog progressDialog;
@Override
protected void onPreExecute() {
progressDialog = android.app.ProgressDialog.show(
MainActivity.this,
getString(R.string.migration_title),
getString(R.string.migration_in_progress),
true
);
}
@Override
protected Boolean doInBackground(Void... params) {
try {
// Setup encryption manager
if (!encryptionManager.setupEncryption(passphrase)) {
return false;
}
// Get database paths
String unencryptedPath = getDatabasePath("wifi_scanner.db").getAbsolutePath();
String encryptedPath = getDatabasePath("wifi_scanner_encrypted.db").getAbsolutePath();
// Get encryption key
String dbKey = encryptionManager.getCachedDatabaseKey();
// Migrate database
boolean success = DatabaseEncryptionHelper.migrateToEncrypted(
MainActivity.this, unencryptedPath, encryptedPath, dbKey);
if (success) {
// Delete unencrypted database
new File(unencryptedPath).delete();
// Rename encrypted database
new File(encryptedPath).renameTo(new File(unencryptedPath));
}
return success;
} catch (Exception e) {
Log.e("MainActivity", "Encryption setup error", e);
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
progressDialog.dismiss();
if (success) {
Toast.makeText(MainActivity.this, R.string.migration_success,
Toast.LENGTH_LONG).show();
isDatabaseEncrypted = true;
initializeEncryptedDatabase();
} else {
Toast.makeText(MainActivity.this, R.string.migration_failed,
Toast.LENGTH_LONG).show();
encryptionManager.disableEncryption();
}
}
}.execute();
}
// Initialize encrypted database
private void initializeEncryptedDatabase() {
String dbKey = encryptionManager.getCachedDatabaseKey();
if (dbKey != null) {
encryptedDbHelper = new DatabaseEncryptionHelper(this, dbKey);
database = encryptedDbHelper.openEncryptedDatabase();
}
}
// Show unlock dialog
private void showUnlockDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.unlock_database_title);
builder.setMessage(R.string.unlock_database_message);
builder.setCancelable(false);
final EditText input = new EditText(this);
input.setHint(R.string.passphrase_hint);
input.setInputType(android.text.InputType.TYPE_CLASS_TEXT |
android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
LinearLayout layout = new LinearLayout(this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(50, 40, 50, 10);
layout.addView(input);
builder.setView(layout);
builder.setPositiveButton(R.string.unlock, (dialog, which) -> {
String passphrase = input.getText().toString();
if (encryptionManager.unlockWithPassphrase(passphrase.toCharArray())) {
initializeEncryptedDatabase();
Toast.makeText(this, "β Database unlocked", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, R.string.wrong_passphrase, Toast.LENGTH_LONG).show();
finish(); // Close app on wrong passphrase
}
});
builder.show();
}
In showMoreDialog(), add:
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, options);
options.add(getString(R.string.encryption_settings));
// In the onItemClick handler:
if (option.equals(getString(R.string.encryption_settings))) {
showEncryptionSettingsDialog();
}
Add encryption settings dialog:
private void showEncryptionSettingsDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.encryption_settings);
String status = encryptionManager.isEncryptionEnabled() ?
"π Encrypted" : "π Not Encrypted";
String[] options = encryptionManager.isEncryptionEnabled() ?
new String[]{"Change Passphrase", "Disable Encryption"} :
new String[]{"Enable Encryption"};
builder.setMessage("Status: " + status + "\n\nOptions:");
builder.setItems(options, (dialog, which) -> {
if (encryptionManager.isEncryptionEnabled()) {
if (which == 0) {
showChangePassphraseDialog();
} else {
showDisableEncryptionDialog();
}
} else {
showSetupEncryptionDialog();
}
});
builder.setNegativeButton(R.string.cancel, null);
builder.show();
}
Add to clear passphrase from memory:
@Override
protected void onPause() {
super.onPause();
if (encryptionManager != null) {
encryptionManager.clearPassphrase();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (encryptionManager != null) {
encryptionManager.clearPassphrase();
}
if (encryptedDbHelper != null) {
encryptedDbHelper.clearPassphrase();
}
super.onDestroy();
}
Modify ScanService.java to support encrypted databases:
private EncryptionManager encryptionManager;
@Override
public void onCreate() {
super.onCreate();
encryptionManager = new EncryptionManager(this);
}
private void initializeDatabase() {
try {
if (database != null) {
database.close();
}
if (encryptionManager.isEncryptionEnabled()) {
// Use encrypted database
String dbKey = encryptionManager.getCachedDatabaseKey();
if (dbKey != null) {
DatabaseEncryptionHelper helper = new DatabaseEncryptionHelper(this, dbKey);
database = helper.openEncryptedDatabase();
Log.d("ScanService", "Using encrypted database");
}
} else if (databasePath != null && !databasePath.isEmpty()) {
// Use external database
database = SQLiteDatabase.openDatabase(databasePath, null, SQLiteDatabase.OPEN_READWRITE);
Log.d("ScanService", "Using external database: " + databasePath);
} else {
// Use internal database
MainActivity.DatabaseHelper dbHelper = new MainActivity.DatabaseHelper(this);
database = dbHelper.getWritableDatabase();
Log.d("ScanService", "Using internal database");
}
} catch (Exception e) {
Log.e("ScanService", "Error initializing database: " + e.getMessage());
}
}
β
Physical device theft/loss
β
File system access (rooted devices)
β
Malicious apps reading database files
β
Cloud backup snooping (if enabled)
β Malware running with app permissions
β Screen recording/keyloggers capturing passphrase
β Advanced forensics (if passphrase is weak)
β User sharing passphrase
| Component | Status | Notes |
|---|---|---|
| SQLCipher Dependency | β Complete | Added to build.gradle.kts |
| EncryptionManager | β Complete | Passphrase management ready |
| DatabaseEncryptionHelper | β Complete | Migration & encryption ready |
| String Resources | β Complete | 90+ strings added |
| MainActivity Integration | β οΈ Pending | Dialogs need implementation |
| ScanService Integration | β οΈ Pending | Encryption support needed |
| Import/Export Updates | β οΈ Pending | Encrypted DB handling |
| Testing | β οΈ Pending | Full test suite needed |
| Documentation | β Complete | This file |
Next Steps:
Last Updated: 2025-11-01
Version: 1.0 (Initial Implementation)