4

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/

saitjr
  • 159
  • 1
  • 9

2 Answers2

3

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];
}];
1

Gus Hovland's answer works but I think what works better is to change inTransaction: to inDeferredTransaction:. From FMDB's documentation for inTransaction:

" Unlike SQLite's BEGIN TRANSACTION, this method currently performs an exclusive transaction, not a deferred transaction. This behavior is likely to change in future versions of FMDB, whereby this method will likely eventually adopt standard SQLite behavior and perform deferred transactions. If you really need exclusive tranaction, it is recommended that you use inExclusiveTransaction, instead, not only to make your intent explicit, but also to future-proof your code."

charshep
  • 416
  • 4
  • 14