应用的数据存储方案和安全性研究
假设我的项目名称是 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"];[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加密常用于请求校验,文件传输校验等。是不可逆加密。

浙公网安备 33010602011771号