5

I'm using SQLCipher on Android to get an encrypted database. The DB gets an default password which is based on some hardcoded value. The user can also set a pincode on the app. When a user does that I also want change the DB password being used by SQLCipher. I already found a few post on StackerOverflow said that I should use rekey.

Currently I'm using this code like the posts suggested

final DatabaseHelper helper = new DatabaseHelper(this);
final SQLiteDatabase db = helper.getWritableDatabase(oldPassword);
final String PRAGMA_KEY = String.format("PRAGMA key = \"%s\";", oldPassword);
final String PRAGMA_REKEY = String.format("PRAGMA rekey = \"%s\";", newPassword);
db.rawExecSQL("BEGIN IMMEDIATE TRANSACTION;");
db.rawExecSQL(PRAGMA_KEY);
db.rawExecSQL(PRAGMA_REKEY);
db.close();

But when I try to insert into the DB after the password should have changed I get this error.

sqlite returned: error code = 26, msg = statement aborts at 1: [BEGIN EXCLUSIVE;] file is encrypted or is not a database
Failure 26 (file is encrypted or is not a database) on 0xb90048c0 when executing 'BEGIN EXCLUSIVE;'
FATAL EXCEPTION: IntentService[DBService]
    Process: com.example, PID: 26502
    net.sqlcipher.database.SQLiteException: file is encrypted or is not a database: BEGIN EXCLUSIVE;
            at net.sqlcipher.database.SQLiteDatabase.native_execSQL(Native Method)
            at net.sqlcipher.database.SQLiteDatabase.execSQL(SQLiteDatabase.java:1831)
            at net.sqlcipher.database.SQLiteDatabase.beginTransactionWithListener(SQLiteDatabase.java:584)
            at net.sqlcipher.database.SQLiteDatabase.beginTransaction(SQLiteDatabase.java:538)
            at com.example.db.Provider.bulkInsert(OurProvider.java:196)
            at android.content.ContentProvider$Transport.bulkInsert(ContentProvider.java:250)
            at android.content.ContentResolver.bulkInsert(ContentResolver.java:1268)
            at nl.qbusict.cupboard.ProviderCompartment.put(ProviderCompartment.java:158)
            at com.example.db.DBUtils.saveObjects(DBUtils.java:32)
            at com.example.services.DBService.getDataFromAPI(DBService.java:119)
            at com.example.services.DBService.onHandleIntent(DBService.java:48)
            at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.os.HandlerThread.run(HandlerThread.java:61)

I checked both the original password and the new password before and after the change they where all the same. I also tried adding db.rawExecSQL("END;"); and db.rawExecSQL("COMMIT;"); but that didn't help. Also I see a log msg come by error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt when I change the password. Don't know if that has anything to do with it?

public class OurProvider extends ContentProvider {
    @Override
    public int bulkInsert(Uri uri, ContentValues[] values) {
        synchronized (LOCK) {
            SQLiteDatabase db = getDatabase().getWritableDatabase(getPassword());
            final String table = getTableString(uri);

            db.beginTransaction();
            int rowsInserted = 0;
            try {
                for (ContentValues value : values) {
                    db.insertWithOnConflict(table, null, value, SQLiteDatabase.CONFLICT_REPLACE);
                    rowsInserted++;
                }
                db.setTransactionSuccessful();
            } catch (Exception e) {
                Crashlytics.logException(e);
                Log.d(TAG, Log.getStackTraceString(e));
                rowsInserted = -1;
            } finally {
                db.endTransaction();
                if (rowsInserted > 0) {
                    getContext().getContentResolver().notifyChange(uri, null);
                }

            }
            return rowsInserted;
        }
    }

    private String getPassword() {
        final String password = Base64.encodeToString(OurApplication.getEncryptionKey().getEncoded(), Base64.DEFAULT);
        Log.e("SQLCipher_OurProvider", "SQLCipher password: " + password);
        return password;
    }

    private void initDb() {
        SQLiteDatabase.loadLibs(getContext());

        mDatabaseHelper = new DatabaseHelper(getContext());
    }

    public DatabaseHelper getDatabase() {
        if (mDatabaseHelper == null) {
            initDb();
        }
        return mDatabaseHelper;
    }
}



