对iOS数据安全的一次小探索

一、主流保证数据安全的方式

1、网络传输安全

1.采用HTTPS通信协议
可防止抓包窃取、篡改传输数据,大大增加了中间人攻击的成本。

2.对敏感数据进行签名校正
采用非对称加密的方式,对敏感数据使用密钥加密,到了客户端用公钥解密,验证数据一致性,防止通信过程中被篡改。

3.采用密文传输
别人即使截取了传输信息,也无法看懂其中的意思。

2、本地存储数据安全

1.数据库加密
通过越狱设备可以很容易地把整个应用包(包括里面的数据)copy出来,这样就可以获取里面的数据库,对于没有加密的数据库就可以非常轻易地读取里面的信息,造成信息的泄漏。
数据库加密可分两个维度:1.整个数据库加密;2.对部分字段先加密再存数据库。
对部分字段加密并不适合多字段的加密存储,容易导致加密数据太过分散,影响性能。所以推荐对整个数据库加密。

2.KeyChain存储敏感数据
KeyChain是iOS系统级的存储方式,安全性无需质疑,且删除应用或者升级系统依然可以保留里面的信息。

3、源码安全

使用越狱设备可以很轻易地把应用砸壳,从而把源码dump下来,即使是没有太多经验的开发者也可以得到应用的类信息,包括函数名等。使用IDA等反编译工具可以看到应用的一些类名和方法名,进而可以分析功能实现的逻辑。

1.字符串混淆
对应用程序中使用到的字符串进行加密,保证源码被逆向后也能保护明文字符串。

2.类名、方法名混淆
市面上很多iOS应用都没有混淆类名方法名,以致于很容易使用class-dump下来,从而进行hook操作,一步一步实现iOS微信自动抢红包(非越狱)是很有趣的一个应用。这个库可以混淆OC的类名、协议、属性还有方法名。

3.程序结构混淆加密
对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低。可参考这个库

4.反调试、反注入等一些主动保护策略
加入第三方安全性SDK

二、对数据库加密的研究

1、主流数据库加密方式

The SQLite Encryption Extension(收费)

SQLiteEncrypt(收费)

SQLiteCrypt(收费)

SQLCipher(开源免费)

只有SQLCipher是免费的,所以本文主要对SQLCipher进行研究。

2、引入SQLCipher第三方库

1.手动引入
请参考官方教程

2.使用CocoaPods
pod 'FMDB/SQLCipher', '2.5'

3、SQLCipher的可行性研究

1.新建加密数据库

若没有旧数据的情况下使用很简单,只需要在FMDatabase里面的-open-openWithFlags:方法里面添加[self setKey:kDatabaseEncryptKey];即可。
如下图所示

添加密钥

但是在团队协作中,如果直接修改pod仓库里面的文件,可能不好同步,下面有一个技巧,就是继承FMDatabase和FMDatabaseQueue并重载其中的方法。

继承FMDatabase的子类需要重载以下方法

- (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 {
        // 设置密钥
        [self setKey:kDatabaseEncryptKey];
    }
    if (_maxBusyRetryTimeInterval > 0.0) {
        // set the handler
        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
    }
    return YES;
}

- (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 {
        // 设置密钥
        [self setKey:kDatabaseEncryptKey];
    }
    if (_maxBusyRetryTimeInterval > 0.0) {
        // set the handler
        [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval];
    }
    return YES;
}

- (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];
}

继承FMDatabaseQueue的子类只需要重载一个方法

+ (Class)databaseClass {
    return [FMEncryptDatabase class];
}

使用的时候只需要更改下面的语句

// 原:
self.normalQueue = [FMDatabaseQueue databaseQueueWithPath:self.normalDbPath];
// 改为:
self.encryptQueue = [FMEncryptDatabaseQueue databaseQueueWithPath:self.encryptDbPath];

2.加密已有数据库

加密已有数据库我已经封装成下面的方法:

/**
 加密数据库(保留原有数据库)
 */
