SQLite是一個輕量的、跨平臺的阵子、開源的數(shù)據(jù)庫引擎拌禾,它的在讀寫效率、消耗總量闽晦、延遲時間和整體簡單性上具有的優(yōu)越性,使其成為移動平臺數(shù)據(jù)庫的最佳解決方案(如iOS提岔、Android)仙蛉。然而免費版的SQLite有一個致命缺點:不支持加密。這就導(dǎo)致存儲在SQLite中的數(shù)據(jù)可以被任何人用任何文本編輯器查看到碱蒙。
如果我們想要使得自己的數(shù)據(jù)庫加密荠瘪,解決方案就是使用另一款開源的加密數(shù)據(jù)庫SQLCipher夯巷,SQLCipher使用256-bit AES加密,由于其基于免費版的SQLite哀墓,主要的加密接口和SQLite是相同的趁餐,當然也增加了一些自己的接口,如在新建和打開數(shù)據(jù)庫時篮绰,給數(shù)據(jù)庫設(shè)置秘鑰之類的操作后雷。
FMDB是一個開源的類庫,它對sqlite數(shù)據(jù)庫操作進行了很不錯的封裝吠各,而且也增加了對sqlcipher的支持臀突,也就是說,我們不直接用sqlcihper也能完成加解密操作贾漏,而且FMDB在操作sqlite方面方便得多『蜓В現(xiàn)在的APP開發(fā)如果涉及到數(shù)據(jù)庫操作,F(xiàn)MDB基本上是首選纵散。
下面內(nèi)容主要是針對一些在初期忽略了數(shù)據(jù)庫私密性梳码,而到了中期需要讓數(shù)據(jù)庫進行升級加密的App的簡單方案介紹和代碼實現(xiàn)。如果讀者沒有使用FMDB伍掀,直接使用了sqlite边翁,那本文也有一定參考性硕盹,只是關(guān)于SQLCipher的配置符匾,并沒有介紹,因為雖然FDMB的加密也是使用SQLCipher瘩例,但是不需要進行配置的啊胶。
具有加密功能的FMDB版本
加密的FMDB其實是一個分支,也就是說垛贤,如果你需要替換FMDB焰坪。Github上關(guān)于該分支的安裝只提供了cocospod的安裝方式。
Github上FMDB的地址:https://github.com/ccgus/fmdb
sqlcipher:https://github.com/sqlcipher/sqlcipher
舉個例子聘惦,如果你以前是如下寫的
pod 'FMDB'
如果想換成有加密功能的某饰,就改成
pod 'FMDB/SQLCipher'
升級數(shù)據(jù)庫代碼實現(xiàn)
得到對的版本后,我們需要在代碼里做一些處理善绎,讓舊數(shù)據(jù)庫升級黔漂。這里升級包括兩點:
- 1 是我們前面所說的,將數(shù)據(jù)庫加密禀酱。
- 2 把舊數(shù)據(jù)庫的表和數(shù)據(jù)遷移到新數(shù)據(jù)庫中炬守。
sqlcipher的使用和sqlite沒有多大的區(qū)別,有一點值得注意便是每次當[dp open]
成功后剂跟,需要給數(shù)據(jù)庫配上口令减途,即[db setKey:DB_SECRETKEY]
酣藻,DB_SECRETKEY
是我們的一個自定義宏。這之后鳍置,我們才能讀出數(shù)據(jù)庫的內(nèi)容辽剧。
FMDatabase *_db = [FMDatabase databaseWithPath:[cachePath stringByAppendingString:dbFileName]];
if (![_db open]) {
_db = nil;
return;
}else{
[_db setKey:DB_SECRETKEY];
}
然后我們需要判斷數(shù)據(jù)庫是否需要升級,當使用sqlchipher
打開用sqlite生成的源數(shù)據(jù)庫時税产,[dp goodConnection]
是為NO怕轿,即使上面[db open]操作是YES。
if(![_db goodConnection]){ //無效連接
[self upgradeDatabase:dbpath];
}
接下來砖第,便是數(shù)據(jù)遷移,path是我們的源數(shù)據(jù)庫路徑环凿,假設(shè)我們的源數(shù)據(jù)庫名字為DBName梧兼,changeDatabasePath即將我們的數(shù)據(jù)庫A改名為DBName.tmp,接下來便是智听,新建數(shù)據(jù)庫B羽杰,因為A在前面已經(jīng)進行了改名為DBName.tmp,并且已經(jīng)B將來就是我們要取代A的數(shù)據(jù)庫到推,所以此處的B名字便是DBName考赛。最后將DBName.tmp的數(shù)據(jù)復(fù)制到DBName中,再刪除DBName.tmp莉测,致此颜骤,我們的數(shù)據(jù)庫便升級完成了。
- (void)upgradeDatabase:(NSString *)path{
NSString *tmppath = [self changeDatabasePath:path];
if(tmppath){
const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY '%@';",path,DB_SECRETKEY] UTF8String];
sqlite3 *unencrypted_DB;
if (sqlite3_open([tmppath 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);
//delete tmp database
[self removeDatabasePath:tmppath];
}
else {
sqlite3_close(unencrypted_DB);
NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
}
}
}
- (NSString *)changeDatabasePath:(NSString *)path{
NSError * err = NULL;
NSFileManager * fm = [[NSFileManager alloc] init];
NSString *tmppath = [NSString stringWithFormat:@"%@.tmp",path];
BOOL result = [fm moveItemAtPath:path toPath:tmppath error:&err];
if(!result){
NSLog(@"Error: %@", err);
return nil;
}else{
return tmppath;
}
}
經(jīng)過上面步驟捣卤,我們知道雖然sqlchipher是基于sqlite的忍抽,但到底還是不一樣的,我們沒辦法直接將sqlite的數(shù)據(jù)庫升級為sqlchipher董朝,只能用sqlchipher新建一個數(shù)據(jù)庫鸠项,再重新寫入數(shù)據(jù)。
以上代碼僅僅是范例子姜,各個App的數(shù)據(jù)存儲模型不盡相同祟绊,這里我也沒辦法給出模板。雖然代碼不一定是適用的哥捕,但是在數(shù)據(jù)庫升級時牧抽,代碼執(zhí)行的先后順序是肯定的:打開數(shù)據(jù)庫open -> 設(shè)置秘鑰 setkey -> 查看連接 goodConnection -> 新建數(shù)據(jù)庫并遷移數(shù)據(jù) upgrade。
參考:
關(guān)于SQLite遥赚,SQLCipher和FMDB
app數(shù)據(jù)庫版本遷移(sqlite表結(jié)構(gòu)變更)