详解iOS应用开发中Keychain的相关使用
小标 2018-05-25 来源 : 阅读 968 评论 0

摘要:在iOS应用开发中,根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的。希望对大家学习iOS应用开发有所帮助。

一、Keychain 基础

在iOS应用开发中,根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌。苹果自己用keychain来保存Wi-Fi网络密码,VPN凭证等等。它是一个sqlite数据库,位于/private/var/Keychains/keychain-2.db,其保存的所有数据都是加密过的。希望对大家学习iOS应用开发有所帮助。

开发者通常会希望能够利用操作系统提供的功能来保存凭证(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)。而值通常是密码。

说明:

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

每一个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];  
}

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

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

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发IOS频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程