应用的数据存储方案和安全性研究

假设我的项目名称是 savedata

bundle identifier  是com.savedata.www

 

讲到iOS系统的数据存储,首先要讲下iOS的沙盒机制。

  • 每一个APP都有一个存储空间,就是沙盒。

  • 每个应用程序只能访问自己的目录,不能相互通信。

  • iOS沙盒主要包括下面几个文件:

上面就是我们开发中常说的沙盒操作的路径。

实际上每个应用程序都是一个Bundle,在Finder中,它是一个包含了nib文件,编译代码,以及其他资源的目录. 我们把这个目录叫做程序的main bundle.

[NSBundle mainBundle]是获得NSBundle的一个单例对象,这个单例对象封装了pathForResource的方法,可以获取我们App打包后的路径。所以我们获取Bundle里面资源的方式为:

NSString *imageStr = [[NSBundle mainBundle] pathForResource:@"pander" ofType:@"jpg"];

UIImage *image = [UIImage imageWithContentsOfFile:imageStr];

(注意:这里涉及到内存方面的问题,此处就不展开讲)

应用的Bundle在Finder以 项目名.app的文件存在( savedata.app ),他和系统分配给应用的沙盒不在同一目录下,但是存在映射关系。

 

下面讲讲开发过程中对沙盒的一些操作和注意事项:

苹果的备份规则:

1.app的home目录下的所有东西都会被备份,除了应用Bundle本身、缓存目录和temp目录。

2.已购买的音乐、应用、书籍、Camera Roll、设备设置、主屏幕、App组织、消息、铃声也都会被备份。

 

1,Documents

  保存持久化数据,会备份。一般用来存储需要持久化的数据。项目中,我们会吧一些用户的登录信息进行存储,以及搜索历史记录等等一些关键数据。

  目前,我去彩票站和彩E通的用户数据就是存在此目录下。

  路径获取:

  NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
  NSString *documentPath = [paths objectAtIndex:0];

2,Library

  a,Caches  缓存,iTunes不会备份该目录。内存不足时会被清除,应用没有运行时,可能会被清除,。一般存储体积大、不需要备份的非重要数据。

  路径获取:NSString *cachesDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];

  b,Preferences 保存持久化数据,会备份(注意:NSUserDefaults就在此文件夹,并且是以 bundle id .plist命名,例:com.savedata.www.plist)

  路径获取:NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];

3,SystemData  

  没有提供路径获取的API,官方文档也没有相应说明,是否会备份有兴趣的自己验证,暂时不讨论(17年下半年才新加的路径)

  路径获取:

4,tmp  

  临时文件夹,iTunes不会备份这个目录,用来保存临时数据,应用退出时会清除该目录下的数据。临时保存的数据但不需要长期保留使用可以放到此文件夹。

  路径获取:NSString *tmpPath = NSTemporaryDirectory();

说明:所有的路径都可以用以下两个方法拼接获取

NSString *homePath = NSHomeDirectory();

NSString *homePath_Document1 = [homePath stringByAppendingString:@"/Documents"];

NSString *homePath_Document2 = [homePath stringByAppendingPathComponent:@"Documents"];

 

说明2:

正常情况下来说,文件存储都按以上规则来存储。但是某些情况下,会存在一些需要持续化存储,却考虑到iCould空间大小等因素而不需要备份的文件。

这时候(Documents下的文件),就需要对文件进行 非备份 标记。

在iOS版本5.0.1标记方法

 

//#import <sys/xattr.h>

 

- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString

 

{

 

    if ([[NSFileManager defaultManager] fileExistsAtPath: filePathString]) {

 

        const char* filePath = [filePathString fileSystemRepresentation];

 

        const char* attrName = "com.apple.MobileBackup";

 

        u_int8_t attrValue = 1;

 

        int result = setxattr(filePath, attrName, &attrValue, sizeof(attrValue), 0, 0);

 

        return result == 0;

 

    }

 

}

在iOS版本5.1+标记方法

 

 

- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString

 

{

 

    if ([[NSFileManager defaultManager] fileExistsAtPath: filePathString]) {

 

        NSURL* URL= [NSURL fileURLWithPath: filePathString];

 

        NSError *error = nil;

 

        BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]

 

                                      forKey: NSURLIsExcludedFromBackupKey error: &error];

 

        if(!success){

 

            NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);

 

        }

 

        return success;

 

    }else{

 

        return NO;

 

    }

 

}

 

 

  

下面进入正题,数据存储的几种方式

