对 iOS 数据安全的一次小探索

一、主流保证数据安全的方式 1、网络传输安全 1.采用HTTPS通信协议 可防止抓包窃取、篡改传输数据,大大增加了中间人攻击的成本。 2.对敏感数据进行签名校正 采用非对称加密的方式,对敏感数据使用密钥加密,到了客户端用公钥解密,验证数据一致性,防止通信过程中被篡改。 3.采用密文传输 别人即使截取了传输信息,也无法看懂其中的意思。 2、本地存储数据安全 1.数据库加密 通过越狱设备可以很容易地把整个应用包(包括里面的数据)copy出来,这样就可以获取里面的数据库,对于没有加密的数据库就可以非常轻易地读取里面的信息,造成信息的泄漏。 数据库加密可分两个维度:1.整个数据库加密;2.对部分字段先加密再存数据库。 对部分字段加密并不适合多字段的加密存储,容易导致加密数据太过分散,影响性能。所以推荐对整个数据库加密。 2.KeyChain存储敏感数据 KeyChain是iOS系统级的存储方式,安全性无需质疑,且删除应用或者升级系统依然可以保留里面的信息。 3、源码安全 使用越狱设备可以很轻易地把应用砸壳,从而把源码dump下来,即使是没有太多经验的开发者也可以得到应用的类信息,包括函数名等。使用IDA等反编译工具可以看到应用的一些类名和方法名,进而可以分析功能实现的逻辑。 1.字符串混淆 对应用程序中使用到的字符串进行加密,保证源码被逆向后也能保护明文字符串。 2.类名、方法名混淆 市面上很多iOS应用都没有混淆类名方法名,以致于很容易使用class-dump下来,从而进行hook操作,一步一步实现iOS微信自动抢红包(非越狱)是很有趣的一个应用。这个库可以混淆OC的类名、协议、属性还有方法名。 3.程序结构混淆加密 对应用程序逻辑结构进行打乱混排,保证源码可读性降到最低。可参考这个库 4.反调试、反注入等一些主动保护策略 加入第三方安全性SDK 二、对数据库加密的研究 1、主流数据库加密方式 The SQLite Encryption Extension(收费) SQLiteEncrypt(收费) SQLiteCrypt(收费) SQLCipher(开源免费) 只有SQLCipher是免费的,所以本文主要对SQLCipher进行研究。 2、引入SQLCipher第三方库 1.手动引入 请参考官方教程。 2.使用CocoaPods pod 'FMDB/SQLCipher', '2.5' 3、SQLCipher的可行性研究 1.新建加密数据库 若没有旧数据的情况下使用很简单,只需要在FMDatabase里面的-open和-openWithFlags:方法里面添加[self setKey:kDatabaseEncryptKey];即可。 如下图所示 但是在团队协作中,如果直接修改pod仓库里面的文件,可能不好同步,下面有一个技巧,就是继承FMDatabase和FMDatabaseQueue并重载其中的方法。 继承FMDatabase的子类需要重载以下方法 - (BOOL)open { if (_db) { return YES; } int err = sqlite3_open([self sqlitePath], &_db ); if(err != SQLITE_OK) { NSLog(@"error opening!...

March 30, 2017 · Darren Ou

利用树莓派 + RetroPie 做一个怀旧游戏机

一、前言 偶尔会好怀念小时候跟朋友一起玩小霸王的日子,那时候很少家里有游戏机的人,一台游戏机有好几个朋友在等着玩,那时候能玩的东西不多,但日子过得简单而开心。虽说现在万能的淘宝还可以买到小霸王游戏机,但是远远不如自己做一个出来的成就感来的强。下面就一步一步把小时候的游戏机做出来。 二、准备 1、树莓派+SD卡 2、显示器、游戏手柄、键盘等外设 三、下载RetroPie镜像 RetroPie是一个开源的游戏模拟器,可以让你的树莓派或者PC变成一个复古游戏机,它集成了任天堂红白机、GameBoy、SNES等著名游戏系统,具有很高的可玩性。 你可以在Raspbian直接安装RetroPie,但是会耗费很长的时间,所以我在这里选择直接下载RetroPie镜像。 下载地址:https://retropie.org.uk/download/ 四、往SD卡写入RetroPie镜像 PC上可以使用 Win32DiskImager 工具写入镜像; Mac系统下可以用 ApplePi-Baker 工具; 具体写入方法及下载地址网上都很容易找到,在这里就不再赘述。 把已经写入RetroPie镜像的SD卡插入树莓派并连接电源。 五、配置 1.设置输入设备 我们的游戏手柄有时候并没有那么多个键位,我们在设置的时候怎么跳过一个键位的设置呢?这里有个小技巧:长按“下”键即可跳过。 设置游戏手柄键位 2.连接WiFi 设置好键位后,我们就可以进入主界面,上面有“13 GAMES AVAILABLE”字样,这并不是意味着里面就有13个游戏可以马上玩的,应该理解为内置了13个游戏模拟器,这时候我们怎么样才能开始玩呢? 初始主界面 按“A”键,进入设置界面,点击WIFI,连接你电脑同一个的局域网。 连接局域网 3.传输roms 打开PC,点击“计算机”上面的地址栏,输入“\retropie\roms”,即可进入游戏roms文件夹,把下载好的游戏rom拉到对应的模拟器文件夹,如.nes后缀的游戏rom拉进对应的nes文件夹。 模拟器文件夹 4.重启模拟器 重启模拟器 重启模拟器后就可以看到主界面多了一个选项,选择游戏就可以开始你的怀旧之旅了! 多了一个选项 超级马里奥 六、其他配置 有人会发现自己的SD卡的内存明明很大,但是没有塞多少个游戏内存就满了,可以进行下面的配置“扩展”内存: 进入RetroPie的设置界面,进入RASPI-CONFIG RASPI-CONFIG 选择第一项Expand Filesystem,重启树莓派即可扩展内存了。 另外,你还可以在这里进行修改登录密码、国际化选项、设置超频等操作。 树莓派设置界面 七、祝你玩得开心😊

March 12, 2017 · Darren Ou

iOS 性能优化汇总

参考:https://i.cmgine.net/archives/14896.html 入门: 1、用ARC管理内存 2、在正确的地方使用reuseIdentifier 3、尽量吧views设置为完全不透明 4、避免过于庞大的xib 5、不要阻塞主线程 6、不要在ImageView中调整图片大小,尽量使用UIImageView大小相同的图片 7、选择正确的Collection Arrays: 有序的一组值。使用index来lookup很快,使用value lookup很慢, 插入/删除很慢。 Dictionaries: 存储键值对。 用键来查找比较快。 Sets: 无序的一组值。用值来查找很快,插入/删除很快。 8、打开gzip压缩 中级: 1、重用和懒加载views 2、Cache,缓存不大可能改变但是需要经常读取的东西(NSCache) 3、权衡渲染方法(View?CALayer?CoreGraphics?OpenGL?) 4、处理内存警告 在app delegate中使用applicationDidReceiveMemoryWarning:的方法 在你的自定义UIViewController的子类(subclass)中覆盖didReceiveMemoryWarning 注册并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知 5、重用大开销对象(如NSDateFormatter,NSCalendar) 6、使用Sprite Sheets 7、避免反复处理数据 解析JSON会比XML更快一些,JSON也通常更小更便于传输。从iOS5起有了官方内建的JSON deserialization 就更加方便使用了。 但是XML也有XML的好处,比如使用SAX 来解析XML就像解析本地文件一样,你不需像解析json一样等到整个文档下载完成才开始解析。当你处理很大的数据的时候就会极大地减低内存消耗和增加性能。 8、正确设定背景图片 使用UIColor的 colorWithPatternImage来设置背景色; 在view中添加一个UIImageView作为一个子View。(不使用“imageName:”) 9、减少使用Web特性 10、设定Shadow Path view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath]; 11、优化UITableView 正确使用reuseIdentifier来重用cells 尽量使所有的view opaque,包括cell自身 避免渐变,图片缩放,后台选人 缓存行高 如果cell内现实的内容来自web,使用异步加载,缓存请求结果 使用shadowPath来画阴影 减少subviews的数量 尽量不适用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后缓存结果 使用正确的数据结构来存储数据 尽量使用rowHeight,sectionFooterHeight和sectionHeaderHeight来设定固定的高,不要请求delegate 12、选择正确的数据存储选项 使用NSUerDefaults 使用XML, JSON, 或者 plist 使用NSCoding存档...

February 11, 2017 · Darren Ou

事件传递:响应者链条(译)

当你设计你的应用程序时,你可能需要动态的响应事件。例如,一个触摸事件可以由屏幕上的很多对象产生,你必须决定由哪个对象响应对应的事件和明白该对象如何接收到事件的。 当用户产生了一个事件,UIKit创建了一个包含处理事件所需信息的事件对象。然后把这个事件对象放置在全局事件队列。对于触摸事件,会被包装成一个UIEvent对象。对于动作事件,会根据你所使用的框架和你感兴趣的动作事件而有所不同。 一个事件沿着一个具体的路径向前传递,直到有一个对象可以对其进行处理。首先, UIApplication单例对象会从事件队列里获取一个事件并把它分发,一贯地将事件发送到应用的key window对象,key window对象会把事件传递到原始对象进行处理,原始对象会根据事件的类型而有所不同。 · 触摸事件。对于触摸事件,window对象首先会尝试传递事件到产生触摸事件的视图。这个视图被称为hit-test视图,寻找hit-test视图的过程被称为hit-testing。 · 动作和远程控制事件。window对象会把摇一摇动作或者远程控制事件发送到第一响应者进行处理。 这些事件路径的最终目标是寻找一个对象可以处理和相应一个事件。因此,UIKit首先会事件发送到最适合处理该事件的对象。最适合处理触摸事件的对象就是hit-test视图,对于其他事件,对象就是第一响应者。接下来的部分会详细展开hit-test视图和第一响应者对象是如何被确认的。 Hit-Testing返回触摸事件发生的视图 iOS利用hit-tesing寻找被触摸的视图。hit-tesing需要检查一个触摸事件是否在相关的视图对象的范围内,如果是,它会递归地检查这个视图对象的所有子视图。处于视图层级中包含触摸点的最底层视图就会成为hit-test视图。当iOS确定hit-test视图后,它会把触摸事件抛给这个视图去处理。 例如,假如用户触摸视图E(图2-1)。iOS通过以下顺序从子视图中找出hit-test视图 1.触摸点在视图A里面,所以会检查子视图B和C。 2.触摸点不在视图B的范围内,但是在视图C的范围内,所以它会检查子视图D和E。 3.触摸点不在视图D,但在视图E的范围内。 视图E是在视图层级中包含触摸点的最底层视图,所以它就成为了hit-test视图 (图2-1) (hitTest:withEvent:)方法返回一个给定CGPoint和UIEvent的hit-test视图。(hitTest:withEvent:)方法通过(pointInside:withEvent:)方法调用。如果一个点通过(hitTest:withEvent:)方法判断是在视图的范围内,(pointInside:withEvent:)方法就会返回YES。然后这个方法就会在返回YES的子视图上面递归地调用(hitTest:withEvent:)方法。 如果这个点通过(hitTest:withEvent:)方法判断不在视图的范围内,最先调用的(pointInside:withEvent:)方法就会返回NO。这个点被忽略了,(hitTest:withEvent:)方法就会返回nil。如果一个子视图返回NO,那么在视图层级中整个分支都会被忽略,因为如果这个触摸点不在这个视图中产生,那么也不会再这个视图的子视图中产生。这就意味着一些在父视图外的子视图的点不能接收到触摸事件,因为触摸点必须在父视图和子视图的范围内。子视图的范围超出父视图的情况会发生在子视图的clipsToBounds属性被设为NO。 注意:一个触摸对象的生命周期会跟它的hit-test密切关联,即使后来触摸移到view的外面。 hit-test视图有第一次机会去处理一个触摸事件。如果hit-test视图不能处理一个事件,这个事件就会在视图的响应者链条上传递,直到系统找到一个可以找到该事件的对象。 响应者链条由响应者对象组成 很多类型的事件依赖一个响应者链条进行传递。响应者链条是一系列的响应者对象。由第一个响应者开始,并由application对象结束。如果第一响应者不能处理一个事件,事件会向前传递给响应者链条的下一个响应者。 响应者对象是一个可以处理事件的对象。UIResponder类是所有响应者对象的基类,它定义的程序接口不只是为了事件的处理,而且为了普通的响应者行为。UIApplication,UIViewController和UIView的实例都可以成为响应者,意味着所有的视图和很多的关键控制器对象都可以是响应者。注意:核心动画图层都不可以成为响应者。 首先由第一响应者接收事件。一般的,第一项响应者是一个视图对象。一个对象通过以下两种方式成为第一响应者: 1、复写(canBecomeFirstResponder)方法并返回YES。 2、接收一个(becomFirstResponder)信息。如果有必要,一个对象可以发送这条信息给它自己。 注意:一个对象成为第一响应者前确保应用已经把这个对象的图层渲染出来。例如,我们一般在(viewDidAppear:)方法里面调用(becomeFirstResponder)方法,如果尝试在(viewWillAppear:)方法里面分配第一响应者,对象的图层还没有渲染出来,所以(becomeFirstResponder)方法就会返回NO。 事件不是唯一的依赖响应者链条的对象。响应者链条被用于以下的情况: · 触摸事件。如果hit-test视图不能处理一个触摸事件,那么这个事件就会从hit-test视图开始沿着响应者链条传递。 · 动作事件。通过UIKit来处理摇一摇动作事件,第一响应者必须实现UIResponder类的(motionBegan:withEvent:)方法或者(motionEnded:withEvent:)方法之一。 · 远程控制事件。第一响应者处理远程控制事件必须实现UIResponder类的(remoteControlReceivedWithEvent:)方法。 · 动作信息。

January 13, 2017 · Darren Ou