The code below shows how you can get a current SQLite database and encrypt using fmdb + sqlcipher. You can use a database with empty tables, an empty database and even a working database with table and data already stored in them. Please try the solution below and tell me in the comments if it worked for you or if you had any problems with it.
First thing you’ll need and this is very important, is to use Cocoapods to load the correct libraries and configuration. By using Cocoapods, it will install FMDB + SQLCipher in seconds for you.
If you don’t know what Cocoapods is (???), I’ve been meaning to write a simple tutorial, but while that does not happen, check this link
Open your Podfile and add the following line
pod 'FMDB/SQLCipher'
If you already had FMDB in your Podfile, remove the line. This pod already have the FMDB dependency and it will install FMDB automatically
Now run pod update (or pod install if first time installing the pods for your project) in the terminal to install the new pods.
After the pods are installed, open your .xcworkspace
If you already know how to do this or are already doing, you can skip this part as this does not change from the default behavior
In my game, I already had a default database with all the tables that I needed inside my project Resources folder, once I run the app I check to see if the database is in the Documents folder, if it’s not, I copy the file from the Resources folder to the Documents folder.
In the AppDelegate I have a NSString property called databasePath which I use throughout the application and in application:didFinishLaunchingWithOptions method I set this property with my default database path by doing:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentDir = [documentPaths objectAtIndex:0]; self.databasePath = [documentDir stringByAppendingPathComponent:@"gameDefault.sqlite"]; [self createAndCheckDatabase]; ... }
You’ll notice that I also call a separate method createAndCheckDatabase which actually do the verification I told above and create a copy of the database file in case it does not exist in the Documents folder.
-(void) createAndCheckDatabase { BOOL success; NSFileManager *fileManager = [NSFileManager defaultManager]; success = [fileManager fileExistsAtPath:self.databasePath]; if(success) return; // If file exists, don't do anything // if file does not exist, make a copy of the one in the Resources folder NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"gameDefault.sqlite"]; // File path [fileManager copyItemAtPath:databasePathFromApp toPath:self.databasePath error:nil]; // Make a copy of the file in the Documents folder }
After you’ve done this, you’ll have your database in the Documents folder where it will be accessible to the application to read/write.
So, in order for you to have an encrypted database, you’ll need to create a new blank encrypted database and then ATTACH you existing database to it. By attaching, you will have everything that you had in your unencrypted database in the new encrypted one.
Let’s start by importing SQLite to your AppDelegate.m file. At the top of you file add
#import <sqlite3.h>
Now inside of the createAndCheckDatabase method, add the following below what we already have
// Set the new encrypted database path to be in the Documents Folder NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentDir = [documentPaths objectAtIndex:0]; NSString *ecDB = [documentDir stringByAppendingPathComponent:@"encrypted.sqlite"]; // SQL Query. NOTE THAT DATABASE IS THE FULL PATH NOT ONLY THE NAME const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY 'secretKey';",ecDB] UTF8String]; sqlite3 *unencrypted_DB; if (sqlite3_open([self.databasePath 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)); } self.databasePath = [documentDir stringByAppendingPathComponent:@"encrypted.sqlite"];
Breaking down this code
This part is now complete and you have your encrypted database. If you go to your Application Documents folder you will now see 2 files, gameDefault.sqlite and encrypted.sqlite.
Keep both there, but make sure you are using the encrypted.sqlite one.
Now comes the easy part. You should already have all your methods to INSERT, UPDATE, CREATE, etc… in your code, so you should need just to add a single line of code after every time you open the database.
[db setKey:@"secretKey"]
.
I’m going to show you 2 examples, one using FMDatabase and another usingFMDatabaseQueue. In the FMDatabase function, you alway call a [db open] method to open your db, and in FMDatabaseQueue you don necessarily open the database directly, you call a queue method that opens the db for you.
If you try to open your encrypted db it’s not going to work because you need your secret key. so add the setKey method after you open your db
// FMDatabase FMDatabase *db = [FMDatabase databaseWithPath:[self getDatabasePath]]; [db open]; [db setKey:@"secretKey"]; // FMDatabaseQueue FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:[self getDatabasePath]]; [queue inDatabase:^(FMDatabase *db) { [db setKey:@"secretKey"]; ... }];
And you’re done! that should convert your existing DB into an encrypted db.
FYI: I tried opening my encrypted database in several sqlite browser programs and none could open because it was encrypted, but the code is able to insert, update, delete, etc… to your db just fine.
So if you’re still in development and need frequent access to your DB for debugging purposes, leave the encryption part to the end.
I really want to know if you’re able to use this and if you have/had any problems, so please drop a comment below once you try it out!
Thanks
.gm.