sqlcipher在IOS中的應用
在iOS開發過程中經常需要用到SQLite來儲存資料,由於Apple的沙盒機制,我們App的資料儲存在沙盒裡面,一般情況下無法拿到資料,但是iOS管理軟體iFunBox可以讀取到應用程式沙盒裡面的檔案,因此為了保證資料的安全性,我們需要對資料庫進行加密儲存,然而,一般的加密儲存手段有兩種方式:
1、對資料庫中的每條資料進行加密。
2、對資料庫整個進行加密。
由於前者較為麻煩,儲存和取出是需要進行加解密操作,過程非常繁瑣。所以建議採用第二種加密手段,即對整個資料庫進行加密。在IOS中,我們經常使用第三方資料庫FMDB來簡化直接對sqlite的操作,因為FMDB是基於sqlite的oc層封裝。然後sqlite並不直接支援對資料庫的加密,需要藉助第三方工具來sqlcipher來實現。並且FMDB已經提供了sqlcipher的拓展。
1.Sqlcipher匯入方式
a.Pod匯入,比較推薦
pod 'FMDB/SQLCipher'
b.手動匯入
在匯入FMDB的基礎上,把Sqlcipher提供的sqlite3.c、sqlite3.h替換掉。
手動修改配置
(1)target -> Build Setting -> Other C Flags新增 -DSQLITE_HAS_CODEC、 -DSQLITE_TEMP_STORE=2、 -DSQLITE_THREADSAFE、 -DSQLCIPHER_CRYPTO_CC 幾項配置
(2)target -> Build Setting -> Other Linker Flags新增-framework Security配置
然後新建子類FMEncryptDatabase繼承於FMDatabase用於設定資料庫key,重寫open和openWithFlags方法,對於加密的資料庫,每次的開啟之後都必須使用密碼,即設定資料庫key.
#import "FMDatabase.h" @interface FMEncryptDatabase : FMDatabase /** 如果需要自定義encryptkey,可以呼叫這個方法修改(在使用之前)*/ + (void)setEncryptKey:(NSString *)encryptKey; @end
#import "FMEncryptDatabase.h"
@implementation FMEncryptDatabase
static NSString *encryptKey_;
+ (void)initialize
{
[super initialize];
//初始化資料庫加密key,在使用之前可以通過 setEncryptKey 修改
encryptKey_ = @"FDLSAFJEIOQJR34JRI4JIGR93209T489FR";
}
#pragma mark - 過載原來方法
- (BOOL)open {
if (_db) {
return YES;
}
int err = sqlite3_open([self sqlitePath], &_db );
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
} else {
//資料庫open後設置加密key
[self setKey:encryptKey_];
}
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}
return YES;
}
#if SQLITE_VERSION_NUMBER >= 3005000
- (BOOL)openWithFlags:(int)flags {
if (_db) {
return YES;
}
int err = sqlite3_open_v2([self sqlitePath], &_db, flags, NULL /* Name of VFS module to use */);
if(err != SQLITE_OK) {
NSLog(@"error opening!: %d", err);
return NO;
} else {
//資料庫open後設置加密key
[self setKey:encryptKey_];
}
if (_maxBusyRetryTimeInterval > 0.0) {
// set the handler
[self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
}
return YES;
}
#endif
- (const char*)sqlitePath {
if (!_databasePath) {
return ":memory:";
}
if ([_databasePath length] == 0) {
return ""; // this creates a temporary database (it's an sqlite thing).
}
return [_databasePath fileSystemRepresentation];
}
#pragma mark - 配置方法
+ (void)setEncryptKey:(NSString *)encryptKey
{
encryptKey_ = encryptKey;
}
@end
建立FMEncryptDatabaseQueue繼承於FMDatabaseQueue,
#import <Foundation/Foundation.h>
#import "FMDatabaseQueue.h"
@interface FMEncryptDatabaseQueue : FMDatabaseQueue
@end
#import "FMEncryptDatabaseQueue.h"
#import "FMEncryptDatabase.h"
@implementation FMEncryptDatabaseQueue
+ (Class)databaseClass
{
return [FMEncryptDatabase class];
}
@end
新建一個數據庫加密工具類
#import <Foundation/Foundation.h>
@interface FMEncryptHelper : NSObject
/** 對資料庫加密 */
+ (BOOL)encryptDatabase:(NSString *)path;
/** 對資料庫解密 */
+ (BOOL)unEncryptDatabase:(NSString *)path;
/** 對資料庫加密 */
+ (BOOL)encryptDatabase:(NSString *)sourcePath targetPath:(NSString *)targetPath;
/** 對資料庫解密 */
+ (BOOL)unEncryptDatabase:(NSString *)sourcePath targetPath:(NSString *)targetPath;
/** 修改資料庫祕鑰 */
+ (BOOL)changeKey:(NSString *)dbPath originKey:(NSString *)originKey newKey:(NSString *)newKey;
@end
#import "FMEncryptHelper.h"
#import "sqlite3.h"
@implementation FMEncryptHelper
static NSString *encryptKey_;
+ (void)initialize
{
encryptKey_ = @"FDLSAFJEIOQJR34JRI4JIGR93209T489FR";
}
//對資料庫加密(檔案不變)
+ (BOOL)encryptDatabase:(NSString *)path
{
NSString *sourcePath = path;
NSString *targetPath = [NSString stringWithFormat:@"%@.tmp.db", path];
if([self encryptDatabase:sourcePath targetPath:targetPath]) {
NSFileManager *fm = [[NSFileManager alloc] init];
[fm removeItemAtPath:sourcePath error:nil];
[fm moveItemAtPath:targetPath toPath:sourcePath error:nil];
return YES;
} else {
return NO;
}
}
//對資料庫解密(檔案不變)
+ (BOOL)unEncryptDatabase:(NSString *)path
{
NSString *sourcePath = path;
NSString *targetPath = [NSString stringWithFormat:@"%@.tmp.db", path];
if([self unEncryptDatabase:sourcePath targetPath:targetPath]) {
NSFileManager *fm = [[NSFileManager alloc] init];
[fm removeItemAtPath:sourcePath error:nil];
[fm moveItemAtPath:targetPath toPath:sourcePath error:nil];
return YES;
} else {
return NO;
}
}
/** 對資料庫加密 */
+ (BOOL)encryptDatabase:(NSString *)sourcePath targetPath:(NSString *)targetPath
{
//給未加密資料庫新增加密的附屬資料庫
const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY '%@';", targetPath, encryptKey_] UTF8String];
sqlite3 *unencrypted_DB;
if (sqlite3_open([sourcePath UTF8String], &unencrypted_DB) == SQLITE_OK) {
// Attach empty encrypted database to unencrypted database
sqlite3_exec(unencrypted_DB, sqlQ, NULL, NULL, NULL);
//begin exclusive transaction,begin deferred transaction(延遲事務)
//處理資料量比較大的資料庫
sqlite3_exec(unencrypted_DB, "begin exclusive transaction", 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);
//end exclusive transaction
sqlite3_exec(unencrypted_DB, "commit transaction", NULL, NULL, NULL);
sqlite3_close(unencrypted_DB);
return YES;
}
else {
sqlite3_close(unencrypted_DB);
NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
return NO;
}
}
/** 對資料庫解密 */
+ (BOOL)unEncryptDatabase:(NSString *)sourcePath targetPath:(NSString *)targetPath
{
//給加密資料庫新增未加密的附屬資料庫
const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS plaintext KEY '';", targetPath] UTF8String];
sqlite3 *encrypted_DB;
if (sqlite3_open([sourcePath UTF8String], &encrypted_DB) == SQLITE_OK) {
sqlite3_exec(encrypted_DB, [[NSString stringWithFormat:@"PRAGMA key = '%@';", encryptKey_] UTF8String], NULL, NULL, NULL);
// Attach empty unencrypted database to encrypted database
sqlite3_exec(encrypted_DB, sqlQ, NULL, NULL, NULL);
// export database
sqlite3_exec(encrypted_DB, "SELECT sqlcipher_export('plaintext');", NULL, NULL, NULL);
// Detach unencrypted database
sqlite3_exec(encrypted_DB, "DETACH DATABASE plaintext;", NULL, NULL, NULL);
sqlite3_close(encrypted_DB);
return YES;
}
else {
sqlite3_close(encrypted_DB);
NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(encrypted_DB));
return NO;
}
}
/** 修改資料庫祕鑰 */
+ (BOOL)changeKey:(NSString *)dbPath originKey:(NSString *)originKey newKey:(NSString *)newKey
{
sqlite3 *encrypted_DB;
if (sqlite3_open([dbPath UTF8String], &encrypted_DB) == SQLITE_OK) {
//利用sqlite專有PRAGMA語法,設定key,開啟資料庫
sqlite3_exec(encrypted_DB, [[NSString stringWithFormat:@"PRAGMA key = '%@';", originKey] UTF8String], NULL, NULL, NULL);
//利用sqlite專有PRAGMA語法,重新設定key
sqlite3_exec(encrypted_DB, [[NSString stringWithFormat:@"PRAGMA rekey = '%@';", newKey] UTF8String], NULL, NULL, NULL);
sqlite3_close(encrypted_DB);
return YES;
}
else {
sqlite3_close(encrypted_DB);
NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(encrypted_DB));
return NO;
}
}
@end
然後下面提供幾種資料庫使用情況:
1.建立未加密資料庫
FMDatabaseQueue *_queue = [FMDatabaseQueue databaseQueueWithPath:path];
2.建立加密資料庫
FMDatabaseQueue *_queue = [FMEncryptDatabaseQueue databaseQueueWithPath:path];
3.讀取加密資料庫
[FMEncryptDatabase setEncryptKey:originKey];
4.對未加密資料庫進行加密
[FMEncryptHelper encryptDatabase:dbPath1];
5.解密資料庫,刪掉資料庫密碼
[FMEncryptHelper unEncryptDatabase:dbPath2];
6.改變資料庫密碼
[FMEncryptHelper changeKey:dbPath1 originKey:originKey newKey:newKey];
關於sqlite中PRAGMA語法的使用,詳情請參考https://www.cnblogs.com/songxingzhu/p/3992884.html
參考文章SQLite的總結和使用