πŸš€ Project Website & GitHub Repo

πŸ” Database Encryption Feature - Implementation Guide

Overview

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 Summary

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

πŸ—οΈ Architecture

Core Components

1. EncryptionManager (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()

2. DatabaseEncryptionHelper (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()

3. String Resources (res/values/strings.xml)

All UI strings for encryption dialogs, messages, and indicators (90+ strings added).

πŸ”’ Security Design

Encryption Stack

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  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)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Security Features

  1. No Plaintext Storage
    • User passphrase never stored directly
    • Only encrypted + hashed versions stored
    • In-memory cache cleared on app close
  2. Android Keystore Integration
    • Hardware-backed encryption (if available)
    • Keys never leave secure environment
    • Protected against extraction
  3. Passphrase Derivation
    • SHA-256 with 32-byte random salt
    • Separate hash for verification
    • 64-character hex key for SQLCipher
  4. Memory Security
    • Passphrase stored as char[] (not String)
    • Explicit zeroing after use
    • Cleared on onPause() / onDestroy()
  5. SQLCipher Protection
    • AES-256-CBC encryption
    • HMAC-SHA512 for integrity
    • 256,000 PBKDF2 iterations
    • Encrypted database header

πŸ“± User Experience Flow

First Launch / Setup Encryption

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

Unlock Database (App Launch)

App Launch (with encrypted DB)
    ↓
[Dialog] "Unlock Database"
[Input] Enter passphrase
[Button] "Unlock"
    ↓
    β”œβ”€ Correct β†’ Database unlocked (cached in memory)
    └─ Wrong β†’ "❌ Wrong passphrase. Access denied."

Change Passphrase

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"

Disable Encryption

More Menu β†’ "πŸ” Database Encryption" β†’ "Disable Encryption"
    ↓
[Warning Dialog] "⚠️ WARNING: This will decrypt your database!"
    β”œβ”€ "Yes, Disable" β†’ [Progress] "Decrypting…"
    β”‚                      ↓
    β”‚                   [Success] "Database decrypted"
    └─ "Cancel" β†’ No change

πŸ”§ Integration Guide

Step 1: Add Dependencies (βœ… Done)

Already added to app/build.gradle.kts:

implementation("net.zetetic:android-database-sqlcipher:4.5.4")
implementation("androidx.sqlite:sqlite:2.4.0")

Step 2: Modify MainActivity

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();
}

Step 3: Add Dialog Methods

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();
}

Step 4: Add Menu Items

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();
}

Step 5: Update onPause() and onDestroy()

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();
}

Step 6: Update ScanService

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());
    }
}

πŸ§ͺ Testing Checklist

Basic Functionality

Unlock/Lock

Change Passphrase

Disable Encryption

Import/Export

Edge Cases

πŸ“Š Performance Impact

Database Operations

Memory Usage

Battery Impact

πŸ” Troubleshooting

β€œWrong passphrase” Error

Migration Fails

Database Corruption

Slow Performance

πŸ›‘οΈ Security Considerations

What This Protects Against

βœ… Physical device theft/loss
βœ… File system access (rooted devices)
βœ… Malicious apps reading database files
βœ… Cloud backup snooping (if enabled)

What This Does NOT Protect Against

❌ Malware running with app permissions
❌ Screen recording/keyloggers capturing passphrase
❌ Advanced forensics (if passphrase is weak)
❌ User sharing passphrase

Best Practices

πŸš€ Future Enhancements

Planned Features

  1. Biometric Unlock (Fingerprint/Face)
    • Unlock database with biometrics
    • Fallback to passphrase
  2. Auto-Lock Timer
    • Lock database after X minutes of inactivity
    • Configurable timeout
  3. Failed Attempt Protection
    • Lock after N wrong attempts
    • Optional: Wipe data after 10 failed attempts
  4. Passphrase Backup
    • Export encrypted QR code with recovery key
    • Secure cloud backup option
  5. Multi-Device Sync
    • Sync encrypted database across devices
    • End-to-end encryption

πŸ“š References

βœ… Implementation Status

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:

  1. Implement dialogs and UI integration in MainActivity
  2. Update ScanService for encrypted database support
  3. Modify import/export functions for encrypted databases
  4. Add encryption status indicators to UI
  5. Comprehensive testing
  6. User documentation update

Last Updated: 2025-11-01
Version: 1.0 (Initial Implementation)