ioios jenkins keychainn安全吗

【iOS 安全攻防】
我的图书馆
【iOS 安全攻防】
iOS安全攻防(二十四):敏感逻辑的保护方案(1)Objective-C代码容易被hook,暴露信息太赤裸裸,为了安全,改用C来写吧!当然不是全部代码都要C来写,我指的是敏感业务逻辑代码。本文就介绍一种低学习成本的,简易的,Objective-C逻辑代码重构为C代码的办法。也许,程序中存在一个类似这样的类:@interface XXUtil : NSObject
+ (BOOL)isVerifi...
iOS安全攻防(二十三):Objective-C代码混淆class-dump可以很方便的导出程序头文件,不仅让攻击者了解了程序结构方便逆向,还让着急赶进度时写出的欠完善的程序给同行留下笑柄。所以,我们迫切的希望混淆自己的代码。混淆的常规思路混淆分许多思路,比如:1)花代码花指令,即随意往程序中加入迷惑人的代码指令2)易读字符替换等等防止class-dump出可读信息的有效办法是易读字符替换。Obj...
static和被裁的符号表为了不让攻击者理清自己程序的敏感业务逻辑,于是我们想方设法提高逆向门槛。本文就介绍一个防御技巧————利用static关键字裁掉函数符号。原理如果函数属性为 static ,那么编译时该函数符号就会被解析为local符号。在发布release程序时(用Xcode打包编译二进制)默认会strip裁掉这些函数符号,无疑给逆向者加大了工作难度。验证写个demo验证一下上述理论,...
废除应用程序的ASLR特性ASLR (Address Space Layout Randomization),即地址空间随机布局。大部分主流的操作系统都已实现了ASLR,以防范对已知地址进行恶意攻击。iOS从4.3开始支持ASLR,Android从4.0也支持了ASLR机制。ASLR的存在,给iOS系统越狱造成了很大的困难,某些不完美越狱方案就是因为攻破不了或者绕不开ASLR,所以每次重新启动后地...
越狱检测的攻与防在应用开发过程中,我们希望知道设备是否越狱,正以什么权限运行程序,好对应采取一些防御和安全提示措施。iOS7相比之前版本的系统而言,升级了沙盒机制,封锁了几乎全部应用沙盒可以共享数据的入口。即使在越狱情况下,限制也非常多,大大增加了应用层攻击难度。比如,在iOS7之前,我们可以尝试往沙盒外写文件判断是否越狱,但iOS7越狱后也无该权限,还使用老方法检测会导致误判。那么,到底应该如何...
基于脚本实现动态库注入MobileSubstrate可以帮助我们加载自己的动态库,于是开发者们谨慎的采取了对MobileSubstrate的检索和防御措施。那么,除了依靠MobileSubstrate帮忙注入dylib,还有别的攻击入口吗?理理思路,条件、目的很明确:1)必须在应用程序启动之前,把dylib的环境变量配置好2)dylib的位置必须能被应用程序放问到3)最后再启动应用程序啊哈,原汁原...
数据保护API题外话开篇先扯几句题外话,许多朋友都问我怎么不写防啊,我确实有点犹豫。hackers总是想象如果自己是开发者会怎么写,然后才能找到入手点。同理,开发者们也要想象自己是hackers会怎么做,才能采取相应的防御措施。然后,就是一场递归的博弈。拿越狱检测这件事来说,起初大家只需判断有无安装Cydia就好了,hackers们说好,那我就不安装Cydia也可以动手脚。开发者们又说,那你一定得...
Fishhook众所周知,Objective-C的首选hook方案为Method Swizzle,于是大家纷纷表示核心内容应该用C写。接下来进阶说说iOS下C函数的hook方案,先介绍第一种方案————fishhook&.什么是fishhookfishhook是facebook提供的一个动态修改链接Mach-O符号表的开源工具。什么是Mach-OMach-O为Mach Object文件格式的缩写,...
使用introspy追踪分析应用程序如果你已阅读了《iOS安全攻防》系列专栏之前的文章,一定已经对静态以及运行时分析app有了一定的了解。我们可以借助的分析工具很多,工具和工具之间一般没有什么优劣比较性,完全看个人习惯什么擅长什么。多个工具多条路,那么本文将介绍追踪分析利器introspy。对应iOS系统版本,下载适用的introspy工具包:introspy下载地址传送门下载后,将其拷贝到设备中...
使用iNalyzer分析应用程序好想用 doxygen 画iOS app的class继承关系。有没有比 class-dump-z 更直观的分析工具?利器 iNalyzer 隆重登场~一、iNalyzer的安装在iPhone端:1)进入cydia添加源 /cydia/2)搜索 iNalyzer 并安装二、Doxygen和Graphviz的安装在Mac端:b...
Hack实战——支付宝app手势密码校验欺骗在 iOS安全攻防(十一):Hack实战——探究支付宝app手势密码&中,介绍了如何利用gdb分析app,确定了支付宝app的手势密码格式为字符串,9个点分别对应。在 iOS安全攻防(十二):iOS7的动态库注入&中,介绍了如果利用越狱大神们为我们开辟的iOS7动态库注入方法。本文将继续深入hack实战,hook支付宝手势密码校验操作,...
数据擦除对于敏感数据,我们不希望长时间放在内存中,而希望使用完后立即就被释放掉。但是不管是ARC还是MRC,自动释放池也有轮循工作周期,我们都无法控制内存数据被擦除的准确时间,让hackers们有机可乘。本文介绍一个小技巧——及时数据擦除。假如一个View Controller A的一个数据被绑在一个property上,@interface WipingMemoryViewController :...
iOS7的动态库注入iOS系统不断升级,结构不断调整,所以我们可以利用的动态库注入方法也根据系统版本的不同而不同。在此之前,我们可以利用环境变量 DYLD_INSERT_LIBRARY 来添加动态库,iOS7被成功越狱后,我们需要自己去探索实践iOS7动态库注入的方式。本文将在iOS7.0.4环境下,以 hook 支付宝app 程序中&ALPLauncherController 的视图加载方法为例...
Hack实战——探究支付宝app手势密码在之前的&iOS安全攻防(七):Hack实战——解除支付宝app手势解锁错误次数限制&中,留了一个问题,就是如何破解手势密码。方法不唯一,本文介绍如何利用gdb分析破解app。当没有程序源代码的情况下,我们如何利用gdb呢?为了确定应该如何设置断点,不得不反汇编程序来作为参考了。在前面的文章提到过,支付宝app的手势密码校验处理非常严谨,没有抛出BOOL判断...
二进制和资源文件自检我们把自己的程序发布到app store,但是不能保证每一个用户都是从app store下载官方app,也不能保证每一个用户都不越狱。换句话说,我们无法保证程序运行环境在MAC管控策略下就绝对的安全。所以,在有些情况下,尤其是和钱有关系的app,我们有必要在和服务器通信时,让服务器知道客户端到底是不是官方正版的app。何以判断自己是不是正版app呢?hackers们破解你的ap...
使用Keychain-Dumper导出keychain数据iOS系统及第三方应用都会使用Keychain来作为数据持久化存储媒介,或者应用间数据共享的渠道。所以Keychain数据库是hacker们最关注的数据源头之一。不知道是算幸运还是不幸,导出Keychain数据库数据的工具早已非常完善,下载地址:Keychain-Dumper传送门操作步骤极其简单:1)赋予Keychain数据库可读权限Pr...
键盘缓存与安全键盘大部分中文应用弹出的默认键盘是简体中文输入法键盘,在输入用户名和密码的时候,如果使用简体中文输入法键盘,输入英文字符和数字字符的用户名和密码时,会自动启动系统输入法自动更正提示,然后用户的输入记录会被缓存下来。系统键盘缓存最方便拿到的就是利用系统输入法自动更正的字符串输入记录。缓存文件的地址是:/private/var/mobile/Library/Keyboard/dynami...
Hack实战——解除支付宝app手势解锁错误次数限制之前仅仅介绍了工具的使用,本文将实践一下如何利用 cycript 结合 class-dump 结果hack,还要牺牲一下支付宝app。首先,老套路,取到手势解锁界面的View Controller:cy# var app = [UIApplication sharedApplication]
cy# var keyWindow = app...
使用class-dump-z分析支付宝app为了了解支付宝app的源码结构,我们可以使用class-dump-z工具来分析支付宝二进制。1.下载配置class_dump_z前往 /p/networkpx/wiki/class_dump_z ,下载tar包,然后解压配置到本地环境$ tar -zxvf class-dump-z_0.2a.tar.gz
使用Cycript修改支付宝app运行时Cycript: Objective-JavaScript ,它懂Objective-C,也懂javascript。我们能够借助Cycript使用Objective-C或者javascript ,给某个正在运行的进程的runtime发送消息。本文以修改支付宝app界面为例,介绍Cycript的使用方法。1)安装Cycript到Cycript官方网站下载资源工...
馆藏&23696
TA的最新馆藏
喜欢该文的人也喜欢今天博客的主题是Keychain, 在本篇博客中会通过一个登陆的Demo将用户名密码存入到KeyChain中,并且查看一下KeyChain中存的是什么东西,把这些内容给导出来。当然本篇博客的重点不是如何使用Keychain来存储你的用户名和密码了。不过今天的博客中会用到这些知识。Apple的开发着文档上有Keychain的完整使用实例(请参见)。今天博客中用户名和密码的存储和更新就是使用的官方文档给的示例。也就是说,直接拷贝的开发文档上的代码呢,这些代码今天就不往上拷贝了,具体的看官方的开发文档即可。
今天博客的主题是,将用户名和密码进行存储,然后在下次登录的时候从钥匙串进行加载。当然keychain在同一个组中是可以共享的,也就是说同一个开发者账号下的App可以进行keychain的共享的。我们先不将用户名密码进行加密,直接存在keychain中,然后看一下效果。在然后就得使用一些加密策略对用户名密码进行处理了。下方会给出具体的实现细节。
一、KeyChain的使用
KeyChain的使用在此呢不做多说,因为我之前也没怎么用过Keychain呢,今天博客中关于KeyChain操作的代码主要来自于苹果的开发文档。下方截图中的内容,是对keyChain的操作进行了一个简单的封装,keychainData用于暂存将要存储到keychain中的数据。genericPasswordQuery则用于从KeyChain中查询数据,kKeychainItemIdentifier就是我们存储在keyChain中的数据的唯一标示了。
其他关于KeyChain的操作代码都可以在上述连接的开发文档中找到,在此就不做过多赘述了。
封装完KeyChainManager后,我们就可以调用这个类的单例来进行KeyChain的操作了。下方就是KeyChainManager的使用方式,从下方截图中,可以看出,将AES加密的代码给注释掉了,也就是传给KeyChainManager什么内容,就会存储什么内容的。关于Keychain的使用就先说这么多,还是那句话,上面的Apple官方文档讲的很清楚了,在此就不做过多赘述了。
二、查看Keychain数据库中的内容
在越狱手机中我们是可以查看Keychain中所存储的内容的。keychain在我们是以数据库的形式存储在设备上的,存储目录为&/private/var/Keychains&。刚好我旁边就有越狱设备,下方就是keychain的存储位置,下方这个keychain-2.db正是钥匙串存储内容的数据库。
既然我们找到了这个文件的话,那么我们可以将他拷贝到Mac上,可以看看其存储的是神马内容。将上述文件考到Mac上后,我们可以使用SQLiteManager打开,可以看一下里边的内容。下方是keychain-2.db这个数据库中的结构,其中有5张表,我们可以重点关注一下genp这张表的结构和内容。
下方这个截图是对keychainData字典中的数据进行的打印,也就是说下方的数据是从Keychain中查询出来的。我们可以看出下方字典的key与genp表中的字段是相对应的,所以喽,我们存在keychain中的一些数据实际上是存储在genp这张表中的。
下方是我们对相关数据进行的查询,表中的数据是非常的多的,不过我们有SQL语句不是,可以根据自己的信息对数据进行查看。当然从这直接看到的一些数据即使你存入Keychain中是没有加密的数据,在表中有些数据是以二进制的形式存储的,直接看也是看不出什么的,那么我们就需要Keychain-dump这个工具了,下方回有介绍。
三、使用Snoop-it来抓取相应app中Keychain中的数据
我们可以使用Snoop-it来查看一些App中的数据,在此我们就以我自己写的Demo为例。之前我们存在Keychain中的数据,在代码中没有做任何的处理,就以字符串的形式直接存储的,那么接下来我们就要使用Snoop-it来查看这些数据。
1.简述Snoop-it的配置与使用
Snoop-it这个工具非常强大,查看keychain中存储的数据是其功能之一,接下来就来认识一下这个工具。首先在你的Cydia商店中把这个(http://repo.nesolabs.de)源添加上,然后搜索snoop-it进行安装即可。安装后你的手机上会多一个带着黄色帽子的小狗图标的App, 这个就是我们要使用的Snoop-it。下方截图就是Snoop-it打开时的界面。点击&Select App Store Apps&可以选择你要分析的App, 在Settings中可以进行相应的配置。
在Mac浏览器中输入上述http的访问地址,然后打开你选择监听的App, 刷新页面即可。从下方截图中我们是可以看到一些存储的信息是以明文的形式被获取到的。这就说明直接以明文的形式存储信息还是有一定风险的,所以我们要对数据进行加密。
我们将代码中的AES加密的相关代码打开,将加密后的数据存储到keychain中看一下效果。下方代码就是调用AES加密模块,将数据加密后在存储到Keychain中。打开后,我们重新运行工程,然后再观察其效果。&
当然,我们对数据进行AES加密,使用Snoop-it监听到的就是加密后的数据,这样一来就增加了逆向工程的难度。
经过上述步骤,我们可以看出,将用户名和密码存储到Keychain中,为了安全起见呢还是要进行加密处理的呢。说到这儿了,今天做这个Demo的时候,把App间共享keychain数据的内容也给搞了一下,过程并不复杂,需要将Keychain Sharing开关打开,并且添加上其他App的Boundle ID即可。&
四、Snoop-it的其他作用
Snoop-it是很强大滴,接下来再看一下Snoop-it的另一个强大的功能。它可以浏览你手机上App的类的层级,当然在AppStore上下载的App也是适用的。并且可以查看该App中某个文件的属性和方法。方法是该类对应的所有方法,即使在.h文件中没有留调用接口,也是可以查看的,功能还是蛮强大滴。好了今天的博客就到这儿吧。
五、Keychain-Dump
我们还可以通过KeyChain-Dump这个工具来查看钥匙串中的内容,也就是适用keychain-dump可以导出keychain中的数据。keychain-dump在github上的下载地址为()。将keychain-dump这个二进制文件拷贝到越狱设备上进行执行即可。刚拷贝过去的二进制文件是没有执行权限的,下面为了省事,直接赋值了一个最高权限,然后就可以执行该二进制文件了。
执行完后,会导出keychain中存储的内容,下方就是我们上述Demo在keychain中存储的数据。
在浏览数据的时候无意中看到了比较敏感的数据。下面这个截图是家里的WiFi账号和密码,可见,是明文存储的呢。之前用这个设备存储的所有WiFi密码都可以看到呢,直接搜AirPort就可以了。
由于个人原因呢,今天博客中所使用的登陆Demo就不往github上放了,还望大家谅解。&
阅读(...) 评论()iOS开发中使用keyChain保存用户密码a year ago那么这个方法是最优方案吗?不是的,因为Apple提供了一个更好的机制:使用keyChain保存用户密码根据文档介绍,iOS设备中的keyChain是一个安全的存储容器,可以用来为不同应用保存敏感信息(用户名,密码,网络密码等)。同时,keyChain是一个相对独立的空间,当应用替换或删除时并不会删除keyChain的内容,这对用户来说就十分的便利了。既然keyChain是Apple提供的专门用于保存敏感信息的容器,同时又具有相对的独立性,那么目前看来,使用keyChain来保存用户名和用户密码是最优的解决方案。更新:经知友指正与查阅资料,证实iPhone越狱后,有技术手段获取keyChain内容。从用户角度来讲,谨慎越狱。从开发者角度来讲,对于这个安全隐患要有清醒认识。实现keyChain保存用户密码通过查询资料,可以知道Apple是提供了官方的一些方法的。但是本着不要重复造轮子,个人采取了学习其他开发者的代码的方法,对于功能实现原理做了解,不去深究细节。首先,需要导入secutity.framework框架:为了进行实践,个人新建了一个keychain项目。而后:在Xcode左侧文件列表中选中项目名称,然后选中TARGETS;中间界面选中Build Phases,在Link Binary With Libraries中点击加号;搜索找到secutity.framework导入即可,如下图所示:其次,其次,新建KeyChain类,用于实现向keyChain存储、读取和修改用户名密码。代码如下:KeyChain.h文件#import &Foundation/Foundation.h&
#import &Security/Security.h&
@interface KeyChain : NSObject
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service;
// save username and password to keychain
+ (void)save:(NSString *)service data:(id)data;
// load username and password from keychain
+ (id)load:(NSString *)service;
// delete username and password from keychain
+ (void)delete:(NSString *)serviece;
KeyChain.m文件#import "KeyChain.h"
@implementation KeyChain
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
#pragma mark 写入
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
#pragma mark 读取
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
if (keyData)
CFRelease(keyData);
return ret;
#pragma mark 删除
+ (void)delete:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
为了验证KeyChain类,修改ViewController.m文件代码进行实验。具体如下:#import "ViewController.h"
#import "KeyChain.h"
@interface ViewController ()
NSString * const KEY_USERNAME_PASSWORD = @"pany.app.usernamepassword";
NSString * const KEY_USERNAME = @"pany.app.username";
NSString * const KEY_PASSWORD = @"pany.app.password";
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSMutableDictionary *userNamePasswordKVPairs = [NSMutableDictionary dictionary];
[userNamePasswordKVPairs setObject:@"userName" forKey:KEY_USERNAME];
[userNamePasswordKVPairs setObject:@"password" forKey:KEY_PASSWORD];
NSLog(@"%@", userNamePasswordKVPairs); //有KV值
// A、将用户名和密码写入keychain
[KeyChain save:KEY_USERNAME_PASSWORD data:userNamePasswordKVPairs];
// B、从keychain中读取用户名和密码
NSMutableDictionary *readUsernamePassword = (NSMutableDictionary *)[KeyChain load:KEY_USERNAME_PASSWORD];
NSString *userName = [readUsernamePassword objectForKey:KEY_USERNAME];
NSString *password = [readUsernamePassword objectForKey:KEY_PASSWORD];
NSLog(@"username = %@", userName);
NSLog(@"password = %@", password);
// C、将用户名和密码从keychain中删除
[KeyChain delete:KEY_USERNAME_PASSWORD];
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
实际遇到的问题与解决方法按理说,运行项目,应该得到的结果应该是得到输出:2016-10-02 04:10:59.463 keychain[16953:1510836] username = userName
2016-10-02 04:10:59.463 keychain[16953:1510836] password = password
然而我在尝试的时候,却出现了两个问题:第一个问题在输出中出现了大量个怪异输出如下:subsystem: com.apple.UIKit, category: HIDEventFiltered, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0
subsystem: com.apple.UIKit, category: HIDEventIncoming, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0
subsystem: com.apple.BaseBoard, category: MachPort, enable_level: 1, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0
subsystem: com.apple.UIKit, category: StatusBar, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0
subsystem: com.apple.BackBoardServices.fence, category: App, enable_level: 1, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0
通过在Stackoverflow上查询,发现这是Xcode8更新后出现的普遍问题,解决方法是:按顺序选择Product–&Scheme–&Edict Scheme;选择Run中的Arguments;选择Environment Variables;添加OS_ACTIVITY_MODE字段并设置Value值为disable;点击Close即可。而后运行项目,问题解决。而后运行项目,问题解决。第二个问题程序关于用户名和密码的输出实际为: 04:10:59.463 keychain[6] username = (null)
04:10:59.463 keychain[6] password = (null)
通过分步NSLog可以发现,NSMutableDictionary类的对象userNamePasswordKVPairs是有键值对的,由此可以判断问题还是在KeyChain类的方法本身。受第一个问题的启发,猜测可能和Xcode或iOS的更新有关,经过查询Stackoverflow,证实了这一个猜测。解决方法:在Xcode左侧文件列表中选中项目名称,然后选中TARGETS;中间界面选中Capabilities;将Keychain Sharing的开关由off改为on,如下图所示:而后运行项目,问题解决,得到输出如下:而后运行项目,问题解决,得到输出如下: 04:10:59.463 keychain[6] username = userName
04:10:59.463 keychain[6] password = password
由此,整个学习与实践的过程结束。小结理解了keyChain保存用户名密码的原理,使用现有代码进行了验证,解决了Xcode更新带来的问题。对于代码内部细节尚未进行细致学习,待有需求时再进行。下一步基于之前的学习,知道是服务器会对发送来的用户名密码验证,正确则返回token。鉴于Apple要求在2017年所有的通信需要从http变成https,下一步对客户端如何使用https与服务器通信做下功课。参考文献;;;。作者反馈本文系个人业余时间iOS开发自学笔记,水平有限,欢迎批评指正;若能指教客户端与服务器端https通信实现的关键词,不胜感激;经知友指出iPhone越狱后,KeyChain的安全性无法保证。经查阅资料一文,证实确可通过技术手段获取越狱后手机中KeyChain内容。25收藏分享举报{&debug&:false,&apiRoot&:&&,&paySDK&:&https:\u002F\\u002Fapi\u002Fjs&,&wechatConfigAPI&:&\u002Fapi\u002Fwechat\u002Fjssdkconfig&,&name&:&production&,&instance&:&column&,&tokens&:{&X-XSRF-TOKEN&:null,&X-UDID&:null,&Authorization&:&oauth c3cef7c66aa9e6a1e3160e20&}}{&database&:{&Post&:{&&:{&isPending&:false,&contributes&:[],&title&:&iOS开发中使用keyChain保存用户密码&,&author&:&du-ke&,&content&:&\u003Cb\u003E版权声明\u003C\u002Fb\u003E:本人原创文章,转载请注明原文链接。\u003Cbr\u003E\u003Cblockquote\u003E前言:\u003Cb\u003E自娱自乐的iOS开发学习实践\u003C\u002Fb\u003E。为实现一个简单社交类应用做功课,认识到\u003Cb\u003E使用keyChain保存用户密码是目前相对稳妥的方案\u003C\u002Fb\u003E,进行了代码实践,解决了一些更新带来的异常。\u003C\u002Fblockquote\u003E\u003Ch2\u003E提出问题\u003C\u002Fh2\u003E\u003Cp\u003E观察主流的社交类iOS客户端,在用户注册于登陆页面都会提供“\u003Cb\u003E记住密码\u003C\u002Fb\u003E”的选项。作为一个完全的自学者,提出了如下问题:\u003C\u002Fp\u003E\u003Cul\u003E\u003Cli\u003E\u003Cb\u003E记住密码\u003C\u002Fb\u003E背后的原理是什么?\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E有哪些解决方案,是否存在\u003Cb\u003E最优方案\u003C\u002Fb\u003E?\u003C\u002Fli\u003E\u003Cli\u003E该\u003Cb\u003E如何实现\u003C\u002Fb\u003E最优方案?\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ch2\u003E原理\u003C\u002Fh2\u003E\u003Cp\u003E通过在网络上查询资料,了解到“记住密码”的原理很简单,就是将\u003Cb\u003E用户密码保存在手机的文件系统\u003C\u002Fb\u003E中。但如何保存,则有不同的实现思路:\u003C\u002Fp\u003E\u003Ch2\u003E明文保存到plist文件中\u003C\u002Fh2\u003E\u003Cp\u003E在\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003ERay Wenderlich\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E的学徒系列教程中,个人已经学习并实践:对于一些轻量的数据,可以使用NSUserDefaults将其保存到应用的沙盒中的plist文件中。\u003C\u002Fp\u003E\u003Cp\u003E那么对于用户密码是否可以如法炮制呢?\u003Cb\u003E答案是否定的\u003C\u002Fb\u003E。根据查询的资料介绍,如果沙盒被破解,或者手机被越狱,plist文件就会被获取,明文的用户密码就暴露了,导致安全问题。\u003C\u002Fp\u003E\u003Ch2\u003E加密后保存到plist文件中\u003C\u002Fh2\u003E\u003Cp\u003E基于第一个方案,自然可以想到是否可以对明文进行加密后保存呢?\u003Cb\u003E答案是肯定的\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003Cp\u003EiOS提供了多种加密算法,对于用户密码,通常采用的是MD5加密,该加密是不可逆的。使用MD5加密算法需要导入头文件:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-objective-c\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&cp\&\u003E#import &CommonCrypto\u002FCommonDigest.h&\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E那么这个方法\u003Cb\u003E是最优方案吗?不是的\u003C\u002Fb\u003E,因为Apple提供了一个更好的机制:\u003C\u002Fp\u003E\u003Ch2\u003E使用keyChain保存用户密码\u003C\u002Fh2\u003E\u003Cp\u003E根据文档介绍,iOS设备中的keyChain是一个安全的存储容器,可以用来为不同应用保存敏感信息(用户名,密码,网络密码等)。同时,keyChain是一个相对独立的空间,当应用替换或删除时并不会删除keyChain的内容,这对用户来说就十分的便利了。\u003C\u002Fp\u003E\u003Cp\u003E既然keyChain是Apple提供的专门用于保存敏感信息的容器,同时又具有相对的独立性,那么目前看来,\u003Cb\u003E使用keyChain来保存用户名和用户密码是最优的解决方案\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003Cblockquote\u003E\u003Cb\u003E更新\u003C\u002Fb\u003E:经知友指正与查阅资料,\u003Cb\u003E证实iPhone越狱后,有技术手段获取keyChain内容\u003C\u002Fb\u003E。从用\u003Cb\u003E户角度来讲,谨慎越狱\u003C\u002Fb\u003E。从\u003Cb\u003E开发者角度来讲,对于这个安全隐患要有清醒认识\u003C\u002Fb\u003E。\u003C\u002Fblockquote\u003E\u003Ch2\u003E实现keyChain保存用户密码\u003C\u002Fh2\u003E\u003Cp\u003E通过查询资料,可以知道Apple是提供了官方的一些方法的。但是本着\u003Cb\u003E不要重复造轮子\u003C\u002Fb\u003E,个人采取了\u003Cb\u003E学习其他开发者的代码\u003C\u002Fb\u003E的方法,对于\u003Cb\u003E功能实现原理做了解,不去深究细节\u003C\u002Fb\u003E。\u003C\u002Fp\u003E\u003Cp\u003E首先,需要\u003Cb\u003E导入\u003C\u002Fb\u003E\u003Cb\u003Esecutity.framework框架\u003C\u002Fb\u003E:为了进行实践,个人新建了一个keychain项目。而后:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E在Xcode左侧文件列表中选中项目名称,然后选中TARGETS;\u003C\u002Fli\u003E\u003Cli\u003E中间界面选中Build Phases,在Link Binary With Libraries中点击加号;\u003C\u002Fli\u003E\u003Cli\u003E搜索找到secutity.framework导入即可,如下图所示:\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cbr\u003E\u003Cp\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-1f53f3cb6ed21a673d89f_b.png\& data-rawwidth=\&2560\& data-rawheight=\&1600\& class=\&origin_image zh-lightbox-thumb\& width=\&2560\& data-original=\&https:\u002F\\u002Fv2-1f53f3cb6ed21a673d89f_r.png\&\u003E其次,\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='2560'%20height='1600'&&\u002Fsvg&\& data-rawwidth=\&2560\& data-rawheight=\&1600\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&2560\& data-original=\&https:\u002F\\u002Fv2-1f53f3cb6ed21a673d89f_r.png\& data-actualsrc=\&https:\u002F\\u002Fv2-1f53f3cb6ed21a673d89f_b.png\&\u003E其次,\u003Cb\u003E新建KeyChain类\u003C\u002Fb\u003E,用于实现向keyChain存储、读取和修改用户名密码。代码如下:\u003C\u002Fp\u003E\u003Cp\u003E\u003Cb\u003EKeyChain.h文件\u003C\u002Fb\u003E\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-objective-c\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&cp\&\u003E#import &Foundation\u002FFoundation.h&\u003C\u002Fspan\u003E\n\u003Cspan class=\&cp\&\u003E#import &Security\u002FSecurity.h&\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@interface\u003C\u002Fspan\u003E \u003Cspan class=\&nc\&\u003EKeyChain\u003C\u002Fspan\u003E : \u003Cspan class=\&bp\&\u003ENSObject\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003EgetKeychainQuery:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&c1\&\u003E\u002F\u002F save username and password to keychain\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Evoid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003Esave:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eservice\u003C\u002Fspan\u003E \u003Cspan class=\&nf\&\u003Edata:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Edata\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&c1\&\u003E\u002F\u002F load username and password from keychain\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003Eload:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&c1\&\u003E\u002F\u002F delete username and password from keychain\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Evoid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003Edelete:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eserviece\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@end\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E\u003Cb\u003EKeyChain.m文件\u003C\u002Fb\u003E\u003Cbr\u003E\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-objective-c\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&cp\&\u003E#import \&KeyChain.h\&\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@implementation\u003C\u002Fspan\u003E \u003Cspan class=\&nc\&\u003EKeyChain\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003EgetKeychainQuery:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eservice\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EdictionaryWithObjectsAndKeys\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecClassGenericPassword\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecClass\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecAttrService\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecAttrAccount\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecAttrAccessibleAfterFirstUnlock\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecAttrAccessible\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E\n
\u003Cspan class=\&nb\&\u003Enil\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&cp\&\u003E#pragma mark 写入\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Evoid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003Esave:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eservice\u003C\u002Fspan\u003E \u003Cspan class=\&nf\&\u003Edata:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Edata\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002FGet search dictionary\u003C\u002Fspan\u003E\n
\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&nb\&\u003Eself\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EgetKeychainQuery\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002FDelete old item before add new item\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ESecItemDelete\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E((\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ECFDictionaryRef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002FAdd new object to search dictionary(Attention:the data format)\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EsetObject\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:[\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSKeyedArchiver\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EarchivedDataWithRootObject\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Edata\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E]\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EforKey\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecValueData\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002FAdd item to keychain with the search dictionary\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ESecItemAdd\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E((\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ECFDictionaryRef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&nb\&\u003ENULL\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&cp\&\u003E#pragma mark 读取\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003Eload:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eservice\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eret\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nb\&\u003Enil\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&nb\&\u003Eself\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EgetKeychainQuery\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002FConfigure the search setting\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002FSince in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EsetObject\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkCFBooleanTrue\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EforKey\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecReturnData\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EsetObject\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecMatchLimitOne\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EforKey\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Eid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkSecMatchLimit\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ECFDataRef\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EkeyData\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&nb\&\u003ENULL\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Eif\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ESecItemCopyMatching\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E((\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ECFDictionaryRef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ECFTypeRef\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E&\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeyData\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E==\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EnoErr\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003E@try\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003Eret\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSKeyedUnarchiver\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EunarchiveObjectWithData\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:(\u003C\u002Fspan\u003E\u003Cspan class=\&k\&\u003E__bridge\u003C\u002Fspan\u003E \u003Cspan class=\&bp\&\u003ENSData\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeyData\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003E@catch\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSException\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Ee\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ENSLog\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E@\&Unarchive of %@ failed: %@\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ee\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003E@finally\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Eif\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeyData\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ECFRelease\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeyData\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n
\u003Cspan class=\&k\&\u003Ereturn\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eret\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&cp\&\u003E#pragma mark 删除\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E+\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Evoid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003Edelete:\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nv\&\u003Eservice\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&nb\&\u003Eself\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EgetKeychainQuery\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Eservice\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ESecItemDelete\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E((\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003ECFDictionaryRef\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EkeychainQuery\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@end\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E为了验证KeyChain类,修改ViewController.m文件代码进行实验。具体如下:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-objective-c\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&cp\&\u003E#import \&ViewController.h\&\u003C\u002Fspan\u003E\n\u003Cspan class=\&cp\&\u003E#import \&KeyChain.h\&\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@interface\u003C\u002Fspan\u003E \u003Cspan class=\&nc\&\u003EViewController\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E()\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@end\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&c1\&\u003E\u002F\u002F\u003C\u002Fspan\u003E\n\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Econst\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EKEY_USERNAME_PASSWORD\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&s\&\u003E@\&pany.app.usernamepassword\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Econst\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EKEY_USERNAME\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&s\&\u003E@\&pany.app.username\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E \u003Cspan class=\&k\&\u003Econst\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EKEY_PASSWORD\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&s\&\u003E@\&pany.app.password\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E;\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@implementation\u003C\u002Fspan\u003E \u003Cspan class=\&nc\&\u003EViewController\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&p\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Evoid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003EviewDidLoad\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&nb\&\u003Esuper\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EviewDidLoad\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F Do any additional setup after loading the view, typically from a nib.\u003C\u002Fspan\u003E\n
\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EuserNamePasswordKVPairs\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Edictionary\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EuserNamePasswordKVPairs\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EsetObject\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E@\&userName\&\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EforKey\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKEY_USERNAME\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EuserNamePasswordKVPairs\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EsetObject\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E@\&password\&\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EforKey\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKEY_PASSWORD\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ENSLog\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E@\&%@\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EuserNamePasswordKVPairs\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E \u003Cspan class=\&c1\&\u003E\u002F\u002F有KV值\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F A、将用户名和密码写入keychain\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKeyChain\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003Esave\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKEY_USERNAME_PASSWORD\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003Edata\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EuserNamePasswordKVPairs\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F B、从keychain中读取用户名和密码\u003C\u002Fspan\u003E\n
\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EreadUsernamePassword\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&bp\&\u003ENSMutableDictionary\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKeyChain\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003Eload\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKEY_USERNAME_PASSWORD\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EuserName\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EreadUsernamePassword\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EobjectForKey\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKEY_USERNAME\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&bp\&\u003ENSString\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E*\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003Epassword\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EreadUsernamePassword\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003EobjectForKey\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKEY_PASSWORD\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ENSLog\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E@\&username = %@\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EuserName\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n
\u003Cspan class=\&n\&\u003ENSLog\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&s\&\u003E@\&password = %@\&\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E,\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Epassword\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E);\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F C、将用户名和密码从keychain中删除\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKeyChain\u003C\u002Fspan\u003E \u003Cspan class=\&nl\&\u003Edelete\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&n\&\u003EKEY_USERNAME_PASSWORD\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&p\&\u003E-\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E(\u003C\u002Fspan\u003E\u003Cspan class=\&kt\&\u003Evoid\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E)\u003C\u002Fspan\u003E\u003Cspan class=\&nf\&\u003EdidReceiveMemoryWarning\u003C\u002Fspan\u003E \u003Cspan class=\&p\&\u003E{\u003C\u002Fspan\u003E\n
\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&nb\&\u003Esuper\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EdidReceiveMemoryWarning\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E];\u003C\u002Fspan\u003E\n
\u003Cspan class=\&c1\&\u003E\u002F\u002F Dispose of any resources that can be recreated.\u003C\u002Fspan\u003E\n\u003Cspan class=\&p\&\u003E}\u003C\u002Fspan\u003E\n\n\u003Cspan class=\&k\&\u003E@end\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Ch2\u003E实际遇到的问题与解决方法\u003C\u002Fh2\u003E\u003Cp\u003E按理说,运行项目,应该得到的结果应该是得到输出:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-objective-c\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003EC\u002Fspan\u003E\u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E10\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&mo\&\u003E02\u003C\u002Fspan\u003E \u003Cspan class=\&mo\&\u003E04\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E10\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&mf\&\u003E59.463\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ekeychain\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E1C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E3C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E]\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Eusername\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003EuserName\u003C\u002Fspan\u003E\n\u003Cspan class=\&mi\&\u003EC\u002Fspan\u003E\u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E10\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E-\u003C\u002Fspan\u003E\u003Cspan class=\&mo\&\u003E02\u003C\u002Fspan\u003E \u003Cspan class=\&mo\&\u003E04\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E10\u003C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&mf\&\u003E59.463\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Ekeychain\u003C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E[\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E1C\u002Fspan\u003E\u003Cspan class=\&o\&\u003E:\u003C\u002Fspan\u003E\u003Cspan class=\&mi\&\u003E3C\u002Fspan\u003E\u003Cspan class=\&p\&\u003E]\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Epassword\u003C\u002Fspan\u003E \u003Cspan class=\&o\&\u003E=\u003C\u002Fspan\u003E \u003Cspan class=\&n\&\u003Epassword\u003C\u002Fspan\u003E\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E然而我在尝试的时候,却出现了\u003Cb\u003E两个问题\u003C\u002Fb\u003E:\u003C\u002Fp\u003E\u003Ch2\u003E第一个问题\u003C\u002Fh2\u003E\u003Cp\u003E在输出中出现了大量个怪异输出如下:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003Esubsystem: com.apple.UIKit, category: HIDEventFiltered, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0
\nsubsystem: com.apple.UIKit, category: HIDEventIncoming, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0
\nsubsystem: com.apple.BaseBoard, category: MachPort, enable_level: 1, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0
\nsubsystem: com.apple.UIKit, category: StatusBar, enable_level: 0, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 1, privacy_setting: 2, enable_private_data: 0
\nsubsystem: com.apple.BackBoardServices.fence, category: App, enable_level: 1, persist_level: 0, default_ttl: 0, info_ttl: 0, debug_ttl: 0, generate_symptoms: 0, enable_oversize: 0, privacy_setting: 0, enable_private_data: 0
\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E通过在\u003Cb\u003EStackoverflow\u003C\u002Fb\u003E上查询,发现这是Xcode8更新后出现的普遍问题,解决方法是:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E按顺序选择Product–&Scheme–&Edict Scheme;\u003C\u002Fli\u003E\u003Cli\u003E选择Run中的Arguments;\u003C\u002Fli\u003E\u003Cli\u003E选择Environment Variables;\u003C\u002Fli\u003E\u003Cli\u003E添加\u003Cb\u003EOS_ACTIVITY_MODE\u003C\u002Fb\u003E字段并设置Value值为\u003Cb\u003Edisable\u003C\u002Fb\u003E;\u003C\u002Fli\u003E\u003Cli\u003E点击Close即可。\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-4a9dca78faae1bcb8c00236fef2a382b_b.png\& data-rawwidth=\&1794\& data-rawheight=\&1016\& class=\&origin_image zh-lightbox-thumb\& width=\&1794\& data-original=\&https:\u002F\\u002Fv2-4a9dca78faae1bcb8c00236fef2a382b_r.png\&\u003E而后运行项目,问题解决。\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='1794'%20height='1016'&&\u002Fsvg&\& data-rawwidth=\&1794\& data-rawheight=\&1016\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&1794\& data-original=\&https:\u002F\\u002Fv2-4a9dca78faae1bcb8c00236fef2a382b_r.png\& data-actualsrc=\&https:\u002F\\u002Fv2-4a9dca78faae1bcb8c00236fef2a382b_b.png\&\u003E而后运行项目,问题解决。\u003C\u002Fp\u003E\u003Ch2\u003E第二个问题\u003C\u002Fh2\u003E\u003Cp\u003E程序关于用户名和密码的输出实际为:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E 04:10:59.463 keychain[6] username = (null)\n 04:10:59.463 keychain[6] password = (null)\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E通过分步NSLog可以发现,NSMutableDictionary类的对象userNamePasswordKVPairs是有键值对的,由此可以判断问题还是在KeyChain类的方法本身。受第一个问题的启发,猜测可能和Xcode或iOS的更新有关,经过查询\u003Cb\u003EStackoverflow\u003C\u002Fb\u003E,证实了这一个猜测。解决方法:\u003C\u002Fp\u003E\u003Col\u003E\u003Cli\u003E在Xcode左侧文件列表中选中项目名称,然后选中TARGETS;\u003C\u002Fli\u003E\u003Cli\u003E中间界面选中Capabilities;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E将\u003Cb\u003EKeychain Sharing的开关由off改为on\u003C\u002Fb\u003E,如下图所示:\u003C\u002Fli\u003E\u003C\u002Fol\u003E\u003Cp\u003E\u003Cnoscript\u003E\u003Cimg src=\&https:\u002F\\u002Fv2-ed9e81da4aeb7eeca707_b.png\& data-rawwidth=\&1642\& data-rawheight=\&126\& class=\&origin_image zh-lightbox-thumb\& width=\&1642\& data-original=\&https:\u002F\\u002Fv2-ed9e81da4aeb7eeca707_r.png\&\u003E而后运行项目,问题解决,得到输出如下:\u003C\u002Fnoscript\u003E\u003Cimg src=\&data:image\u002Fsvg+utf8,&svg%20xmlns='http:\u002F\u002Fwww.w3.org\u002FFsvg'%20width='1642'%20height='126'&&\u002Fsvg&\& data-rawwidth=\&1642\& data-rawheight=\&126\& class=\&origin_image zh-lightbox-thumb lazy\& width=\&1642\& data-original=\&https:\u002F\\u002Fv2-ed9e81da4aeb7eeca707_r.png\& data-actualsrc=\&https:\u002F\\u002Fv2-ed9e81da4aeb7eeca707_b.png\&\u003E而后运行项目,问题解决,得到输出如下:\u003C\u002Fp\u003E\u003Cdiv class=\&highlight\&\u003E\u003Cpre\u003E\u003Ccode class=\&language-text\&\u003E\u003Cspan\u003E\u003C\u002Fspan\u003E 04:10:59.463 keychain[6] username = userName\n 04:10:59.463 keychain[6] password = password\n\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003C\u002Fdiv\u003E\u003Cp\u003E由此,整个学习与实践的过程结束。\u003C\u002Fp\u003E\u003Ch2\u003E小结\u003C\u002Fh2\u003E\u003Cp\u003E理解了\u003Cb\u003EkeyChain保存用户名密码的原理\u003C\u002Fb\u003E,\u003Cb\u003E使用现有代码进行了验证\u003C\u002Fb\u003E,解决了Xcode更新带来的问题。对于代码内部细节尚未进行细致学习,待有需求时再进行。\u003C\u002Fp\u003E\u003Ch2\u003E下一步\u003C\u002Fh2\u003E\u003Cp\u003E基于之前的学习,知道是服务器会对发送来的用户名密码验证,正确则返回token。鉴于Apple要求在2017年所有的通信需要从http变成https,下一步对客户端如何使用https与服务器通信做下功课。\u003C\u002Fp\u003E\u003Cbr\u003E\u003Ch2\u003E参考文献\u003C\u002Fh2\u003E\u003Cul\u003E\u003Cli\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\\u002Fp\u002Fa94f258c1213\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EiOS中Keychain保存用户名和密码\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\\u002Fp\u002F4af3b2Fcomments\u002F1294203\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EiOS开发中用户密码应该保存在哪里\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E;\u003C\u002Fli\u003E\u003Cli\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\\u002FJenaral\u002Fp\u002F5663096.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EKeychain 浅析\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E;\u003Cbr\u003E\u003C\u002Fli\u003E\u003Cli\u003E\u003Ca href=\&http:\u002F\\u002F?target=http%3A\u002F\\u002Fioss-keychaindumper.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EiOS安全–使用Keychain Dumper导出Keychain中的数据\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E。\u003Cbr\u003E\u003C\u002Fli\u003E\u003C\u002Ful\u003E\u003Ch2\u003E作者反馈\u003C\u002Fh2\u003E\u003Col\u003E\u003Cli\u003E本文系个人业余时间iOS开发自学笔记,水平有限,欢迎批评指正;\u003C\u002Fli\u003E\u003Cli\u003E若能指教客户端与服务器端https通信实现的关键词,不胜感激;\u003C\u002Fli\u003E\u003Cli\u003E经知友\u003Ca href=\&http:\u002F\\u002Fpeople\u002Fd72400f2cdaa59ef7865e5c\& data-hash=\&d72400f2cdaa59ef7865e5c\& class=\&member_mention\& data-editable=\&true\& data-title=\&@JamesYu\& data-hovercard=\&p$b$d72400f2cdaa59ef7865e5c\&\u003E@JamesYu\u003C\u002Fa\u003E指出\u003Cb\u003EiPhone越狱后,KeyChain的安全性无法保证。\u003C\u002Fb\u003E经查阅资料\u003Ca href=\&http:\u002F\\u002F?target=http%3A\\u002FJenaral\u002Fp\u002F5663096.html\& class=\& wrap external\& target=\&_blank\& rel=\&nofollow noreferrer\&\u003EKeychain 浅析\u003Ci class=\&icon-external\&\u003E\u003C\u002Fi\u003E\u003C\u002Fa\u003E一文,证实确可通过技术手段获取越狱后手机中KeyChain内容。\u003C\u002Fli\u003E\u003C\u002Fol\u003E&,&updated&:new Date(&T02:48:45.000Z&),&canComment&:false,&commentPermission&:&anyone&,&commentCount&:15,&collapsedCount&:0,&likeCount&:25,&state&:&published&,&isLiked&:false,&slug&:&&,&isTitleImageFullScreen&:false,&rating&:&none&,&titleImage&:&https:\u002F\\u002Fv2-891c3d413dfc84f9ef9e1e1ca2fdbbf1_r.jpg&,&links&:{&comments&:&\u002Fapi\u002Fposts\u002F2Fcomments&},&reviewers&:[],&topics&:[{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&iOS 开发&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&密码&},{&url&:&https:\u002F\\u002Ftopic\u002F&,&id&:&&,&name&:&自学&}],&adminClosedComment&:false,&titleImageSize&:{&width&:1322,&height&:386},&href&:&\u002Fapi\u002Fposts\u002F&,&excerptTitle&:&&,&tipjarState&:&closed&,&annotationAction&:[],&sourceUrl&:&&,&pageCommentsCount&:15,&hasPublishingDraft&:false,&snapshotUrl&:&&,&publishedTime&:&T10:48:45+08:00&,&url&:&\u002Fp\u002F&,&lastestLikers&:[{&bio&:&程序员,喜欢音乐、跑步&,&isFollowing&:false,&hash&:&724ad337bdecb6354eea70d61f7ace09&,&uid&:88,&isOrg&:false,&slug&:&fu-su-97&,&isFollowed&:false,&description&:&部分出柜 单身 .5&,&name&:&Fargo&,&profileUrl&:&https:\u002F\\u002Fpeople\u002Ffu-su-97&,&avatar&:{&id&:&&,&template&:&https:\u002F\\u002F50\u002F{id}_{size}.jpg&},&isOrgWhiteList&:false,&isBanned&:false},{&bio&:&iOS开发攻城狮&,&isFollowing&:false,&hash&:&9fb935e

我要回帖

更多关于 ios11 keychain 的文章

 

随机推荐