详解iOS开发中Keychain的相关使用

一、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];

kSecAttrAccessiblein变量用来指定这个应用合适需要访问这个数据。我们需要对这个选项特别注意,并且使用最严格的选项。这个键(key)可以设置6种值。

当然,我们应该绝对不要使用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)

三、Keychain使用

引入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]; 
}

(0)

相关推荐

  • IOS开发使用KeychainItemWrapper 持久存储用户名和密码

    首先从官网下载 KeychainItemWrapper.h KeychainItemWrapper.m 将这两个文件导入项目中 不过该文件是手动释放的 所以要使用这个文件需要先做一些处理: 如果要使用KeychainItemWrapper.h类 在CompileSources中选中该类 添加-fno-objc-arc 接下来直接上代码: KeychainItemWrapper *keychain=[[KeychainItemWrapper alloc] initWithIdentifier:@"

  • 详解iOS使用Keychain中的kSecClassGenericPassword存储数据

    iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌.苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等.它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的.模拟器下keychain文件路径:~/Library/Application Support/iPhone Simulator/4.3/Library/Keychains ke

  • iOS中利用KeyChain保存用户信息的方法示例

    前言 说到保存用户名和密码,以前有用过本地的数据库来保存,也接触过用userdefault来保存,后来在一个项目中发现了一个新的方法--用Keychain来保存.下面话不多说了,直接通过示例代码来介绍吧. 方法示例 一.新建一个LYKeychainTool类,导入系统Security框架 ,LYKeychainTool.h文件实现如下: // // LYKeychainTool.h // keyChainTest // // Created by Liyu on 2017/6/2. // Cop

  • 详解iOS开发中Keychain的相关使用

    一.Keychain 基础 根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌.苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等.它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的. 开发者通常会希望能够利用操作系统提供的功能来保存凭证(credentials)而不是把它们(凭证)保存到NSUserDefault

  • 详解IOS开发中生成推送的pem文件

    详解IOS开发中生成推送的pem文件 具体步骤如下: 首先,需要一个pem的证书,该证书需要与开发时签名用的一致. 具体生成pem证书方法如下: 1. 登录到 iPhone Developer Connection Portal(http://developer.apple.com/iphone/manage/overview/index.action )并点击 App IDs 2. 创建一个不使用通配符的 App ID .通配符 ID 不能用于推送通知服务.例如,  com.itotem.ip

  • 详解iOS开发中的转场动画和组动画以及UIView封装动画

    一.转场动画 CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果.iOS比Mac OS X的转场动画效果少一点 UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果 属性解析: type:动画过渡类型 subtype:动画过渡方向 startProgress:动画起点(在整体动画的百分比) endProgress:动画终点(在整体动画的百分比) 转场动画代码示例 1.界面搭建 2.实现代码 复制代码

  • 详解iOS开发中使用storyboard创建导航控制器的方法

    关于StoryBoard iOS5之后Apple提供了一种全新的方式来制作UI,那就是StoryBoard.简单理解来说,可以把StoryBoard看做是一组viewController对应的xib,以及它们之间的转换方式的集合.在StoryBoard中不仅可以看到每个ViewController的布局样式,也可以明确地知道各个ViewController之间的转换关系.相对于单个的xib,其代码需求更少,也由于集合了各个xib,使得对于界面的理解和修改的速度也得到了更大提升.减少代码量就是减少

  • 详解iOS开发中UItableview控件的数据刷新功能的实现

    实现UItableview控件数据刷新 一.项目文件结构和plist文件 二.实现效果 1.说明:这是一个英雄展示界面,点击选中行,可以修改改行英雄的名称(完成数据刷新的操作). 运行界面: 点击选中行: 修改数据后自动刷新: 三.代码示例 数据模型部分: YYheros.h文件 复制代码 代码如下: // //  YYheros.h //  10-英雄展示(数据刷新) // //  Created by apple on 14-5-29. //  Copyright (c) 2014年 itc

  • 详解iOS开发中UITableview cell 顶部空白的多种设置方法

    我知道没人会主动设置这个东西,但是大家一定都遇到过这个问题,下面总结下可能是哪些情况: 1, self.automaticallyAdjustsScrollViewInsets = NO; 这个应该是最常见而且不容易被发现的原因,起因是iOS7在Conttoller中新增了automaticallyAdjustsScrollViewInsets这个属性,当设置为YES时(默认YES),如果视图里面存在唯一一个UIScrollView或其子类View,那么它会自动设置相应的内边距,这样可以让scr

  • 详解IOS开发中图片上传时两种图片压缩方式的比较

    IOS 图片上传时两种图片压缩方式的比较 上传图片不全面的想法:把图片保存到本地,然后把图片的路径上传到服务器,最后又由服务器把路径返回,这种方式不具有扩展性,如果用户换了手机,那么新手机的沙盒中就没有服务器返回的图片路径了,此时就无法获取之前已经上传了的头像了,在项目中明显的不可行. 上传图片的正确方式:上传头像到服务器一般是将图片NSData上传到服务器,服务器返回一个图片NSString地址,之后再将NSString的路径转为url并通过url请求去更新用户头像(用户头像此时更新的便是NS

  • 详解iOS开发中app的归档以及偏好设置的存储方式

    ios应用数据存储方式(归档) 一.简单说明 在使用plist进行数据存储和读取,只适用于系统自带的一些常用类型才能用,且必须先获取路径相对麻烦: 偏好设置(将所有的东西都保存在同一个文件夹下面,且主要用于存储应用的设置信息) 归档:因为前两者都有一个致命的缺陷,只能存储常用的类型.归档可以实现把自定义的对象存放在文件中. 二.代码示例 1.文件结构 2.代码示例 YYViewController.m文件 复制代码 代码如下: // //  YYViewController.m //  02-归

  • 详解iOS开发中UIPickerView控件的使用方法

    UIPickerView控件在给用户选择某些特定的数据时经常使用到,这里演示一个简单的选择数据,显示在UITextField输入框里,把UIPickerView作为输入View,用Toolbar作为选定数据的按钮.和其他UITableView控件相似,UIPickerView也需要数据源. 我们要实现的效果如下: 下面开始使用的步骤. 1.打开XCode 4.3.2,新建一个Single View Application ,命名为PickerViewDemo,Company Identifier

  • 详解iOS开发中解析JSON中的boolean类型的数据遇到的问题

    问题描述: Xcode中打印的JSON数据: { content = { createTime = 1462512975497; expiryDate = 1475137813; id = 204; intervalSeconds = 0; lastHgt = "63.689"; lastLat = "39.9621096"; lastLng = "116.3175201"; lastTime = 1462848844; manage = 1;

随机推荐