这篇文章介绍如何对数据库文件进行加密,基于FMDB+SQLCipher,标明踩过的坑,留给同样掉进坑里的小伙伴参考。
1、下载支持SQLCipher的FMDB分支
FMDB官方库地址:https://github.com/ccgus/fmdb
SQLCipher官网:https://www.zetetic.net/sqlcipher/
作者做了一个支持SQLCipher的仓库,为我们节约了大量编译和工程配置时间,感谢!
修改Podfile:
将
pod 'FMDB'
替换为
pod 'FMDB/SQLCipher'
更新一下Pod即可得到支持SQLCipher的FMDB。
2、加密方法的使用
- (BOOL)setKey:(NSString*)key;
方法很简单,关键是使用的位置:
2.1、直接使用FMDatabase
每次调用
[db open]
的时候调用一次
2.2、使用FMDatabaseQueue
调用
+ (instancetype)databaseQueueWithPath:(NSString*)aPath
的时候调用一次 ,例如:
self.dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath]
[self.dbQueue inDatabase:^(FMDatabase*db) {
[db setKey:kDBEncryptedKey]
}];
这里有个坑:不是每次调用
- (void)inDatabase:(void(^)(FMDatabase*db))block
的时候都要在block里调用
- (BOOL)setKey:(NSString*)key
只需要在初始化FMDatabaseQueue完的时候调用一次即可,而且要保证最先调用,不然任何数据库操作都是非法的!
3、非加密数据库<->加密数据库
如何将现有非加密数据库在保存数据的情况下转换为加密数据库?
又如何将加密数据库保留数据还原为非加密数据库?
这里需要用到sqlcipher_export()方法。
3.1、创建全新的加密数据库
如果待创建的数据库是全新的,按照上面说的方法直接调用
- (BOOL)setKey:(NSString*)key
即可得到加密数据库。
3.2、将现有非加密数据库转换为加密数据库
如果你的数据库开始是没有加密的,按照上面介绍的方法调用
- (BOOL)setKey:(NSString*)key
并不会将现有数据库转换为加密数据库,只会得到一大堆报错。
下面是一段从SQLCipher拷贝来的例子:
Example 1: Encrypt a Plaintext Database
$ ./sqlcipher plaintext.db
sqlite> ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'testkey';
sqlite> SELECT sqlcipher_export('encrypted');
sqlite> DETACH DATABASE encrypted;
我们只需要执行三个SQL语句即可将非加密数据库拷贝到加密数据库,是不是很简单?但是这里又有个坑:
1、sqlite> ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'testkey';
这句话中的'encrypted.db',代表待创建的加密数据库的全路径,并不只是数据库的名称;
2、sqlite> SELECT sqlcipher_export('encrypted');
这句话中的'encrypted',代表一个关键字!!!并不是什么待创建的数据库的全路径也不是什么数据库名称!或许真的是我太单纯了...
3、sqlite> DETACH DATABASE encrypted;
没错,这里的encrypted也是一个关键字
使用代码封装一下:
#define kDBEncryptedKey@"abcdefg"
NSString *encryptedDBPath = @"Your/DB/Path/encryptedDB.db"
NSString *plainttextDBPath = @"Your/DB/Path/plainttextDB.db"
const char *sql = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY '%@';", encryptedDBPath, kDBEncryptedKey] UTF8String];
const char *exportSql = [[NSString stringWithFormat:@"SELECT sqlcipher_export('encrypted');"] UTF8String];
const char *detachSql = [[NSString stringWithFormat:@"DETACH DATABASE encrypted;"] UTF8String];
sqlite3 *unencrypted_DB = NULL;
if(sqlite3_open([plainttextDBPath UTF8String], &unencrypted_DB) ==SQLITE_OK)
{
int rc;
char *errmsg = NULL;
rc =sqlite3_exec(unencrypted_DB, sql,NULL,NULL, &errmsg);
rc =sqlite3_exec(unencrypted_DB, exportSql,NULL,NULL, &errmsg);
rc =sqlite3_exec(unencrypted_DB, detachSql,NULL,NULL, &errmsg);
sqlite3_close(unencrypted_DB);
}
else
{
sqlite3_close(unencrypted_DB);
NSAssert1(NO,@"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
}
通过上面的代码可以从非加密数据库拷贝一份保留数据的加密数据库,加密数据库生成后记得删除旧数据库。
3.3、将加密数据库转换为非加密数据库
下面又是一段从SQLCipher拷贝来的例子:
Example 2: Decrypt a SQLCipher database to a Plaintext Database
$ ./sqlcipher encrypted.db
sqlite> PRAGMA key = 'testkey';
sqlite> ATTACH DATABASE 'plaintext.db' AS plaintext KEY ''; -- empty key will disable encryption
sqlite> SELECT sqlcipher_export('plaintext');
sqlite> DETACH DATABASE plaintext;
记得上面说的坑:
'plaintext.db'是待创建的未加密数据的全路径;
'plaintext'是关键字,不是路径也不是去掉后缀的数据库名称;
plaintext是关键字;
使用代码封装一下:
#define kDBEncryptedKey@"abcdefg"
NSString *encryptedDBPath = @"Your/DB/Path/encryptedDB.db"
NSString *plaintextDBPath = @"Your/DB/Path/plainttextDB.db"
const char *sql = [[NSStringstringWithFormat:@"PRAGMA key = '%@';", kDBEncryptedKey] UTF8String];
const char *attachSql = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS plaintext KEY '';", plaintextDBPath] UTF8String];
const char *exportSql = [[NSStringstringWithFormat:@"SELECT sqlcipher_export('plaintext');"] UTF8String];
const char *detachSql = [[NSStringstringWithFormat:@"DETACH DATABASE plaintext;"] UTF8String];
sqlite3*encrypted_DB =NULL;
if(sqlite3_open([encryptedDBPathUTF8String], &encrypted_DB) ==SQLITE_OK)
{
int rc;
char *errmsg = NULL;
rc = sqlite3_exec(encrypted_DB, sql,NULL,NULL, &errmsg);
rc =sqlite3_exec(encrypted_DB, attachSql,NULL,NULL, &errmsg);
rc =sqlite3_exec(encrypted_DB, exportSql,NULL,NULL, &errmsg);
rc =sqlite3_exec(encrypted_DB, detachSql,NULL,NULL, &errmsg);
sqlite3_close(encrypted_DB);
}
else
{
sqlite3_close(encrypted_DB);
NSAssert1(NO,@"Failed to open database with message '%s'.", sqlite3_errmsg(encrypted_DB));
}
使用上面的代码就可以将加密后的数据库还原成未加密数据库,同样记得删除旧数据库。
坑:转换后的数据库除了保留了数据外,并没有保留以前的数据库附加信息,例如userVersion,记得将老数据库的userVersion赋给新数据库,不然数据库升级可能要有麻烦了!
关键代码都在这里,大家根据个人需要自由组合,例子我就不写了,因为懒。