一、Keychain 基础
根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的。
开发者通常会希望能够利用操作系统提供的功能来保存凭证(credentials)而不是把它们(凭证)保存到NSUserDefaults,plist文件等地方。保存这些数据的原因是开发者不想用户每次都要登录,因此会把认证信息保存到设备上的某个地方并且在用户再次打开应用的时候用这些数据自动登录。Keychain的信息是存在于每个应用(app)的沙盒之外的。
通过keychain access groups可以在应用之间共享keychain中的数据。要求在保存数据到keychain的时候指定group。把数据保存到keychain的最好方法就是用苹果提供的KeychainItemWrapper。可以到这下载例子工程。第一步就是创建这个类的实例。
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@”Password” accessGroup:nil];
标识符(Identifier)在后面我们要从keychain中取数据的时候会用到。如果你想要在应用之间共享信息,那么你需要指定访问组(access group)。有同样的访问组 的应用能够访问同样的keychain信息。
KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@”Account Number” accessGroup:@”YOUR_APP_ID_HERE.com.yourcompany.GenericKeychainSuite”];
要把信息保存到keychain中,使用 setObject:forKey: 方法。在这里, (id)kSecAttrAccount 是一个预先定义好的键(key),我们可以用它来保存账号名称。 kSecClass指定了我们要保存的某类信息,在这里是一个通用的密码。kSecValueData可以被用来保存任意的数据,在这里是一个密码。
[wrapper setObject:kSecClassGenericPassword forKey:(id)kSecClass];[wrapper setObject:@"username" forKey:(id)kSecAttrAccount];
[wrapper setObject:@"password"forKey:(id)kSecValueData];
[wrapper setObject:(id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(id)kSecAttrAccessible];
当然,我们应该绝对不要使用kSecAttrAccessibleAlways。一个安全点的选项是kSecAttrAccessibleWhenUnlocked。有些选项是以 ThisDeviceOnly 结尾的,如果选中了这个选项,那么数据就会被以硬件相关的密钥(key)加密,因此不能被传输到或者被其他设备看到。即使它们提供了进一步的安全性,使用它们可能不是一个好主意,除非你有一个更好的理由不允许数据在备份之间迁移。
要从keychain中获取数据,可以用 NSString *accountName = [wrapper objectForKey:(id)kSecAttrAccount];
钥匙串中的条目称为SecItem,但它是存储在CFDictionary中的。SecItemRef类型并不存在。SecItem有五类:通用密码、互联网密码、证书、密钥和身份。在大多数情况下,我们用到的都是通用密码。许多问题都是开发人员尝试用互联网密码造成的。互联网密码要复杂得多,而且相比之下优势寥寥无几,除非开发Web浏览器,否则没必要用它。KeyChainItemWrapper只使用通用密码,这也是我喜欢它的原因之一。iOS应用很少将密钥和身份存储起来,所以我们在本书中不会讨论这方面的内容。只有公钥的证书通常应该存储在文件中,而不是钥匙串中。
最后,我们需要在钥匙串中搜索需要的内容。密钥有很多个部分可用来搜索,但最好的办法是将自己的标识符赋给它,然后搜索。通用密码条目都包含属性kSecAttrGeneric,可以用它来存储标识符。这也是KeyChainItemWrapper的处理方式。
钥匙串中的条目都有几个可搜索的**属性**和一个加密过的**值**。对于通用密码条目,比较重要的属性有账户(kSecAttrAccount)、服务(kSecAttrService)和标识符(kSecAttrGeneric)。而值通常是密码。
说明:
每一个keyChain的组成如图,整体是一个字典结构.
1.kSecClass key 定义属于那一种类型的keyChain
2.不同的类型包含不同的Attributes,这些attributes定义了这个item的具体信息
3.每个item可以包含一个密码项来存储对应的密码
二、Keychain操作
iOS中Security.framework框架提供了四个主要的方法来操作KeyChain:
// 查询 OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);// 添加 OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result);
// 更新 KeyChain中的ItemOSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);
// 删除 KeyChain中的ItemOSStatus SecItemDelete(CFDictionaryRef query)
引入Security包,引入文件 #import <Security/Security.h>
添加
- (IBAction)add:()sender { (nameField.text.length > && passwordField.text.length > ) { NSMutableDictionary* dic = [NSMutableDictionary dictionary]; [dic setObject:()kSecClassGenericPassword forKey:()kSecClass]; [dic setObject:nameField.text forKey:()kSecAttrAccount]; [dic setObject:[passwordField.text dataUsingEncoding:NSUTF8StringEncoding] forKey:()kSecValueData]; OSStatus s = SecItemAdd((CFDictionaryRef)dic, NULL); NSLog(,s); } }
- (IBAction)sel:()sender { NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, kSecMatchLimitAll,kSecMatchLimit, kCFBooleanTrue,kSecReturnAttributes,nil]; CFTypeRef result = nil; OSStatus s = SecItemCopyMatching((CFDictionaryRef)query, &result); NSLog(,s); NSLog(,result); }- (IBAction)sname:()sender { (nameField.text.length >) { NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, nameField.text,kSecAttrAccount, kCFBooleanTrue,kSecReturnAttributes,nil]; CFTypeRef result = nil; OSStatus s = SecItemCopyMatching((CFDictionaryRef)query, &result); NSLog(,s); NSLog(,result); (s == noErr) { NSMutableDictionary* dic = [NSMutableDictionary dictionaryWithDictionary:result]; [dic setObject:()kCFBooleanTrue forKey:kSecReturnData]; [dic setObject:[query objectForKey:kSecClass] forKey:kSecClass]; NSData* data = nil; (SecItemCopyMatching((CFDictionaryRef)dic, (CFTypeRef*)&data) == noErr) { (data.length) NSLog(,[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); } } } }
- (IBAction)update:()sender { (nameField.text.length > && passwordField.text.length > ) { NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, nameField.text,kSecAttrAccount, kCFBooleanTrue,kSecReturnAttributes,nil]; CFTypeRef result = nil; (SecItemCopyMatching((CFDictionaryRef)query, &result) == noErr) { NSMutableDictionary* update = [NSMutableDictionary dictionaryWithDictionary:(NSDictionary*)result]; [update setObject:[query objectForKey:kSecClass] forKey:kSecClass]; [update setObject:[passwordField.text dataUsingEncoding:NSUTF8StringEncoding] forKey:kSecValueData]; [update removeObjectForKey:kSecClass]; TARGET_IPHONE_SIMULATOR [update removeObjectForKey:()kSecAttrAccessGroup];NSMutableDictionary* updateItem = [NSMutableDictionary dictionaryWithDictionary:result]; [updateItem setObject:[query objectForKey:()kSecClass] forKey:()kSecClass]; OSStatus status = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)update); NSLog(,status);
- (IBAction)del:()sender { (nameField.text.length >) { NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, nameField.text,kSecAttrAccount,nil]; OSStatus status = SecItemDelete((CFDictionaryRef)query); NSLog(,status); } }
四、保存密码实例
来看一下使用keychain保存密码的例子:
@implementation WQKeyChain + (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
@interface WQUserDataManager : NSObject /** * @brief 存储密码 * * @param password 密码内容 */ +(void)savePassWord:(NSString *)password; /** * @brief 读取密码 * * @return 密码内容 */ +(id)readPassWord; /** * @brief 删除密码数据 */ +(void)deletePassWord; @end
#import "WQUserDataManager.h" @implementation WQUserDataManager static NSString * const KEY_IN_KEYCHAIN = @"com.wuqian.app.allinfo"; static NSString * const KEY_PASSWORD = @"com.wuqian.app.password"; +(void)savePassWord:(NSString *)password { NSMutableDictionary *usernamepasswordKVPairs = [NSMutableDictionary dictionary]; [usernamepasswordKVPairs setObject:password forKey:KEY_PASSWORD]; [WQKeyChain save:KEY_IN_KEYCHAIN data:usernamepasswordKVPairs]; } +(id)readPassWord { NSMutableDictionary *usernamepasswordKVPair = (NSMutableDictionary *)[WQKeyChain load:KEY_IN_KEYCHAIN]; return [usernamepasswordKVPair objectForKey:KEY_PASSWORD]; } +(void)deletePassWord { [WQKeyChain delete:KEY_IN_KEYCHAIN]; } @end
-(IBAction)btnAciton:(id)sender { [WQUserDataManager savePassWord:self.textfield.text]; self.label.text = [WQUserDataManager readPassWord]; }
本文向大家介绍实例详解IOS开发之UIWebView,包括了实例详解IOS开发之UIWebView的使用技巧和注意事项,需要的朋友参考一下 iOS开发之UIWebView 是本文要介绍的内容,UIWebView是iOS sdk中一个最常用的控件。是内置的浏览器控件,我们可以用它来浏览网页、打开文档等等。这篇文章我将使用这个控件,做一个简易的浏览器。如下图: 我们创建一个Window-based A
本文向大家介绍详解Ruby on Rails中的mailer相关使用,包括了详解Ruby on Rails中的mailer相关使用的使用技巧和注意事项,需要的朋友参考一下 把 mails 命名为 SomethingMailer。 没有 Mailer 字根的话,不能立即显现哪个是一个 Mailer,以及哪个视图与它有关。 提供 HTML 与纯文本视图模版。 在你的开发环境启用
本文向大家介绍IOS中UIWebView的使用详解,包括了IOS中UIWebView的使用详解的使用技巧和注意事项,需要的朋友参考一下 一、初始化与三种加载方式 UIWebView继承与UIView,因此,其初始化方法和一般的view一样,通过alloc和init进行初始化,其加载数据的方式有三种: 第一种: 这是加载网页最常用的一种方式,通过一个网页URL来进行加载,这个URL可以是远程的也可以
本文向大家介绍详解IOS中GCD的使用,包括了详解IOS中GCD的使用的使用技巧和注意事项,需要的朋友参考一下 Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的
本文向大家介绍iOS中关于信鸽推送的使用demo详解,包括了iOS中关于信鸽推送的使用demo详解的使用技巧和注意事项,需要的朋友参考一下 最近在看推送方面的知识,用的是信鸽推送主要是因为后台用的是信鸽 推送用第三方推送,也就是在客户端建一个广播接收器,当服务器发送消息时发送到信鸽,信鸽再发送一次,广播接受器接受下; 我实现的功能比较简单,当app在运行状态时,会在主页展示一个弹窗展示推送的消息;
本文向大家介绍iOS开发————详解适配iOS10问题,包括了iOS开发————详解适配iOS10问题的使用技巧和注意事项,需要的朋友参考一下 2016年9月7日,苹果发布iOS 10。2016年9月14日,全新的操作系统iOS 10将正式上线。 作为开发者,如何适配iOS10呢? 1.Notification(通知) 自从Notification被引入之后,苹果就不断的更新优化,但这些更新优化只