NSuserdefaults 是一种IOS常用的数据持久化的方式,操作简便,配合NSCoding 和NSKeyedArchiver,很容易将数据model转化成NSData直接存储在NSuserdefaults。那使用NSuserdefaults保存数据,数据的安全性如何呢?
NSuserdefaults 的本质是使用了plist存储数据,将存储在NSuserdefaults中的数据写入了一个以Bundle Identifier的plist中。
下面是一个简单的例子。
@interface TestInfo : NSObject<NSCoding>
@property (nonatomic,retain) NSString *username;
@property (nonatomic,retain) NSString *phone;
@property (nonatomic,retain) NSString *ticket;
@property (nonatomic,retain) NSString *email;
@property (nonatomic,retain) NSString *passport;
//混淆
@property (nonatomic,retain) NSString *uiofdsaouiSHJ;
@property (nonatomic,assign) BOOL isLGBT;
@property (nonatomic,retain) NSNumber *age;
-(void)saveLoginInfo;
@end
@implementation TestInfo
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self=[self init]) {
self.username=[aDecoder decodeObjectForKey:@"username"];
self.uiofdsaouiSHJ=[aDecoder decodeObjectForKey:@"uiofdsaouiSHJ"];
self.phone = [aDecoder decodeObjectForKey:@"phone"];
self.email = [aDecoder decodeObjectForKey:@"email"];
self.passport = [aDecoder decodeObjectForKey:@"passport"];
self.ticket = [aDecoder decodeObjectForKey:@"ticket"];
self.isLGBT = [[aDecoder decodeObjectForKey:@"passport"] boolValue];
self.age = [aDecoder decodeObjectForKey:@"ticket"];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_username forKey:@"username"];
[aCoder encodeObject:_uiofdsaouiSHJ forKey:@"uiofdsaouiSHJ"];
[aCoder encodeObject:_phone forKey:@"phone"];
[aCoder encodeObject:_email forKey:@"email"];
[aCoder encodeObject:_passport forKey:@"passport"];
[aCoder encodeObject:_ticket forKey:@"ticket"];
[aCoder encodeObject:[NSNumber numberWithBool:_isLGBT] forKey:@"isLGBT"];
[aCoder encodeObject:_age forKey:@"age"];
}
-(void)saveLoginInfo{
NSUserDefaults *cache=[NSUserDefaults standardUserDefaults];
NSString *gameKeyStr = [NSString stringWithFormat:@"CC_userinfos"];
NSData *logininfo=[NSKeyedArchiver archivedDataWithRootObject:self];
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:logininfo];
[cache setObject:array forKey:gameKeyStr];
}
@end
当打开plist时,可以看到存储下来的数据按照 NSuserdefaults 写入的数据类型被保存下来。
其中在例子代码中使用saveLoginInfo保存的数据,如下所示。看是在plist中保存时二进制的数据,那么此段数据是否很容易被破解呢?
<62706c69 73743030 d4010203 04050623 24582476 65727369 6f6e5824
6f626a65 63747359 24617263 68697665 72542474 6f701200 0186a0ab
07080f0d 0910110a 1b1c1d55 246e756c 6cd9090a 0b0c0d0e 0f101112
13141516 1718191a 5570686f 6e655674 69636b65 74566973 4c474254
5624636c 6173735d 75696f66 6473616f 75695348 4a536167 65587573
65726e61 6d655565 6d61696c 58706173 73706f72 74800480 07800880
0a800380 09800280 05800609 1017d21e 1f20215a 24636c61 73736e61
6d655824 636c6173 73657358 54657374 496e666f a2202258 4e534f62
6a656374 5f100f4e 534b6579 65644172 63686976 6572d125 2654726f
6f748001 00080011 001a0023 002d0032 00370043 0049005c 00620069
00700077 00850089 00920098 00a100a3 00a500a7 00a900ab 00ad00af
00b100b3 00b400b6 00bb00c6 00cf00d8 00db00e4 00f600f9 00fe0000
00000000 02010000 00000000 00270000 00000000 00000000 00000000 0100>
按照encodeWithCoder来说,NSObject使用了NSCoder进行了encode。按照道理讲,在不知道类参数的情况下,是无法从这一段二进制数据中恢复出原来被加密的数据。
但是实际操作会是如何呢?假设在只拿到这个plist文件,不知道TestInfo情况下。进行逐步分析。
NSString *infoPath = [[NSBundle mainBundle] pathForResource:@"Info" ofType:@"plist"];
NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile:infoPath];
NSArray *cachearray=[data objectForKey:@"CC_userinfos"];
if (cachearray) {
for (int i=0; i<cachearray.count; i++) {
NSData *data=[cachearray objectAtIndex:i];
NSString *logininfo=[NSKeyedUnarchiver unarchiveObjectWithData:data];
}
}
首先将plist数据复制到info.plist中,读入内存转化为NSData,因为不知道数据从什么类型转化来的,就先用NSString作为数据类型。
NSString *logininfo=[NSKeyedUnarchiver unarchiveObjectWithData:data];
不出意味,因为类型不匹配,报错,但是从出错信息中,可以看出原来这段数据的类型为TestInfo。
2016-05-05 20:02:03.260 PanNaviControllerDemo[48761:1143922] *** Terminating app due to uncaught exception ‘NSInvalidUnarchiveOperationException‘, reason: ‘*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (TestInfo) for key (root); the class may be defined in source code or a library that is not linked‘
把这段数据编码为NSString会怎么样?
NSString *str =[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(@"%@",str);
2016-05-05 20:11:05.130 NSUserDefaultsDataSaveDemo[48898:1151203] bplist00?&‘T$topX$objectsX$versionY$archiver?Troot€?
U$nullù
XpassportVticketUphoneSage]uiofdsaouiSHJV$classUemailVisLGBTXusername
因为OC语言的特点,参数命名上讲究可读性,所以在这段NSSting中大概可以猜出TestInfo的若干属性(大写字母分隔),如passport、ticket、phone、age、email、username。
[uiofdsaouiSHJ、isLGBT]是我故意写的两个命名不规范的属性。
大概猜出属性的名字后,重新initWithCoder方法,便得到TestInfo的明文。
因为对NSuserdefaults保存数据有疑虑,做了以上的试验。证明了NSuserdefaults数据保存安全性差,在使用encodeObject之前,需要使用自己的加密算法进行加密,即使被分析出类后,也不至于读出明文数据。