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数据库加解密,迁移的更多相关文章

  1. postgresql9.1数据库加解密

    --如下为postgresql9.1数据库加解密模块配置 --设置schemapsql -U postgres -h localhostset schema 'sbdc';--生成日志\o E:/sh ...

  2. Android数据库安全解决方案,使用SQLCipher进行加解密

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952409 我们都知道,Android系统内置了SQLite数据库,并且提供了一 ...

  3. java与IOS之间的RSA加解密

    很简单的一个需求,ipad端给密码RSA加密,传到java后台,解密.RSA加密算法是基于一个密钥对的,分为公钥和私钥,一般情况公钥加密,私钥解密,但也可私钥加密,公钥解密.还可以验签,就是先用私钥对 ...

  4. iOS AES-CBC、AES-ECB 加解密

    简介 AES是加密的算法,使用128.192 和 256 位密钥,将被加密数据划分为128位(16字节)一块,然后使用某种加密模式进行加密 关键词: 块大小:16字节 密钥长度:AES算法下,key的 ...

  5. iOS - (base64对字符串加解密)

    今天公司让做支付系统,为了安全起见,需要对一些数据进行加密,然而我首想到的就是 base64 ,严格来说这不是一种加密方式,这只是将原有的一些字符串或者其它的一些文本进行一个转化而已,就是转化成数字, ...

  6. IOS FMDB 获取数据库表和表中的数据

    ios开发中,经常会用到数据库sqlite的知识,除了增,删,改,查之外,我们说说如何获取数据库中有多少表和表相关的内容. 前言 跟数据库使用相关的一般的增删改查的语句,这里就不做解释了.在网上有很多 ...

  7. 利用BBRSACryptor实现iOS端的RSA加解密

    背景 RSA这种非对称加密被广泛的运用于网络数据的传输,但其在iOS上很难直接实现,BBRSACryptor框架通过移植openssl实现了iOS端的RSA,本文将介绍如何使用BBRSACryptor ...

  8. 通过T-SQL语句实现数据库加解密功能

    CREATE TABLE [dbo].[Users] ( [U_nbr] NVARCHAR(20) NOT NULL PRIMARY KEY, [Pwd] nvarchar(MAX) ) --加密 D ...

  9. SpringBoot+ShardingSphere彻底解决生产环境数据库字段加解密问题

    前言   互联网行业公司,对于数据库的敏感字段是一定要进行加密的,方案有很多,最直接的比如写个加解密的工具类,然后在每个业务逻辑中手动处理,在稍微有点规模的项目中这种方式显然是不现实的,不仅工作量大而 ...

随机推荐

  1. js计时器 + asp 计时器

    JS: <script type="text/javascript"> ; function starts() { ) { alert('已经开启了实时监控!') re ...

  2. Entityframework修改某个字段

    public void ChangePassword(int userId, string password) { var user = new User() { Id = userId, Passw ...

  3. table变宽格式

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. iOS开发之身份证号码校验

    // //  Card.h //  THCStore // //  Created by Mac on 15/7/15. //  Copyright (c) 2015年 Mac. All rights ...

  5. coreData 深入理解4 --总结 (线程安全与同步--iOS5 前后对比)

    Core Data是iOS中很重要的一个部分,可以理解为基于SQLite(当然也可以是其他的Storage,如In-memory,只是SQLite比较常见)的一个ORM实现,所以有关系数据库的特性,又 ...

  6. Unity Remote 4安卓机使用指南

    必须U3D版本为4.5以上,可以在Public目录下载.想实时调试IOS版本必须是MAC系统! 优点:可以在不编译的情况下实时的去调试真实Android设备的各种情况,包括使用触摸功能(Remote接 ...

  7. android xml解析添加到listview中的问题

    一个问题不知什么原因,代码: public class OtherActivity extends ListActivity { @Override protected void onCreate(B ...

  8. .net和java和谐相处之安卓客户端+.net asp.net mvc webapi 2

    作为没有花很多时间转java,把java当C#用的我,在做服务器端程序的时候,自然不想考虑java web,java需要学的框架太多了,看了一下Java Servlet,始终没有编码的冲动.经过几天的 ...

  9. __get,__set

    __get,__set 为php的magic方法,在类中定义为 public 类型. class UserModel { private $id; public $name; public funct ...

  10. Codeforces Round #237 (Div. 2) C. Restore Graph(水构造)

    题目大意 一个含有 n 个顶点的无向图,顶点编号为 1~n.给出一个距离数组:d[i] 表示顶点 i 距离图中某个定点的最短距离.这个图有个限制:每个点的度不能超过 k 现在,请构造一个这样的无向图, ...