上个项目的一些反思 I
最近一直在反思之前的项目,发现了很多问题.比如数据安全...
虽然项目需求是只展示最新的数据,所以几乎没用什么本地存储.除了通讯录和用户的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[]; CC_MD5(cStr, (CC_LONG)strlen(cStr), result); NSMutableString *hash = [NSMutableString string]; for (int i = ; i < ; 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,只能说本地暂时安全了(后来和一做网络安全聊过,如果目标iPhone没有被破解(越狱),其实存储位置和方式是无所谓的,因为iOS的沙盒机制,应用间是彼此独立的,附keychain导出,http://blog.jobbole.com/58832/).
不过如果WiFi被劫持了,或者用户接入了不安全的网络,通过一些抓包软件,也是很容易造成用户数据的泄露.
所以结合MD5再加点salt,客户端发送请求的时候,是自身token结合当前时间(时间戳)进行MD5加密.
服务端做效验的时候,也是取出数据库存储的Token+客服端发送过来的时间进行MD5加密后进行对比
或许能更安全点..或者直接https,只不过有没有过度设计?
其他的,非对称加密RSA,对称加密AES,DES再等着你.
另外Base64是编码方式,不是加密方式.它是可以被解码的...
上个项目的一些反思 I的更多相关文章
- 上个项目的一些反思 II
上个项目需要使用通讯录,我在回顾自己设计的时候,发现自己少设计了cache这一环. 虽然直接用SQLite在初期体验上没什么大损失,不过可以预想通讯录增长到一定数量后势必会影响体验. 单例模式,全局缓 ...
- 上个项目的一些反思 III
离线缓存 之前的项目因为实时性要求比较高,所以一打开客户端,就开始做网络请求.现在想想,是没有做内容的离线缓存.这个问题,我没意识到.产品经理也没有意识到... 我觉得Archiver,来做比较合适, ...
- git上传项目代码到github
参考: git学习——上传项目代码到github github上传时出现error: src refspec master does not match any解决办法 git 上传本地文件到gith ...
- GitHub的用法:到GitHub上部署项目
先提供两个较好的Git教程: 1. 如何在github部署项目: lhttp://jingyan.baidu.com/article/656db918fbf70ce381249c15.html 2. ...
- 如何从eclipse中下载并导入Github上的项目
eclipse导入项目,方法就是点击File ->Import,选择Existing Projects into Workspace 但前提是,你导入的这个项目原本就是用eclipse的构建的, ...
- 参与github上开源项目的大致流程和注意事项
Foreword github是一个很火的代码托管服务网站,可能好多人都想参与一两个项目玩一玩学习一下,但由于是纯英文的网站,可能又会止步于想法上没有动手实践.接下来我就介绍一下参与github上开源 ...
- 在GitHub上管理项目
在GitHub上管理项目 新建repository 本地目录下,在命令行里新建一个代码仓库(repository) 里面只有一个README.md 命令如下: touch README.md git ...
- Eclipse-将svn上的项目转化成相应的项目
这里假设svn上的项目为maven项目 首先从svn检出项目 其中项目名称code可自己定义更改新的名称 从svn检出的项目结构 然后将项目转化成相关的项目 转换加载中 加载/下载 maven相关内容 ...
- 利用gitbash上传项目到github
GitHub主要是用作基于Git的分布式版本管理系统的库,可以保存和管理自己的代码,而且主要用作代码的合作开发.不过对于我来说,Git控制系统还比较难以掌握,或者开发小系统还不太用得着,因此我把Git ...
随机推荐
- SQLMAP大全
转自:http://blog.csdn.net/zsf1235/article/details/50974194 本人小白,初次认识sqlmap,很多都是转载资料,只是学习研究一下! 练习的网站可以用 ...
- PAT 1041. 考试座位号(15)
每个PAT考生在参加考试时都会被分配两个座位号,一个是试机座位,一个是考试座位.正常情况下,考生在入场时先得到试机座位号码,入座进入试机状态后,系统会显示该考生的考试座位号码,考试时考生需要换到考试座 ...
- usb驱动开发24之接口驱动
从第一节我们已经知道,usb_generic_driver在自己的生命线里,以一己之力将设备的各个接口送给了linux的设备模型,让usb总线的match函数,也就是usb_device_match, ...
- Python-06-面向对象(基础篇)
面向对象编程--Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机 ...
- echarts在.Net中使用实例(二) 使用ajax动态加载数据
通过上一篇文章可以知道和echarts参考手册可知,series字段就是用来存储我们显示的数据,所以我们只需要用ajax来获取series的值就可以. option 名称 描述 {color}back ...
- GHOST急速安装win10或win7
首先说说写这篇博客的原因,我自己曾经被装各种系统弄得焦头烂额,各种刻光盘光驱安装,写优盘安装以及pe盘恢复系统等等,每次都各种方式尝试一下,太浪费时间了,所以天真的想着能不能有一个类似"一劳 ...
- 搭建一套自己实用的.net架构(4)【CodeBuilder-RazorEngine】
工欲善其事必先利其器, 下面来说说代码生成器. 现在代码生成器品种繁多各式各样, 什么codesmith.T4. 动软也算.那么每款代码生成器都有自己模板解析引擎. 现在比较流行的 NVelocit ...
- SqlMetal.exe ORM代码生成
作甚? 先说说这个工具是干啥的,我们所做的程序,或多或少需要存储一些数据到数据库,当然直接使用Sql语句也可以,甚至有些情况下就是要使用sql语句,但对于一些基本的增删改查,对每张表都要写查询语句就显 ...
- WPF中RadioButton绑定数据的正确方法
RadioButton一般用于单选的时候,也就是从一组值中选择一个值. 比如性别有“男”和“女”两种取值,而对于一个员工的实例来说,性别的取值要么是男,要么是女. 这种时候一般就会用到RadioBut ...
- 【Python】[进程和线程]多进程,多线程,ThreadLocal,进程VS.线程,分布式进程
1.多进程,multiprocessing模块, 进程间的通信:Queue[队列],Pipes[管子]2.多线程, 注意:线程公用变量,混乱 解决方法Lock:因为只有一个锁,所以当要执 ...