数据持久化:所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。

iOS中常用的存储方式:

property list(属性列表)

Preference(偏好设置)

NSKeyedArchiver(归档)

SQLite3/FMDB(嵌入式数据库)

CoreData(面向对象的嵌入式数据库)

 

Keychain(钥匙串)

 

 

1,property list(属性列表,plist文件)

plist文件是将某些特定的类,通过XML文件的方式保存在目录中。

可以被序列化的类型只有如下几种:

NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
 
例子:
获得文件路径
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"savedata_test.plist"];
存储
NSArray *array = @[@"123", @"456", @"789"];
//写入前   可以进行文件是否存在的检测 [[NSFileManager defaultManager] fileExistsAtPath:filename]
[array writeToFile:fileName atomically:YES];

读取

NSArray *result = [NSArray arrayWithContentsOfFile:fileName];

 

注意:

  • 只有以上列出的类型才能使用plist文件存储。

  • 存储时使用writeToFile: atomically:方法。 其中atomically表示是否需要先写入一个辅助文件,再把辅助文件拷贝到目标文件地址。这是更安全的写入文件方法,一般都写YES。

    • 读取时使用以下系列方法 (实际上,这里获取的三个数组都是可变数组,但是后两个API显示的是NSArray类型,所以用可变去接收会报警告,但是操作不会报错,字典也是同理)

        NSMutableArray *array1 = [[NSMutableArray alloc]initWithContentsOfFile:fileName];

          NSArray *array2 = [[NSArray alloc]initWithContentsOfFile:fileName];

          NSArray *array3 = [NSArray arrayWithContentsOfFile:fileName];

 

2,Preference(偏好设置)

  • 偏好设置是专门用来保存应用程序的配置信息的,一般不要在偏好设置中保存其他数据。

  • 如果没有调用synchronize方法,系统会根据I/O情况不定时刻地保存到文件中。所以如果需要立即写入文件的就必须调用synchronize方法。

 

3,NSKeyedArchiver(归档)

只要遵循了NSCoding协议的对象都可以通过它实现序列化。由于决大多数支持存储数据的Foundation和Cocoa Touch类都遵循了NSCoding协议,因此,对于大多数类来说,归档相对而言还是比较容易实现的。归档生成的文件是加密的。归档的本质是对象转化为数据字节,以文件的形式存储在磁盘上。

归档方式有以下三种:
a. 对Foundation框架中对象进行归档
b. 对自定义的内容进行归档
c. 对自定义的对象进行归档

 

a. 对Foundation框架中对象进行归档:

- (void)test2{

    // 获取文件路径

    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

    // 拼接路径

    NSString *dataPath = [documentsPath stringByAppendingPathComponent:@"NewData.data"];

    // 待归档的数据

    NSArray *dataArr = @[@"1",@"2"];

    // 归档

    if ([NSKeyedArchiver archiveRootObject:dataArr toFile:dataPath]) {

        NSLog(@"Archiver success !");

    }

    // 解档

    NSArray *data = [NSKeyedUnarchiver unarchiveObjectWithFile:dataPath];

    NSLog(@"%@%@",data[0],data[1]);

}

 

b. 对自定义的内容进行归档:

- (void)test3{

    // 获取文件路径

    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

    // 拼接路径

    NSString *dataPath = [documentsPath stringByAppendingPathComponent:@"NewData.data"];

    // 创建存储数据的NSData对象

    NSMutableData *dataM = [NSMutableData data];

    // 创建归档对象

    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:dataM];

    // 添加归档内容

    [archiver encodeObject:@"小明" forKey:@"name"];

    [archiver encodeObject:@"男" forKey:@"sex"];

    [archiver encodeInteger:19 forKey:@"age"];

    // 完成归档内容

    [archiver finishEncoding];

    // 写入磁盘

    if ([dataM writeToFile:dataPath atomically:YES]) {

        NSLog(@"Archiver success !");

    }

    // 解档

    // 获取数据

    NSData *data = [NSData dataWithContentsOfFile:dataPath];

    // 创建解档对象

    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

    // 解档

    NSString *name = [unarchiver decodeObjectForKey:@"name"];

    NSString *sex = [unarchiver decodeObjectForKey:@"sex"];

    NSInteger age = [unarchiver decodeIntForKey:@"age"];

}


c. 对自定义的对象进行归档:(常用)

遵循NSCoding协议

NSCoding协议声明了两个方法,这两个方法都是必须实现的。一个用来说明如何将对象编码到归档中,另一个说明如何进行解档来获取一个新对象。