public class DBService extends IntentService {
    private void updatePasscode(Intent intent) {
        final SecretKey oldKey = OurApplication.getEncryptionKey();
        final String encryptedString = SecurePrefs.getEncryptedString(PrefKeys.ENC_CONFIRM);
        if (!TextUtils.isEmpty(encryptedString)) {
            String decryptedString = Crypto.decrypt(oldKey, encryptedString);
            // check if the oldkey can decrypt the confirmation string.
            if (BaseActivity.CONFIRM_STRING.equals(decryptedString)) {
                String pin = intent.getStringExtra(KEY_PASSCODE);
                if (pin.equals(PasscodeActivity.REMOVE_PIN)) {
                    pin = null;
                }
                final SecretKey newKey = SecurityUtil.generateKey(this, pin);
                final String accessToken = getUserAccessToken();
                final String refreshToken = SecurePrefs.getString(PrefKeys.USER_REFRESH_TOKEN);
                final String email = SecurePrefs.getString(PrefKeys.USER_ID);
                final String confirmEncrypted = SecurePrefs.getString(PrefKeys.ENC_CONFIRM);
                // set the newly generated string in the application.
                OurApplication.setEncryptionKey(newKey);

                // clear the old encrypted prefs. save the values with the new encryption key.
                SecurePrefs.clear();
                SecurePrefs.putString(PrefKeys.USER_ACCESS_TOKEN, accessToken);
                SecurePrefs.putString(PrefKeys.USER_REFRESH_TOKEN, refreshToken);
                SecurePrefs.putString(PrefKeys.USER_ID, email);
                SecurePrefs.putString(PrefKeys.ENC_CONFIRM, confirmEncrypted);

                // update de encryption key in the database.
                final String oldPassword = Base64
                        .encodeToString(oldKey.getEncoded(), Base64.DEFAULT);
                final String newPassword = Base64
                        .encodeToString(newKey.getEncoded(), Base64.DEFAULT);

                final String PRAGMA_KEY = String.format("PRAGMA key = \"%s\";", oldPassword);
                final String PRAGMA_REKEY = String.format("PRAGMA rekey = \"%s\";", newPassword);

                final DatabaseHelper helper = new DatabaseHelper(this);
                final SQLiteDatabase db = helper.getWritableDatabase(oldPassword);


                db.rawExecSQL("BEGIN IMMEDIATE TRANSACTION;");
                db.rawExecSQL(PRAGMA_KEY);
                db.rawExecSQL(PRAGMA_REKEY);
                db.close();
                sendBroadcast(IntentUtil.createBroadcastPasscodeUpdated());
            }
        }
    }
}
4
  • possible duplicate of file is encrypted or is not a database (Exception net.sqlcipher.database.SQLiteException) Commented Dec 10, 2014 at 11:39
  • @maveňツ the database always has a password. It uses a hardcode one for when hasn't set a pincode. Commented Dec 10, 2014 at 11:42
  • error itself indicates That File is not a database file. pls share complete code. Commented Dec 10, 2014 at 11:46
  • @maveňツ okay added some extra code Commented Dec 10, 2014 at 14:07

2 Answers 2

4

I also had this problem and the reason turned out to be that the DB wasn't encrypted at the first place, so then PRAGMA key = <new_key> or PRAGMA rekey = <new_key> failed. So, the oldPassword CAN'T be null or empty.

Sign up to request clarification or add additional context in comments.

2 Comments

Hey, i have the same problem when try to change the password into a database without password, what was your solution ?
It seems to me, stackoverflow.com/questions/64229489/… is something close to this topic. I encrypted db file with DB Browser for SQLite, put it in assets/database folder. BUT. I got: "java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time." This post discuss.zetetic.net/t/pragma-rekey-performance/4049 informs that "...rekey operations are expected to be fairly time consuming...". I think, rekey operation is not a choice for Android app.
2

You should not begin a transaction (this is handled internally and slightly different), nor do you need to perform the PRAGMA key='…'; portion of your code either. You can should just open the database with your call to getWritableDatabase(…); and then execute your PRAGMA rekey='…'; command.

8 Comments

I removed PRAGMA key='…'; but it still doesn't work properly so I just simply get all the data from the DB and delete it then create a new one with the new key.
We have an example of rekeying a database within the SQLCipher for Android test suite here.
Hi Nick I tried this but it didn't work sqlite returned: error code = 26, msg = file is encrypted or is not a database CREATE TABLE android_metadata failed Failed to setLocale() when constructing, closing the database net.sqlcipher.database.SQLiteException: file is encrypted or is not a database
Did you run the test suite?
For anyone hitting this page I believe this is the new link HERE
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.