当前位置:   article > 正文

SQLite 加密、解密、迁移_sqlite密码添加移除

sqlite密码添加移除

数据库迁移


在做数据库迁移前需要考虑的几个问题:

  1. 是直接安装新版本(不涉及数据迁移问题)还是从旧版本迁移过来的;
  2. 用户在用的是版本1,还是版本2,还是版本xxx;
  3. 迁移的操作应该放在哪里;

问题1:

所有创建数据库的代码都是最新结构的代码,因为直接安装新版本是不存在数据迁移的问题,要做的把从旧版本升级上来的表结构保持和新版本的一致,并且保留原来的内容,或者更新你需要更新的内容。

问题2:

如果用户没有及时更新,错过了好几个版本的数据库升级,那么数据库的升级需要一步步的向上升级,比如最新版本是 V4,当前用户的版本是 V2,升级顺序应该是 V2 -> V3 -> V4。

解决:对每个数据库版本的迁移提供一个依次递增的版本号,这个标识主要是用来检查是否需要做数据库迁移,和要做那些版本的数据迁移,可以用NSUserDefaults或者把当前版本放到数据库中。

问题3:

应用应该放在 didFinishLaunching,SDK 应该放在启动的方法里面。


思路:

一般app启动之后,都有一个初始化的过程。此外后续app升级,还需要考虑数据迁移。所以初始化和数据迁移的框架,在初期的版本就要考虑好

总结一下我们的app采取的方案:

1、在持久化的文件夹内(比如UserDefaults或者Documents目录),用一个字段保存老版本号

2、在开始初始化之前,读取老版本号,以及当前版本号

3、如果该应用是第一次加载,那么老版本号就取不到(因为是初次加载,这个字段还没有保存),那么就可以执行初始化过程;如果取到了老版本号,就不执行初始化

4、初始化完成之后,执行数据迁移。因为有老版本号和新版本号,所以可以通过对比,实现增量式的迁移

5、上述动作都完成之后,刷新老版本号

6、下次正常启动,就不会再初始化,也不会执行数据迁移了;如果是安装新版本,由于当前版本号刷新,又会触发数据迁移

用户切换账户的场景:

上面说的是比较简单的场景。如果应用允许多用户切换账号,而且不同用户的数据是分离的,就更复杂一些

首先标识老版本号的字段不能保存在UserDefaults里,因为UserDefaults是用户共享的。这样当A用户初始化之后,老版本号就存在了。切换到B用户,发现老版本号已存在,则不会执行初始化,其实这时候B用户的数据文件还没有创建好。所以需要把老版本号存在单独的地方,比如每个用户各自的sqlite文件中

然后,读取老版本号的时候,也要根据用户的独立标识去查询

改进:

目前暂时是把老版本号保存在sqlite里,但是这样首次读取的时候,判断逻辑比较麻烦。需要判断sqlite文件是否存在,然后要判断table有没有,最后才能取值。如果用文本保存可能会稍微方便一点,比存在sqlite里,少了一个判断table是否存在的步骤

实例代码: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
-(BOOL) needInit
{
     return  [oldVersion isEqual: @ "0" ];
}
 
-(NSString*) oldVersion
{
     return  oldVersion;
}
 
-(NSString*) currentVersion
{
     return  currentVersion;
}
 
#pragma mark - private  method
 
-( void ) initOldVersion
{
     // 数据库文件不存在,oldVersion设为0
     NSFileManager *fileManager = [NSFileManager defaultManager];
     NSString *dbFilePath = [YLSGlobalUtils getDatabaseFilePath];
     if (![fileManager fileExistsAtPath:dbFilePath]){
         oldVersion = @ "0" ;
         return ;
     }
     
     // 数据库文件打开失败,oldVersion设为0
     FMDatabase *db = [FMDatabase databaseWithPath:dbFilePath];
     if (![db open]){
         oldVersion = @ "0" ;
         return ;
     }
     
     // tb_clientstage表不存在,oldVersion设为0
     int  tableCount = 0 ;
     FMResultSet *rs = [db executeQuery:@ "select count(*) as count from sqlite_master where type='table' and name='tb_clientstage'" ];
     if ([rs next]){
         tableCount = [rs intForColumn:@ "count" ];
     }
     if (tableCount == 0 ){
         oldVersion = @ "0" ;
         [db close];
         return ;
     }
     
     // 设置oldVersion
     rs = [db executeQuery:@ "select * from tb_clientstage where id = '1' and tableno = '0'" ];
     if ([rs next]){
         oldVersion = [rs stringForColumn:@ "version" ];
     } else {
         oldVersion = @ "0" ;
     }
     [db close];
}
 
