SQLite + SQLCipher + FMDatabase on iOS - ios

I'm currently using FMDatabase in my iOS app and I'm very happy with it.
I plan to encrypt the sqlite database with SQLCipher.
Here are my questions:
1) Are FMDatabase and SQLCipher compatibles ? I think I just need to add a new method in FMDatabase called openEncrypted...and do the job for SQLCipher. I hope all FMDatabase methods will work.
2) Actually, I have 2 databases in my app. Then I do an ATTACH DATABASE in my app to join them. I would like to encrypt just one of the two. Will it work or I need to encrypt the 2 databases ? (One is critical, the other is not)
3) I don't really understand what I will have to provide to Apple (documents) if I encrypt these files.
Thanks you !

For those looking for a simple tutorial on how to accomplish this, I was able to create one: http://www.guilmo.com/fmdb-with-sqlcipher-tutorial/
But the most important parts are, Opening your existing DB and attaching a new encrypted one. Then setting the key in your FMDB connections.
SQLCipher - Encrypting the database
// Import sqlite3.h in your AppDelegate
#import <sqlite3.h>
// 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"];
Note that we set 2 parameters in SQL Query, the DATABASE and the KEY. The DATABASE should be the full path to the encrypted database you want to create, in this case, string ecDB, and the KEY parameter is the key that’s going to be use to ENCRYPT your database, so choose a strong one
Now on your FMDB functions, call [db setKey:#"strongKey"] after every time you open the db.
// FMDatabase Example
FMDatabase *db = [FMDatabase databaseWithPath:[self getDatabasePath]];
[db open];
[db setKey:#"secretKey"];
// FMDatabaseQueue Exmple
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:[self getDatabasePath]];
[queue inDatabase:^(FMDatabase *db) {
[db setKey:#"secretKey"];
...
}];
Let me know if you have any questions!

Yes, You still need to compile in SQLCipher, but you can use FMDB. FMDB also provides a function to set the encryption key for the database
You can attach a non-encrypted database to an encrypted database (see http://sqlcipher.net/sqlcipher-api/#attach)
You will generally need to do an encryption registration with the DOC and then self-classify as mass market http://www.bis.doc.gov/encryption/question4.htm

Hi I am using swift and following is the code I follow. But I have one problem the encrypted.sqlite file I can open with SQLiteBrowser what I was wrong here.
var db: COpaquePointer = nil;
let databasePath = FileUtils.getPath("data.db")
var ecDB = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].stringByAppendingPathComponent("encrypted.sqlite")
let result = String.fromCString("ATTACH DATABASE \(ecDB) AS encrypted KEY TaP")
if (sqlite3_open(databasePath, &db) == SQLITE_OK) {
sqlite3_exec(db, result!, nil, nil, nil);
sqlite3_exec(db, "SELECT sqlcipher_export('encrypted');", nil, nil, nil);
sqlite3_exec(db, "DETACH DATABASE encrypted;", nil, nil, nil);
sqlite3_close(db);
}
else {
sqlite3_close(db);
sqlite3_errmsg(db);
}

Related

How to view the sqlite db in the application installed in iOS for testing

I have installed my application in the simulator and need to view the DB. Please tell me the application to view this or can I view it from Xcode itself.
DATABASE
//database connection
con = [[DataBase alloc]init];
NSError *error = [[NSError alloc]init];
NSFileManager *filemanager = [NSFileManager defaultManager];
NSArray *arryPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *DocumentPath = [arryPath objectAtIndex:0];
NSString *strDocumentPath = [DocumentPath stringByAppendingPathComponent:#"School.sqlite"];
// check file is exist or not
int success = [filemanager fileExistsAtPath:strDocumentPath];
//if file not exist at path
if (!success) {
NSLog(#"SchoolName is: %#",#"No Database exist");
NSString *strDefaultPath = [[[NSBundle mainBundle]resourcePath]stringByAppendingPathComponent:#"School.sqlite"];
success = [filemanager copyItemAtPath:strDefaultPath toPath:strDocumentPath error:&error];
if (!success) {
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
//file exist at path
if (success) {
NSLog(#"SchoolName is: %#",#" Database exist");
if (sqlite3_open([strDocumentPath UTF8String],&database) == SQLITE_OK) {
} else {
sqlite3_close(database);
NSAssert1(0, #"Failed to open database with message '%s'.", sqlite3_errmsg(database));
}
}
Install DB Browser for Sqlite in Mac.
Provide path of .sqlite in documents directory of app to DB browser
It'll open live db from simulator
All entities of Core Data will have ZTablename naming convention
Keep refreshing DB browser for updated data during testing
Try to use the client e.g. from here http://sqlitebrowser.org/ and open sqlite file.
If you installed firefox on your PC, use "SQLite Manager" add-on is quite good, it's easy, lightly, and free. I've used it before to manage my sqlite db with ~40k records with no problem.
https://addons.mozilla.org/en-US/firefox/addon/sqlite-manager/
For live database Changes please try this:
1.Install DBBrowser for SQLite.
2.Log your strDocumentPath in console using NSLog And copy the path.
3.Open DBBrowser and open database and in finder press CMD+SHIFT+G and paste your link there and press ENTER. You will be redirected to that location.
4.Select your SQLite file and it will be opened in DBBrowser. And keep it there until you are done with your DB works.
Now whenever you refresh the database from DBBrowser you will get the recent changes in DB also any manual changes in DB will be effected in your Simulator.
Here is a way I do that
init() {
do {
// Get database path
if let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first {
// Connect to database
db = try Connection("\(path)/db.sqlite3")
// Initialize tables
try db?.run(rssNewspaper.create(ifNotExists: true) { table in
table.column(rssNewspaperId, primaryKey: .autoincrement)
table.column(rssNewspaperName)
table.column(rssIsActive)
})
}
} catch {
print(error.localizedDescription)
}
}
point debuger point in path and when deice is locked there print to path then open terminal and use this command "open (path/of/printed/path)" in between( ) past the printed value it will be open

Reuse core data created .db file in FMDB

I'm using Core Data to manage local DB in my iOS app. But then I have a requirement to insert bulk of records to local DB. So I choose FMDB to perform bulk insertion. But when I tried to insert the record I'm getting this DB Error: 1 "no such table: Table Name.
But when I cross checked, the local DB exist there with all the tables that I created in xcdatamodel.
- (FMDatabase *)dbInMainThread{
if (_dbInMainThread) {
return _dbInMainThread;
}
NSURL *storeURL = [[[CoreDataManager manager] applicationLibraryDirectory] URLByAppendingPathComponent:[DATABASE_NAME stringByAppendingString:#".sqlite"]];
[self checkDB:[storeURL relativePath]];
_dbInMainThread = [FMDatabase databaseWithPath:[storeURL relativePath]];
return _dbInMainThread;
}
-(void) checkDB:(NSString *)dbpath{
BOOL success;
NSFileManager * fm = [NSFileManager defaultManager];
success = [fm fileExistsAtPath:dbpath];
if (success) return;
}
Success bool in the above code snippet is always returning true.
I followed the steps in this SO answer.
Since I am using Core Data, I don't need to add the DB file to the project. I uninstalled the app, resets the simulator and verified the DB file was in the correct folder before starting insertion. But nothing helps.
What am I missing here
Thanks

SQLite query runs on SQLiteManager but not on iOS

Having an sqlite issue. The query runs fine on the firefox addon SQLiteManager. It does not run on iOS however. I get an error code of 1. The database gets connected to fine.
NSString *sqLiteDb = [[NSBundle mainBundle] pathForResource:#"featureDB"
ofType:#"sqlite3"];
if (sqlite3_open([sqLiteDb UTF8String], &_database) != SQLITE_OK) {
DLog(#"Failed to open database!");
} else {
DLog(#"Connected to db");
}
NSString *query = [NSString stringWithFormat:#"SELECT * FROM featureTable"];
sqlite3_stmt *statement;
NSLog(#"could not prepare statement: %s\n", sqlite3_errmsg(_database));
if (sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, nil) == SQLITE_OK) {
while (sqlite3_step(statement) == SQLITE_ROW) {
DLog(#"row");
int uniqueId = sqlite3_column_int(statement, 0);
}
sqlite3_finalize(statement);
DLog(#"sqliteStep");
} else {
DLog(#"statement Error %d", sqlite3_prepare_v2(_database, [query UTF8String], -1, &statement, nil));
}
The log for "row or sqliteStep" never fires.
Your else statement should not call sqlite3_prepare_v2 again, but rather should:
DLog(#"prepare error: %s", sqlite3_errmsg(_database));
What does this error message report?
Note: You already are logging sqlite3_errmsg before you call sqlite3_prepare_v2. Of course that's going to report no error, because no error could have possibly has taken place yet. Do this logging inside the else clause where you know sqlite3_prepare_v2 (a) has been called; but (b) didn't return SQLITE_OK. And don't call any SQLite functions between when sqlite3_prepare_v2 failed and where you log the error message.
The most common problem is that it reports that there is no such table. And if that's what you see, in this case that could be a result of failing to include the database in the app bundle (see the "Copy Bundle Resources" section of the "Build Phases" section of your target settings).
Alternatively, if it says something about the database is busy, that can happen if you have mismatched your database open calls and your database close calls (like you have in this code sample).
please make sure your sqlite db is not opened with any sqlite db browser when you are running app in simulator.
It happened with me, I had my sqlite db opened in sqlite browser to test my query and when I was running app from simulator it was not updating anything in db.
The problem was my file was of sqlite extension, but the code specified an extension of sqlite3. I thought the two were synonymous but they weren't

Keychain iOS not always storing values

I´m developing a couple of iOS applications and i need to share a element between them, that i want to store in the keychain.
This element is used in a complex login process with 3 or 4 steps, in each one i need to read the value from the keychain, to do this i used the code bellow:
- (NSString *)installationToken
{
KeychainItemWrapper *kw = [[KeychainItemWrapper alloc] initWithIdentifier:#"uuid" accessGroup:#"yyyyy.xxxxxxxxxxx"];
if (![kw objectForKey:(NSString*)kSecAttrAccount] || [[kw objectForKey:(NSString*)kSecAttrAccount] isEqualToString:#""]) {
NSString *result;
CFUUIDRef uuid;
CFStringRef uuidStr;
uuid = CFUUIDCreate(NULL);
assert(uuid != NULL);
uuidStr = CFUUIDCreateString(NULL, uuid);
assert(uuidStr != NULL);
result = [NSString stringWithFormat:#"%#", uuidStr];
assert(result != nil);
CFRelease(uuidStr);
CFRelease(uuid);
[kw setObject:result forKey:(NSString*)kSecAttrAccount];
return result;
} else {
return [kw objectForKey:(NSString*)kSecAttrAccount];
}
}
This all works well in almost every device but in some, users are complaining. So, i checked what my server is receiving, and saw that different values are being sent.
I checked the code and in no other place i'm acessing/emptying this keychain element, what can be wrong with this? For the majority of devices this works like a charm but for some reason, in some devices, they aren't storing or retrieving well from the keychain.
The problem happens in different invocation in the same application.
If you are using Apples' sample code for KeyChainWrapper, then main problem is sometimes randomly, SecItemCopyMatching fails and then the sample code has resetKeychainItem which will basically reset your keychain.
if (! SecItemCopyMatching((CFDictionaryRef)tempQuery, (CFTypeRef *)&outDictionary) == noErr)
{
// Stick these default values into Keychain if nothing found.
[self resetKeychainItem];
}
In our app, we noticed similar problems, and so now we are using
https://github.com/carlbrown/PDKeychainBindingsController to do all keyChain related functionality. Now it works very well.

iOS Data Storage Guidelines Rejection

My app was rejected cause it must follow the iOS Data Storage Guidelines. I have already read some answer here on stackoverflow, and i have already read some blogs... I know my problem, at first application launch i copy 1 sqlite db and unzip some images in documents folder. The problem it's icloud that automaticcaly backup any files in documents directory. I don't need to use icloud in my app but my files must remain in document folder because they are the base data of my application and must be persit (cache folder or temp folder aren't correct solutions). So i've read about a flag that i can set file by file to forbid the backup :
[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:nil];
I have also read that this method works only in ios higher than 5.0.1, in the other it will be simply ignored (i have setted my ios deployment target to 4.3)... If i use this method my app will be rejected again because the older ios devices backup aren't managed? If yes there is a cross-iosversion method to set the NSURLIsExcludedFromBackupKey?
EDIT
I'm sorry icloud isn't present in ios earlier 5.0 so i think that the problem regards only differences beetween version 5.0 and 5.0.1, I'm wrong?
I have passed all files that will stored in documents folder through this method.
Try this
-(BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
const char* filePath = [[URL path] fileSystemRepresentation];
const char* attrName = "com.apple.MobileBackup";
if (&NSURLIsExcludedFromBackupKey == nil) {
// iOS 5.0.1 and lower
u_int8_t attrValue = 1;
int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);
return result == 0;
}
else {
// First try and remove the extended attribute if it is present
int result = getxattr(filePath, attrName, NULL, sizeof(u_int8_t), 0, 0);
if (result != -1) {
// The attribute exists, we need to remove it
int removeResult = removexattr(filePath, attrName, 0);
if (removeResult == 0) {
NSLog(#"Removed extended attribute on file %#", URL);
}
}
// Set the new key
NSError *error = nil;
[URL setResourceValue:[NSNumber numberWithBool:YES] forKey:NSURLIsExcludedFromBackupKey error:&error];
return error == nil;
}
}
I've found this method here in Stack Overflow:
Use NSURLIsExcludedFromBackupKey without crashing on iOS 5.0
NOTE:
do not use
NSString *mediaDir = [NSString stringWithFormat:#"%#/Library/Caches/%#", NSHomeDirectory(), MEDIA_DIRECTORY];
since you are making assumptions about the name.
rather, use:
NSArray* lCachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString* lCacheDirectory = [lCachePaths objectAtIndex:0];
In my app I solved it by adding the files (images in my case) to the bundle,
and check in the method where I load the images if they exist in the document folder where the user generated data / images will be placed.
If it isn't there, I know it's one of my prepopulated images and I load it from the bundle.
For the sqlite file:
Not a really satisfying solution, but I create the data in code now, instead of a prepopulated sqlite file.
For media files, we store them in the Caches/ directory. During application startup, we check if they are there. Otherwise the images are either loaded from the bundle (in your case unzipped) or in our case re-downloaded from the Server.
The exact location is
NSString *mediaDir = [NSString stringWithFormat:#"%#/Library/Caches/%#", NSHomeDirectory(), MEDIA_DIRECTORY];
which is the place where you SHOULD store your temporary files. The files in the cache are generally persistent, much more so than in Temp, but can be deleted at some point.

Resources