天天看點

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的總結和使用