-( void ) initCurrentVersion
{
     NSDictionary* infoDict =[[NSBundle mainBundle] infoDictionary];
     NSString* versionNum =[infoDict objectForKey:@ "CFBundleVersion" ];
     currentVersion = versionNum;
}


然后,是否进行初始化的判断:

1
2
3
4
5
6
7
8
clientInfo = [YLSClientInfo new ];
 
if ([clientInfo needInit]){
     [self createEverythingForFirstTime]; // 初始化时才执行
}
[self allTheTime]; // 任何时候都执行
 
[migrationHelper doMigration:clientInfo];


增量迁移:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-( void ) doMigration:(YLSClientInfo*)clientInfo
{
     NSString *oldVersion = [clientInfo oldVersion];
     NSString *currentVersion = [clientInfo currentVersion];
     
     // 正常登陆,不需要数据迁移
     if ([oldVersion isEqualToString:currentVersion]){
         return ;
     }
     
     // 全新安装,非升级,不需要数据迁移
     if ([oldVersion isEqualToString:@ "0" ]){
         return ;
     }
     
     // 以下均是版本升级,需要数据迁移
     if ([oldVersion isEqualToString:@ "1.0.0" ]){
         [script1 doMigration];
         [script2 doMigration];
         [script3 doMigration];
         [script4 doMigration];
         return ;
     }
     
     // 其他的情况
}

使用第三方库:

使用FMDBMigrationManager数据库迁移


数据库加减密

使用SQLite数据库的时候,有时候对于数据库要求比较高,特别是在iOS8.3之前,未越狱的系统也可以通过工具拿到应用程序沙盒里面的文件,这个时候我们就可以考虑对SQLite数据库进行加密,这样就不用担心sqlite文件泄露了

通常数据库加密一般有两种方式

  1. 对所有数据进行加密
  2. 对数据库文件加密

第一种方式虽然加密了数据,但是并不完全,还是可以通过数据库查看到表结构等信息,并且对于数据库的数据,数据都是分散的,要对所有数据都进行加解密操作会严重影响性能,通常的做法是采取对文件加密的方式

iOS 免费版的sqlite库并不提供了加密的功能,SQLite只提供了加密的接口,但并没有实现,iOS上支持的加密库有下面几种

前三种都是收费的,SQLCipher是开源的,这里我们使用SQLCipher

集成

如果你使用cocoapod的话就不需要自己配置了,为了方便,我们直接使用FMDB进行操作数据库,FMDB也支持SQLCipher

pod ‘FMDB/SQLCipher’, ‘~> 2.6.2’

打开加密数据库

使用方式与原来的方式一样,只需要数据库open之后调用setKey设置一下秘钥即可
下面摘了一段FMDatabase的open函数,在sqlite3_open成功后调用setKey方法设置秘钥

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

