在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的总结和使用