最近一直在反思之前的项目,发现了很多问题.比如数据安全...
虽然项目需求是只展示最新的数据,所以几乎没用什么本地存储.除了通讯录和用户的Token.
用户通讯录另表,今天反思下用户的Token的存储,我直接用<Preferences>存在了本地.一旦被非法获取,配合API借口,后果不堪设想...
就像这样.
/* * 正如其名,还是存储些用户的设置比较好~ */ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; //存 [userDefaults setObject:@"USERSTOKEN" forKey:@"token"]; [userDefaults synchronize];// return bool value //取 NSString *Token = [userDefaults stringForKey:@"token"];
后来有和后端讨论是不是该用MD5,后端表示不需要阿~(FUCK TJ)
不过转念一下,也对.都能读取到你的token了,加密存储也没什么用,大不了连你加密的一起复制就行...想了想,不如加点salt吧.
//需要向服务器验证token的时候调用 +(NSString *)MD5EncryptWith:(NSString*)code { NSString *SaltCode = [NSString stringWithFormat:@"%@%@",code,[self daySalt]]; const char *cStr = [SaltCode UTF8String]; unsigned char result[16]; CC_MD5(cStr, (CC_LONG)strlen(cStr), result); NSMutableString *hash = [NSMutableString string]; for (int i = 0; i < 16; i++){ [hash appendFormat:@"%02X", result[i]]; } return [hash lowercaseString]; } +(NSString *)daySalt { NSDate * senddate=[NSDate date]; NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init]; //精确到小时,如果通过局域网抓包获得token,能保证一小时后失效,当然也可以精确到分,秒.(需要服务器配合) [dateformatter setDateFormat:@"YYYYMMddhh"]; return [dateformatter stringFromDate:senddate]; }
弄完这些我就后悔了.我只要换个存储方式就能解决了..比如keychain..
// // KeyChainIO.h // // Created by M on 16/1/13. // Copyright © 2016年 Meng. All rights reserved. // #import <Foundation/Foundation.h> @interface KeyChainIO : NSObject +(void)SaveToken:(NSString *)Token; +(id)ReadToken; +(void)DeleteToken; @end
// // KeyChainIO.m // // Created by M on 16/1/13. // Copyright © 2016年 Meng. All rights reserved. // #import "KeyChainIO.h" @implementation KeyChainIO static NSString * const KEY_IN_KEYCHAIN = @"com.m1989.info"; static NSString * const KEY_Token = @"com.m1989.token"; +(void)SaveToken:(NSString *)Token { NSMutableDictionary *usernamepasswordKVPairs = [NSMutableDictionary dictionary]; [usernamepasswordKVPairs setObject:Token forKey:KEY_Token]; [self save:KEY_IN_KEYCHAIN data:usernamepasswordKVPairs]; } +(id)ReadToken { NSMutableDictionary *usernamepasswordKVPair = (NSMutableDictionary *)[self load:KEY_IN_KEYCHAIN]; return [usernamepasswordKVPair objectForKey:KEY_Token]; } +(void)DeleteToken { [self delete:KEY_IN_KEYCHAIN]; } #pragma mark ========================== + (NSMutableDictionary *)getKeychainQuery:(NSString *)service { return [NSMutableDictionary dictionaryWithObjectsAndKeys: (__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass, service, (__bridge_transfer id)kSecAttrService, service, (__bridge_transfer id)kSecAttrAccount, (__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible, nil]; } + (void)save:(NSString *)service data:(id)data { //Get search dictionary NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; //Delete old item before add new item SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery); //Add new object to search dictionary(Attention:the data format) [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData]; //Add item to keychain with the search dictionary SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL); } + (id)load:(NSString *)service { id ret = nil; NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; //Configure the search setting [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData]; [keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit]; CFDataRef keyData = NULL; if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) { @try { ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData]; } @catch (NSException *e) { NSLog(@"Unarchive of %@ failed: %@", service, e); } @finally { } } return ret; } + (void)delete:(NSString *)service { NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery); } @end
+(void)SaveToken:(NSString *)Token;
+(id)ReadToken;
+(void)DeleteToken;
能满足需求了.
换成keychain,只能说本地暂时安全了.如果接入了不安全的WiFi,照旧会被干.
所以结合MD5再加点salt,客户端发送请求的时候,是自身token结合当前时间进行MD5加密.
服务端做效验的时候,也是取出数据库存储的Token+客服端发送过来的时间进行MD5加密后进行对比
或许能更安全点..或者直接https,只不过有没有过度设计?
其他的,非对称加密RSA,对称加密AES,DES再等着你.