为了不修改FMDB的源代码,我们可以继承自FMDatabase类重写需要setKey的几个方法,这里我继承FMDatabase定义了一个 FMEncryptDatabase 类,提供打开加密文件的功能(具体定义见 Demo

      
      
@interface FMEncryptDatabase : FMDatabase
+ ( instancetype)databaseWithPath:( NSString*)aPath encryptKey:( NSString *)encryptKey;
- ( instancetype)initWithPath:( NSString*)aPath encryptKey:( NSString *)encryptKey;
@end

用法与FMDatabase一样,只是需要传入secretKey

SQLite数据库加解密

SQLCipher提供了几个命令用于加解密操作

加密
      
      
$ ./sqlcipher plaintext.db
sqlite> ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'testkey';
sqlite> SELECT sqlcipher_export('encrypted');
sqlite> DETACH DATABASE encrypted;
  1. 打开非加密数据库
  2. 创建一个新的加密的数据库附加到原数据库上
  3. 导出数据到新数据库上
  4. 卸载新数据库
解密
      
      
$ ./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;
  1. 打开加密数据库
  2. 创建一个新的不加密的数据库附加到原数据库上
  3. 导出数据到新数据库上
  4. 卸载新数据库

代码操作

  1. /** encrypt sqlite database to new file */
  2. + (BOOL)encryptDatabase:(NSString *)sourcePath targetPath:(NSString *)targetPath encryptKey:(NSString *)encryptKey
  3. {
  4. const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS encrypted KEY '%@';", targetPath, encryptKey] UTF8String];
  5. sqlite3 *unencrypted_DB;
  6. if (sqlite3_open([sourcePath UTF8String], &unencrypted_DB) == SQLITE_OK) {
  7. char *errmsg;
  8. // Attach empty encrypted database to unencrypted database
  9. sqlite3_exec(unencrypted_DB, sqlQ, NULL, NULL, &errmsg);
  10. if (errmsg) {
  11. NSLog(@"%@", [NSString stringWithUTF8String:errmsg]);
  12. sqlite3_close(unencrypted_DB);
  13. return NO;
  14. }
  15. // export database
  16. sqlite3_exec(unencrypted_DB, "SELECT sqlcipher_export('encrypted');", NULL, NULL, &errmsg);
  17. if (errmsg) {
  18. NSLog(@"%@", [NSString stringWithUTF8String:errmsg]);
  19. sqlite3_close(unencrypted_DB);
  20. return NO;
  21. }
  22. // Detach encrypted database
  23. sqlite3_exec(unencrypted_DB, "DETACH DATABASE encrypted;", NULL, NULL, &errmsg);
  24. if (errmsg) {
  25. NSLog(@"%@", [NSString stringWithUTF8String:errmsg]);
  26. sqlite3_close(unencrypted_DB);
  27. return NO;
  28. }
  29. sqlite3_close(unencrypted_DB);
  30. return YES;
  31. }
  32. else {
  33. sqlite3_close(unencrypted_DB);
  34. NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(unencrypted_DB));
  35. return NO;
  36. }
  37. }
  38. /** decrypt sqlite database to new file */
  39. + (BOOL)unEncryptDatabase:(NSString *)sourcePath targetPath:(NSString *)targetPath encryptKey:(NSString *)encryptKey
  40. {
  41. const char* sqlQ = [[NSString stringWithFormat:@"ATTACH DATABASE '%@' AS plaintext KEY '';", targetPath] UTF8String];
  42. sqlite3 *encrypted_DB;
  43. if (sqlite3_open([sourcePath UTF8String], &encrypted_DB) == SQLITE_OK) {
  44. char* errmsg;
  45. sqlite3_exec(encrypted_DB, [[NSString stringWithFormat:@"PRAGMA key = '%@';", encryptKey] UTF8String], NULL, NULL, &errmsg);
  46. // Attach empty unencrypted database to encrypted database
  47. sqlite3_exec(encrypted_DB, sqlQ, NULL, NULL, &errmsg);
  48. if (errmsg) {
  49. NSLog(@"%@", [NSString stringWithUTF8String:errmsg]);
  50. sqlite3_close(encrypted_DB);
  51. return NO;
  52. }
  53. // export database
  54. sqlite3_exec(encrypted_DB, "SELECT sqlcipher_export('plaintext');", NULL, NULL, &errmsg);
  55. if (errmsg) {
  56. NSLog(@"%@", [NSString stringWithUTF8String:errmsg]);
  57. sqlite3_close(encrypted_DB);
  58. return NO;
  59. }
  60. // Detach unencrypted database
  61. sqlite3_exec(encrypted_DB, "DETACH DATABASE plaintext;", NULL, NULL, &errmsg);
  62. if (errmsg) {
  63. NSLog(@"%@", [NSString stringWithUTF8String:errmsg]);
  64. sqlite3_close(encrypted_DB);
  65. return NO;
  66. }
  67. sqlite3_close(encrypted_DB);
  68. return YES;
  69. }
  70. else {
  71. sqlite3_close(encrypted_DB);
  72. NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(encrypted_DB));
  73. return NO;
  74. }
  75. }
  76. /** change secretKey for sqlite database */
  77. + (BOOL)changeKey:(NSString *)dbPath originKey:(NSString *)originKey newKey:(NSString *)newKey
  78. {
  79. sqlite3 *encrypted_DB;
  80. if (sqlite3_open([dbPath UTF8String], &encrypted_DB) == SQLITE_OK) {
  81. sqlite3_exec(encrypted_DB, [[NSString stringWithFormat:@"PRAGMA key = '%@';", originKey] UTF8String], NULL, NULL, NULL);
  82. sqlite3_exec(encrypted_DB, [[NSString stringWithFormat:@"PRAGMA rekey = '%@';", newKey] UTF8String], NULL, NULL, NULL);
  83. sqlite3_close(encrypted_DB);
  84. return YES;
  85. }
  86. else {
  87. sqlite3_close(encrypted_DB);
  88. NSAssert1(NO, @"Failed to open database with message '%s'.", sqlite3_errmsg(encrypted_DB));
  89. return NO;
  90. }
  91. }

总结

SQLCipher使用起来还是很方便的,基本上不需要怎么配置,需要注意的是,尽量不要在操作过程中修改secretKey,否则,可能导致读不了数据,在使用第三方库的时候尽量不去修改源代码,可以通过扩展或继承的方式修改原来的行为,这样第三方库代码可以与官方保持一致,可以跟随官方版本升级,具体代码可以到我的github上下载咯

参考

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号