對iOS數(shù)據(jù)安全的一次小探索

一、主流保證數(shù)據(jù)安全的方式

1、網(wǎng)絡(luò)傳輸安全

1.采用HTTPS通信協(xié)議
可防止抓包竊取、篡改傳輸數(shù)據(jù),大大增加了中間人攻擊的成本。

2.對敏感數(shù)據(jù)進(jìn)行簽名校正
采用非對稱加密的方式,對敏感數(shù)據(jù)使用密鑰加密,到了客戶端用公鑰解密,驗(yàn)證數(shù)據(jù)一致性,防止通信過程中被篡改。

3.采用密文傳輸
別人即使截取了傳輸信息,也無法看懂其中的意思。

2、本地存儲數(shù)據(jù)安全

1.數(shù)據(jù)庫加密
通過越獄設(shè)備可以很容易地把整個(gè)應(yīng)用包(包括里面的數(shù)據(jù))copy出來,這樣就可以獲取里面的數(shù)據(jù)庫,對于沒有加密的數(shù)據(jù)庫就可以非常輕易地讀取里面的信息,造成信息的泄漏。
數(shù)據(jù)庫加密可分兩個(gè)維度:1.整個(gè)數(shù)據(jù)庫加密;2.對部分字段先加密再存數(shù)據(jù)庫。
對部分字段加密并不適合多字段的加密存儲,容易導(dǎo)致加密數(shù)據(jù)太過分散,影響性能。所以推薦對整個(gè)數(shù)據(jù)庫加密。

2.KeyChain存儲敏感數(shù)據(jù)
KeyChain是iOS系統(tǒng)級的存儲方式,安全性無需質(zhì)疑,且刪除應(yīng)用或者升級系統(tǒng)依然可以保留里面的信息。

3、源碼安全

使用越獄設(shè)備可以很輕易地把應(yīng)用砸殼,從而把源碼dump下來,即使是沒有太多經(jīng)驗(yàn)的開發(fā)者也可以得到應(yīng)用的類信息,包括函數(shù)名等。使用IDA等反編譯工具可以看到應(yīng)用的一些類名和方法名,進(jìn)而可以分析功能實(shí)現(xiàn)的邏輯。

1.字符串混淆
對應(yīng)用程序中使用到的字符串進(jìn)行加密,保證源碼被逆向后也能保護(hù)明文字符串。

2.類名、方法名混淆
市面上很多iOS應(yīng)用都沒有混淆類名方法名,以致于很容易使用class-dump下來,從而進(jìn)行hook操作,一步一步實(shí)現(xiàn)iOS微信自動搶紅包(非越獄)是很有趣的一個(gè)應(yīng)用。這個(gè)庫可以混淆OC的類名、協(xié)議、屬性還有方法名。

3.程序結(jié)構(gòu)混淆加密
對應(yīng)用程序邏輯結(jié)構(gòu)進(jìn)行打亂混排,保證源碼可讀性降到最低。可參考這個(gè)庫

4.反調(diào)試、反注入等一些主動保護(hù)策略
加入第三方安全性SDK

二、對數(shù)據(jù)庫加密的研究

1、主流數(shù)據(jù)庫加密方式

The SQLite Encryption Extension(收費(fèi))

SQLiteEncrypt(收費(fèi))

SQLiteCrypt(收費(fèi))

SQLCipher(開源免費(fèi))

只有SQLCipher是免費(fèi)的,所以本文主要對SQLCipher進(jìn)行研究。

2、引入SQLCipher第三方庫

1.手動引入
請參考官方教程

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

3、SQLCipher的可行性研究

1.新建加密數(shù)據(jù)庫

若沒有舊數(shù)據(jù)的情況下使用很簡單,只需要在FMDatabase里面的-open-openWithFlags:方法里面添加[self setKey:kDatabaseEncryptKey];即可。
如下圖所示

添加密鑰

但是在團(tuán)隊(duì)協(xié)作中,如果直接修改pod倉庫里面的文件,可能不好同步,下面有一個(gè)技巧,就是繼承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 {
        // 設(shè)置密鑰
        [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 {
        // 設(shè)置密鑰
        [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的子類只需要重載一個(gè)方法

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

使用的時(shí)候只需要更改下面的語句

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

2.加密已有數(shù)據(jù)庫

加密已有數(shù)據(jù)庫我已經(jīng)封裝成下面的方法:

/**
 加密數(shù)據(jù)庫(保留原有數(shù)據(jù)庫)
 */
+ (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;
    }
}

這里有兩點(diǎn)需要注意的地方:
1、這里必須使用全路徑
2、加密后的queue必須同步更換為FMEncryptDatabaseQueue,不然無法讀寫數(shù)據(jù)

3.解密已加密數(shù)據(jù)庫

解密已加密的數(shù)據(jù)庫已封裝為以下方法:

/**
 解密數(shù)據(jù)庫(保留原有數(shù)據(jù)庫)
 */
+ (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;
    }
}

這里也有兩點(diǎn)需要注意的地方:
1、這里必須使用全路徑
2、解密后的queue必須同步更換為FMDatabaseQueue,不然無法讀寫數(shù)據(jù)

4、SQLCipher性能分析

本次性能分析主要從兩個(gè)維度下手:
1.對比無加密數(shù)據(jù)庫和加密數(shù)據(jù)庫的插入數(shù)據(jù)速度;
2.加密已有數(shù)據(jù)的數(shù)據(jù)庫耗時(shí)。

本次測試都基于iOS10.3的iPad mini2下進(jìn)行。

1.對比插入數(shù)據(jù)速度

規(guī)則:對比無加密數(shù)據(jù)庫和加密數(shù)據(jù)庫使用事務(wù)時(shí),在插入1w條、5w條和10w條8個(gè)字段的數(shù)據(jù)的耗時(shí),每組數(shù)據(jù)測試5次,最后取平均值。最后的測試結(jié)果如下圖所示:

插入測試結(jié)果

結(jié)論:兩者相差在4%左右,在移動設(shè)備上存儲大數(shù)據(jù)的情況很少,所以我認(rèn)為是完全可以接受的。

2.測試加密數(shù)據(jù)庫耗時(shí)

規(guī)則:對比存儲1w條、5w條和10w條數(shù)據(jù)的數(shù)據(jù)庫加密的耗時(shí),每組數(shù)據(jù)測試5次,最后取平均值。最后的測試結(jié)果如下圖所示:

加密耗時(shí)測試結(jié)果

結(jié)論:加密過程需要一定的時(shí)間,且跟數(shù)據(jù)庫所存儲的數(shù)據(jù)量有關(guān),數(shù)據(jù)量越大耗時(shí)越長,從本次的測試結(jié)果來看,加密存儲10w條數(shù)據(jù)的數(shù)據(jù)庫耗時(shí)1.38s是可以接受的。

5、采用SQLCipher存在的風(fēng)險(xiǎn)

1、SQLCipher依賴本地存儲的字符串進(jìn)行數(shù)據(jù)庫的加密和解密,如果這個(gè)字符串被泄漏出去,本地的數(shù)據(jù)庫依然容易被解密,進(jìn)而被讀取里面的信息??刹捎肒eyChain保存?
2、加密已存儲大數(shù)據(jù)的數(shù)據(jù)庫會消耗一定的時(shí)間,這段時(shí)間怎么處理才能讓用戶無感知?加密失敗了怎么處理?
3、歡迎大家補(bǔ)充。。。

6、本文demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容