+ (BOOL)encryptDatabase:(NSString *)origPath toPath:(NSString *)toPath {
    sqlite3 *db;
    if (sqlite3_open([origPath UTF8String], &db) == SQLITE_OK) {
        char *err = NULL;
        sqlite3_exec(db, [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY '%@';", toPath, kDatabaseEncryptKey] UTF8String], NULL, NULL, &err);
        sqlite3_exec(db, "SELECT sqlcipher_export('encrypted');", NULL, NULL, &err);
        sqlite3_exec(db, "DETACH DATABASE encrypted;", NULL, NULL, &err);
        sqlite3_close(db);
        return err ? NO : YES;
    } else {
        sqlite3_close(db);
        NSLog(@"Open db failed:%s", sqlite3_errmsg(db));
        return NO;
    }
}

这里有两点需要注意的地方:
1、这里必须使用全路径
2、加密后的queue必须同步更换为FMEncryptDatabaseQueue,不然无法读写数据

3.解密已加密数据库

解密已加密的数据库已封装为以下方法:

/**
 解密数据库(保留原有数据库)
 */
+ (BOOL)unencryptDatabase:(NSString *)origPath toPath:(NSString *)toPath {
    sqlite3 *db;
    if (sqlite3_open([origPath UTF8String], &db) == SQLITE_OK) {
        char *err = NULL;
        sqlite3_exec(db, [[NSString stringWithFormat:@"PRAGMA key = '%@';", kDatabaseEncryptKey] UTF8String], NULL, NULL, &err);
        sqlite3_exec(db, [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS plaintext KEY '';", toPath] UTF8String], NULL, NULL, NULL);
        sqlite3_exec(db, "SELECT sqlcipher_export('plaintext');", NULL, NULL, &err);
        sqlite3_exec(db, "DETACH DATABASE plaintext;", NULL, NULL, &err);
        sqlite3_close(db);
        
        return err ? NO : YES;
    } else {
        sqlite3_close(db);
        NSLog(@"Open db failed:%s", sqlite3_errmsg(db));
        return NO;
    }
}

这里也有两点需要注意的地方:
1、这里必须使用全路径
2、解密后的queue必须同步更换为FMDatabaseQueue,不然无法读写数据

4、SQLCipher性能分析

本次性能分析主要从两个维度下手:
1.对比无加密数据库和加密数据库的插入数据速度;
2.加密已有数据的数据库耗时。

本次测试都基于iOS10.3的iPad mini2下进行。

1.对比插入数据速度

规则:对比无加密数据库和加密数据库使用事务时,在插入1w条、5w条和10w条8个字段的数据的耗时,每组数据测试5次,最后取平均值。最后的测试结果如下图所示:

插入测试结果

结论:两者相差在4%左右,在移动设备上存储大数据的情况很少,所以我认为是完全可以接受的。

2.测试加密数据库耗时

规则:对比存储1w条、5w条和10w条数据的数据库加密的耗时,每组数据测试5次,最后取平均值。最后的测试结果如下图所示:

加密耗时测试结果

结论:加密过程需要一定的时间,且跟数据库所存储的数据量有关,数据量越大耗时越长,从本次的测试结果来看,加密存储10w条数据的数据库耗时1.38s是可以接受的。

5、采用SQLCipher存在的风险

1、SQLCipher依赖本地存储的字符串进行数据库的加密和解密,如果这个字符串被泄漏出去,本地的数据库依然容易被解密,进而被读取里面的信息。可采用KeyChain保存?
2、加密已存储大数据的数据库会消耗一定的时间,这段时间怎么处理才能让用户无感知?加密失败了怎么处理?
3、欢迎大家补充。。。

6、本文demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,601评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,367评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,249评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,539评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,967评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,929评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,098评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,825评论 0 207
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,609评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,796评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,282评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,603评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,277评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,159评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,959评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,079评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,874评论 2 277

推荐阅读更多精彩内容