How to protect my SQLite db by intentionally corrupting it, then fix it through code?

274 views Asked by At

This is my first app on Android with Java and SQLite.

ISSUE:
I have a local SQLIte db on my app. I was very surprised to see how easy it is to get access to the db once you have installed the app (no need to be a programmer nor a hacker).

I tried adding SQLCipher to my app but it only worked for newer Android versions 11 & 12 and didn't work for Android 9 for example and it did make my app's size much bigger.

After researching more I found a better solution for my case which doesn"t involve crypting the db with SQLCipher but rather it consists of corrupting the first bytes of the db file then after each launch of the app the code will decorrupt the file and use the fixed file instead. This insures that anyone who decompiles the apk will only get access to a corrupt db file and will have to put more effort to fix it which is my goal.

I came across this solution in a reply [here][1] but I don't know how to implement it as I am new to Android and SQLite programming. Any help is much appreciated on how to actually do it.

These are the steps as mentioned by the user: farhad.kargaran which need more explanation as I don't get how to do it:
1- corrupt the db file (convert it to byte array and change some values)
2- copy it in asset folder
3- in first run fix corrupted file from asset and copy it in database
folder.
Change first 200 byte values like this:

    int index = 0;
    for(int i=0;i<100;i++)
    {
        byte tmp = b[index];
        b[index] = b[index + 1];
        b[index + 1] = tmp;
        index += 2;
    }

As only the first 200 bytes were replaced, the same code is used for fixing first 200 byte values.

Here is my code for the SQLiteOpenHelper if needed:

   

public class DatabaseHelper extends SQLiteOpenHelper {

    private static final String TAG = DatabaseHelper.class.getSimpleName();

    public static String DB_PATH;
    public static String DB_NAME;

    public SQLiteDatabase database;
    public final Context context;

    public SQLiteDatabase getDb() {
        return database;
    }

    public DatabaseHelper(Context context, String databaseName, int db_version) {
        super(context, databaseName, null, db_version);
        this.context = context;

        DB_PATH = getReadableDatabase().getPath();
        DB_NAME = databaseName;

        openDataBase();

        // prepare if need to upgrade
        int cur_version = database.getVersion();
        if (cur_version == 0) database.setVersion(1);
        Log.d(TAG, "DB version : " + db_version);
        if (cur_version < db_version) {
            try {
                copyDataBase();
                Log.d(TAG, "Upgrade DB from v." + cur_version + " to v." + db_version);
                database.setVersion(db_version);
            } catch (IOException e) {
                Log.d(TAG, "Upgrade error");
                throw new Error("Error upgrade database!");
            }
        }
    }

    public void createDataBase() {
        boolean dbExist = checkDataBase();
        if (!dbExist) {
            this.getReadableDatabase();
            this.close();
            try {
                copyDataBase();
            } catch (IOException e) {
                Log.e(TAG, "Copying error");
                throw new Error("Error copying database!");
            }
        } else {
            Log.i(this.getClass().toString(), "Database already exists");
        }
    }

    private boolean checkDataBase() {
        SQLiteDatabase checkDb = null;
        try {
            String path = DB_PATH + DB_NAME;
            checkDb = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLException e) {
            Log.e(TAG, "Error while checking db");
        }

        if (checkDb != null) {
            checkDb.close();
        }
        return checkDb != null;
    }

    private void copyDataBase() throws IOException {
        InputStream externalDbStream = context.getAssets().open(DB_NAME);
        String outFileName = DB_PATH + DB_NAME;

        OutputStream localDbStream = new FileOutputStream(outFileName);

        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = externalDbStream.read(buffer)) > 0) {
            localDbStream.write(buffer, 0, bytesRead);
        }

        localDbStream.close();
        externalDbStream.close();

    }

    public SQLiteDatabase openDataBase() throws SQLException {
        String path = DB_PATH + DB_NAME;
        if (database == null) {
            createDataBase();
            database = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE);
        }
        return database;
    }

    @Override
    public synchronized void close() {
        if (database != null) {
            database.close();
        }
        super.close();
    }

Much appreciated.
[1]: https://stackoverflow.com/a/63637685/18684673

1

There are 1 answers

1
MikeT On

As part of the copyDatabase, correct and then write the corrupted data, then copy the rest.

Could be done various ways

e.g.

    long buffersRead = 0;  //<<<<< ADDED for detecting first buffer        
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = externalDbStream.read(buffer)) > 0) {
        if (bufferesRead++ < 1) {
            //correct the first 200 bytes here before writing ....
        }
        localDbStream.write(buffer, 0, bytesRead);
    }