.h

@interface MyArchiverModel : NSObject<NSCoding>

@property (nonatomic,strong) NSString  *stuName;

@property (nonatomic,assign) NSInteger  stuAge;

@property (nonatomic,assign) BOOL       isGirl;

@end

 

.m

@implementation MyArchiverModel

//反归档

- (instancetype)initWithCoder:(NSCoder *)aDecoder{

//    self=[super initWithCoder:aDecoder;

    if (self = [super init]) {

        _stuName = [aDecoder decodeObjectForKey:@"stuName"];

        _stuAge  = [aDecoder decodeIntegerForKey:@"stuAge"];

        _isGirl  = [aDecoder decodeBoolForKey:@"isGirl"];

    }

    return self;

}

//归档

- (void)encodeWithCoder:(NSCoder *)aCoder{

//    [super encodeWithCoder:aCoder];

    [aCoder encodeObject:_stuName forKey:@"stuName"];

    [aCoder encodeInteger:_stuAge forKey:@"stuAge"];

    [aCoder encodeBool:_isGirl forKey:@"isGirl"];

}

@end

 

- (void)test1{

    // 获取Documents的路径

    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

    // 拼接路径

    NSString *path = [documentPath stringByAppendingPathComponent:@"student.data"];

    //需要归档的对象

    MyArchiverModel *student1 = [[MyArchiverModel alloc]init];

    student1.stuName = @"小微";

    student1.stuAge = 18;

    student1.isGirl = YES;

    //归档

    [NSKeyedArchiver archiveRootObject:student1 toFile:path];

    //解档

    MyArchiverModel *student = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

}

缺点:在归档的形式来保存数据,只能一次性归档保存以及一次性解压,所以只能针对小量数据。而且对数据操作比较笨拙,即如果想改动数据的某一小部分,还是需要解压整个数据或者归档整个数据。

扩展:当自定义模型里面包括另一个自定义模型的时候,如果需要使用归档,所有的自定义模型都必须实现其对应的NSCoding协议。

@interface SecondModel : NSObject<NSCoding>

@property (nonatomic,strong) NSString *name;

@property (nonatomic,strong) MyArchiverModel *model;

@end

 

注:因为数据库文件不可以直接打开查看表数据情况,这里提供一种查看方式

打开db                                      sqlite3 MyDB.db

展示db文件中的tables               .tables

展示某个table的字段构成(schema)         .schema tableName

执行sql语句                    select * from tableName where ...;

展示结果的时候显示顶部column名称       .head on

 

4,SQLite3/FMDB(嵌入式数据库)

见DEMO 

5,CoreData(面向对象的嵌入式数据库)

见DEMO 

6,Keychain

见DEMO  

 

附件地址:DataTestDemo.zip

 

 

数据加密 与 数据安全性问题(1,本地数据的加密存储   2,网络传输数据的加密)

 

因为考虑到 逆向 和 越狱 对沙盒文件的直接访问,所以沙盒里面的以明文形式存储的文件并不是绝对安全的。

所以对于重要的用户信息,需要进行加密存储。

以上介绍的所有的存储方法,只有 NSKeyedArchiver的对自定义对象的归档是安全的。

分析:1,归档文件首先就是以加密形式存在的

2,只有自定义对象的归档 文件的解档方法是开发者所自己定义的,在不知道此方法前,是不能解档成功的。

所以,自定义对象的归档,可以视为数据安全的存储方式之一。

其次:在开发过程中,一般直接是对敏感信息进行加密存储,这也是目前运用最为广泛的手段。

 

加密方式一般分为 对称加密和非对称加密。

对称加密:算法公开,使用唯一密钥 加密和解密。(私钥双方持有)

对称加密常用的算法有:DES算法,3DES算法,TDEA算法,Blowfish算法,RC5算法,IDEA算法。

 

 

非对称加密:非对称加密算法需要两个密钥来进行加密和解密,这两个秘钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。(算法公开,公钥公开,私钥一方持有)

非对称加密的常用算法:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)等。(目前 我去彩票站 和 彩e通就是用的RSA算法)

 

对于具体选用哪种算法,需要根据项目需求选择,本地存储方案,我个人偏向于DES和3DES算法。

 说明:MD5加密常用于请求校验,文件传输校验等。是不可逆加密。

 

posted on 2018-03-26 19:04  冰棍超人  阅读(350)  评论(0)    收藏  举报

导航