Search code examples
iosobjective-cencryptionfmdb

iOS sqlcipher fmdb inTransaction “File is encrypted or is not a database”


When I encrypt my database using sqlcipher, and call inDatabase in FMDatabaseQueue——success!

But When I change inDatabase to inTransaction, the console says "File is encrypted or is not a database".

The code:

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:st_dbPath];

// success
[queue inDatabase:^(FMDatabase *db) {

    [db setKey:st_dbKey];
    [db executeUpdate:@"INSERT INTO t_user VALUES (16)"];
}];

// fail : File is encrypted or is not a database
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {

    [db setKey:st_dbKey];
    [db executeUpdate:@"INSERT INTO t_user VALUES (17)"];
}];

And the encrypt database code:

NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
NSString *ecDB = [documentDir stringByAppendingPathComponent:st_dbEncryptedName];

// SQL Query. NOTE THAT DATABASE IS THE FULL PATH NOT ONLY THE NAME
const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY '%@';", ecDB, st_dbKey] UTF8String];

sqlite3 *unencrypted_DB;
if (sqlite3_open([st_dbPath UTF8String], &unencrypted_DB) == SQLITE_OK) {

    // Attach empty encrypted database to unencrypted database
    sqlite3_exec(unencrypted_DB, sqlQ, NULL, NULL, NULL);

    // export database
    sqlite3_exec(unencrypted_DB, "SELECT sqlcipher_export('encrypted');", NULL, NULL, NULL);

    // Detach encrypted database
    sqlite3_exec(unencrypted_DB, "DETACH DATABASE encrypted;", NULL, NULL, NULL);

    sqlite3_close(unencrypted_DB);
}
else {
    sqlite3_close(unencrypted_DB);
    NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
}

the encrypt code come from there:http://www.guilmo.com/fmdb-with-sqlcipher-tutorial/


Solution

  • Calling inTransaction causes the SQL statement begin exclusive transaction to be executed on your database before calling your completion block. Therefore that SQL is executed before you have a chance to call setKey.

    You could instead use inDatabase and call beginTransaction on the FBDatabase instance that is passed back like this:

    [self.queue inDatabase:^(FMDatabase *db) {
    
        [db setKey:st_dbKey];
        [db beginTransaction];
    
        [db executeUpdate:@"INSERT INTO t_user VALUES (17)"];
    
        [db commit];
    }];