Is there a correct way to test whether a user-provided SQLCipher password is valid on Android?
Asked Answered
P

3

8

I'm using SQLCipher for Android and am trying to determine the correct way to test whether a user-provided password is valid.

My first inclination was to just try and open the database with the given password, using the SQLCipher implementation of SQLiteOpenHelper.getReadableDatabase(password), and then catch the SQLiteException that pops out.

This does work, but the problem is that because the Android API actually wraps the underlying C calls, it does a lot of the work for you - specifically, when you open a database using the Android API, it opens the database, runs the native C-level sqlite3_key method (with the provided password), then tries to set the locale on the database, whether or not the provided password was correct.

At this point, the Android library tries to set the locale, and the underlying database throws the "encrypted or not a database" SQLiteException, which is caught and rethrown; but before it is, an unrelated error is written to the logs, essentially saying the locale cannot be set and the database is being closed (with an included stack trace). Because this is specifically written by the Android library, I cannot suppress it, leaving an ugly error in the logs that is actually unrelated to my original problem, which was simply that I passed in the wrong password.

Because the Android library does not expose the C-level calls, I can't just use the method described in the SQLCipher API documentation regarding Testing the Key, because I don't have access to directly open the database.

I lean towards using the SQLiteDatabaseHook, but as best I can tell, that precludes my use of SQLiteOpenHelper, which does not appear to provide a way to set up a hook.

Does anyone else know any better way to test whether an input passphrase properly decrypts a SQLCipher database through the SQLCipher Android API? I would completely expect to call a method and check for an exception being thrown - what I don't want is for the operation to try and perform extraneous processing (like set locale) on the database and write a completely insuppressible error to my logs.

Paratrooper answered 14/5, 2013 at 21:28 Comment(4)
"...that is actually unrelated to my original problem, which was simply that I passed in the wrong password" -- no, that is the original problem: you passed in the wrong password, which is why the set-locale operation failed. "I can't just use the method described in the SQLCipher API documentation regarding Testing the Key..." -- your results would be identical to what you are presently getting, except maybe without any extraneous traces in LogCat. Beyond that, what is your criteria for "any better way"?Robena
I'm sorry I apparently didn't make myself clear. All I want to do is check whether or not I passed in the wrong password. I totally agree that my results would be the same (make the call, check for an error). My criteria for "any better way" is WITHOUT the extraneous setLocale processing and error.Paratrooper
I'd just file an issue to get them to not log the error and not worry about it. Beyond that, I recommend the sqlcipher Google Group for more assistance.Robena
Looks like you asked basically the same question here: github.com/sqlcipher/android-database-sqlcipher/issues/76 and didn't get an answer you were happy with either.Paratrooper
S
2

I don't have a better way of testing it. I just want to provide some code in addition, so when other people look this up, they may find some useful code snippets. At least when I found this question, I would have loved to see some code how do to the check-up :)

The way I do it in android is this:

    // Simply get an instance of SQLiteOpenHelper.
    dbHelperObj = myDatabase.getInstance(this, str_username, version);

    // Now we try to open the database with the password from the user.
    try {
        dbObj = dbHelperObj.getReadableDatabase(str_password);

    // The only possible error now must be a wrong password.
    } catch (Exception e) {
        dbHelperObj.close();

        // Do stuff to tell the user he provided a wrong password.
    }
Setter answered 22/8, 2014 at 11:40 Comment(1)
As there are currently no better solutions around, this is considered a working solution. Just to all who want to use this, it would be more precise if you catch SQLiteExceptions coming from import net.sqlcipher.database.SQLiteException.Christa
E
1

SQLCipher for Android does not know that the password you have provided is invalid following a sqlite3_key call, as the database key isn't used until a SQL command is issued against the database following the sqlite3_key, such as the setLocale(...) method you reference above. The problem is, providing an invalid key may be only one of possible other scenarios that could be the problem at the point the first SQL statement is executed. A corrupt data file, a failed HMAC check, or opening a non database file could result in this same error message. For a detailed description of this please review this thread. It would be best to catch the exception when attempting to open the database and handle accordingly within the client application.

Extraneous answered 15/5, 2013 at 15:32 Comment(1)
I definitely get all that. The problem I have is that there's no way for me to replicate "Testing the Key" as in my question. With the iOS API I have access to the C API, so I can so I can open the database, pragma key, and then try to read from sqlite_master. If that throws an error, I can catch that and call it a day. Through the Android API, however, I cannot do that so simply, because I must call openOrCreateDatabase which opens the db, keys it, then tries to set locale, which is where the error happens. That's not so bad, but the insuppressible log output is frustrating.Paratrooper
C
0

Currently, the only way to validate passphrase is to do an operation on your database and check if it throws a net.sqlcipher.database.SQLiteException. So for those who are using SQLCipher with Room ORM, the following code should do the job.

After getting an instance of your Room database using the provided passphrase, call the isPassphraseValid() function and pass your database instance to it.

    /* Passphrase validation: */
    private fun isPassphraseValid(db: MyRoomDatabase): Boolean {
        return try {
            db.openHelper.readableDatabase
            Log.d("sql-log", "Pass phrase is valid.")
            true
        } catch (e: net.sqlcipher.database.SQLiteException) {
            e.printStackTrace()
            Log.d("sql-log", "Pass phrase is not valid.")
            false
        }
    }
Christa answered 29/2, 2020 at 9:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.