【iOS】FMDB/SQLCipher数据库加解密,迁移
2016-04-19更新:本文代码可能有些问题,请移步 http://zhengbomo.github.io/2016-04-18/sqlcipher-start/ 查看
sqlite应用几乎在所有的App都能看到,虽然我们的数据存储在沙盒里面,一般情况下无法拿到,但是iOS管理软件(如:iFunBox)可以读取到应用程序沙盒里面的文件,为了提高数据的安全性,我们需要考虑对数据库进行加密
数据库加密一般有两种方式
1、对所有数据进行加密
2、对数据库文件加密
处于客户端性能的考虑,通常我们对数据库文件进行加密,在iOS上用的比较多的是 sqlcipher,由于原生提供的sqlite API是C语言实现的,通常我们会用一个在github上比较有名的一个工具库FMDB,FMDB对原生的sqlite进行了封装,提供了面向对象的方式对数据库操作,同时FMDB 也提供了对 sqlcipher 的支持
下面基于 FMDB 和 sqlcipher 演示数据库加解密
编译sqlcipher需要做一些配置,具体配置详情见:https://www.zetetic.net/sqlcipher/ios-tutorial/
我们通过 cocoapod 引用 FMDB 和sqlcipher 我们可以直接拿到编译好的.a文件,直接用就可以
1、通过cocoapod 引用库
pod 'FMDB/SQLCipher', '~> 2.5'
如果项目已经引用了FMDB,改为FMDB/SQLCipher 重新install一次即可,通过cocoapod添加的FMDB默认还是没有加密的,要使用加密的功能,需要在数据库open后调用setKey方法设置key,如下
- (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;
}
关键代码:[self setKey:encryptKey_];
2、添加数据库加密操作类
上面代码是FMDatabase中的,CocoaPod添加的库不推荐修改,修改后不利于类库的统一管理和更新
有些人则不用cocoapod引用FMDB,而是直接把FMDB的源文件拷贝到项目中,然后进行修改,我更倾向与保留cocoapod对FMDB的管理,通过新增类提供对数据库加密的支持,这里新增两个类:FMEncryptDatabase 和 FMEncryptDatabaseQueue
我们可以重用 FMDatabase 和 FMDatabaseQueue 的逻辑,所以我们可以继承自他们,同时我再FMEncryptDatabase 中提供两个数据库迁移的方法,可以把未加密的数据库转换为加密的数据库,也可以反向转换
由于secretKey一般只需要一份,所以这里使用一个静态变量实现,如果需要修改,可以在AppDelegate的 application:didFinishLaunchingWithOptions: 方法进行设置
#import "FMDatabase.h" @interface FMEncryptDatabase : FMDatabase /** 如果需要自定义encryptkey,可以调用这个方法修改(在使用之前)*/
+ (void)setEncryptKey:(NSString *)encryptKey; @end @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] == ) {
return ""; // this creates a temporary database (it's an sqlite thing).
} return [_databasePath fileSystemRepresentation]; } #pragma mark - 配置方法
+ (void)setEncryptKey:(NSString *)encryptKey
{
encryptKey_ = encryptKey;
} @end
FMEncryptDatabase
#import "FMDatabaseQueue.h"
#import "FMEncryptDatabase.h" @interface FMEncryptDatabaseQueue : FMDatabaseQueue @end @implementation FMEncryptDatabaseQueue + (Class)databaseClass
{
return [FMEncryptDatabase class];
} @end
FMEncryptDatabaseQueue
#import <Foundation/Foundation.h>
#import "sqlite3.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 @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); // 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); 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) { sqlite3_exec(encrypted_DB, [[NSString stringWithFormat:@"PRAGMA key = '%@';", originKey] UTF8String], NULL, NULL, NULL); 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
FMEncryptHelper
3、测试
好了,通过上面两个类创建的数据库都是加密过的,下面做一些测试,具体代码见后面的demo
加密后的数据库暂时没有找到可以打开的GUI工具查看(MesaSQLite),即使输入secretKey也无法查看,不知道为何
4、常见问题问题
如果你不是通过Cocoapod的方式引用的Fmdb和Sqlcipher,可以直接在https://github.com/sqlcipher/sqlcipher 下载到sqlcipher的工程文件,然后应用到项目中,并且需要在你的项目中添加-DSQLITE_HAS_CODEC 宏定义,否则使用Fmdb的时候将不会加密
5、Demo
http://files.cnblogs.com/files/bomo/FmdbEncryptDemo.zip
个人水平有限,如果你有更好的建议和实现方式,欢迎留言探讨
【iOS】FMDB/SQLCipher数据库加解密,迁移的更多相关文章
- postgresql9.1数据库加解密
--如下为postgresql9.1数据库加解密模块配置 --设置schemapsql -U postgres -h localhostset schema 'sbdc';--生成日志\o E:/sh ...
- Android数据库安全解决方案,使用SQLCipher进行加解密
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952409 我们都知道,Android系统内置了SQLite数据库,并且提供了一 ...
- java与IOS之间的RSA加解密
很简单的一个需求,ipad端给密码RSA加密,传到java后台,解密.RSA加密算法是基于一个密钥对的,分为公钥和私钥,一般情况公钥加密,私钥解密,但也可私钥加密,公钥解密.还可以验签,就是先用私钥对 ...
- iOS AES-CBC、AES-ECB 加解密
简介 AES是加密的算法,使用128.192 和 256 位密钥,将被加密数据划分为128位(16字节)一块,然后使用某种加密模式进行加密 关键词: 块大小:16字节 密钥长度:AES算法下,key的 ...
- iOS - (base64对字符串加解密)
今天公司让做支付系统,为了安全起见,需要对一些数据进行加密,然而我首想到的就是 base64 ,严格来说这不是一种加密方式,这只是将原有的一些字符串或者其它的一些文本进行一个转化而已,就是转化成数字, ...
- IOS FMDB 获取数据库表和表中的数据
ios开发中,经常会用到数据库sqlite的知识,除了增,删,改,查之外,我们说说如何获取数据库中有多少表和表相关的内容. 前言 跟数据库使用相关的一般的增删改查的语句,这里就不做解释了.在网上有很多 ...
- 利用BBRSACryptor实现iOS端的RSA加解密
背景 RSA这种非对称加密被广泛的运用于网络数据的传输,但其在iOS上很难直接实现,BBRSACryptor框架通过移植openssl实现了iOS端的RSA,本文将介绍如何使用BBRSACryptor ...
- 通过T-SQL语句实现数据库加解密功能
CREATE TABLE [dbo].[Users] ( [U_nbr] NVARCHAR(20) NOT NULL PRIMARY KEY, [Pwd] nvarchar(MAX) ) --加密 D ...
- SpringBoot+ShardingSphere彻底解决生产环境数据库字段加解密问题
前言 互联网行业公司,对于数据库的敏感字段是一定要进行加密的,方案有很多,最直接的比如写个加解密的工具类,然后在每个业务逻辑中手动处理,在稍微有点规模的项目中这种方式显然是不现实的,不仅工作量大而 ...
随机推荐
- JS验证 只能输入小数点,数字,负数。
<script language="JavaScript" type="text/javascript"> function clearNoNum( ...
- go2shell的安装与修改默认terminal方法
go2shell的安装与修改默认terminal方法 1. 安装go2shell后,打开finder的application文件夹,找到go2shell 2. 按住command,用鼠标将go2s ...
- Android开发(三十一)——重复引用包错误Conversion to Dalvik format failed
错误:Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define Landroid/sup ...
- Asp.net Core 1.0.1升级到Asp.net Core 1.1.0 Preview版本发布到Windows Server2008 R2 IIS中的各种坑
Asp.net Core 1.0.1升级到Asp.net Core 1.1.0后,程序无法运行了 解决方案:在project.json中加入runtime节点 "runtimes" ...
- FindProcDLL::FindProc 和 KillProcDLL::KillProc,必须使用WPF x86编译出来的程序
如果是 WPF 编写的exe,想用NSIS打包. 脚本里面要注意了,如果使用了 FindProcDLL::FindProc 和 KillProcDLL::KillProc, 那么WPF 的编译选项必须 ...
- 实例详解 DB2 排序监控和调优
实例详解 DB2 排序监控和调优http://automationqa.com/forum.php?mod=viewthread&tid=2882&fromuid=2
- swift 枚举类型
1:swift的枚举类型是一系列的值,不同于c语言中枚举类型是整数类型.每个枚举定义了个新的类型 2:switch类型匹配 2.1枚举类型和switch单个匹配 enum PlatType{ case ...
- Redis和Memcached的区别
From: https://www.biaodianfu.com/redis-vs-memcached.html Redis的作者Salvatore Sanfilippo曾经对这两种基于内存的数据存储 ...
- java之对象转型
对象转型(casting) 1.一个基类的引用类型变量可以“指向”其子类的对象. 2.一个基类的引用不可以访问其子类对象新增加的成员(属性和方法). 3.可以使用 引用变量 instanceof 类名 ...
- drupal7 form模板复写方法
给form制作一个template 从官方的drupal api document中可得到form有#theme这个参数,它可以指定form使用一个模板来用于form的基本布局,#theme的值必须是 ...