热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

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

这篇文章主要介绍了iOS开发中Keychain的相关使用,文中列举了一个使用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)。而值通常是密码。

说明:

2015102692011661.png (327×285)

每一个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

添加

代码如下:

- (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]; 


2015102691854624.png (352×699)

2015102691930133.png (350×701)


推荐阅读
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • macOS Big Sur全新设计大版本更新,10+个值得关注的新功能
    本文介绍了Apple发布的新一代操作系统macOS Big Sur,该系统采用全新的界面设计,包括图标、应用界面、程序坞和菜单栏等方面的变化。新系统还增加了通知中心、桌面小组件、强化的Safari浏览器以及隐私保护等多项功能。文章指出,macOS Big Sur的设计与iPadOS越来越接近,结合了去年iPadOS对鼠标的完善等功能。 ... [详细]
  • 本文介绍了互联网思维中的三个段子,涵盖了餐饮行业、淘品牌和创业企业的案例。通过这些案例,探讨了互联网思维的九大分类和十九条法则。其中包括雕爷牛腩餐厅的成功经验,三只松鼠淘品牌的包装策略以及一家创业企业的销售额增长情况。这些案例展示了互联网思维在不同领域的应用和成功之道。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • win10电脑蓝屏代码0x000000a5无法进入系统解决方法详解
    许多用户在使用电脑的时候遇到蓝屏问题,重启无法进入系统。本文提供了解决方法:调整BIOS设置、禁用安全启动、重装系统等。如果以上方法都无法解决问题,需要重新安装一个系统。详细步骤请参考正文内容。 ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • 爱奇艺如何使用扫码登录二维码
    本文介绍了在使用爱奇艺时如何通过扫码登录二维码的具体操作方法,包括打开爱奇艺app、点击“我的”按钮、点击扫码按钮等步骤。同时提供了操作环境的相关信息,包括手机品牌型号、系统版本和软件版本。 ... [详细]
  • 本文介绍了iOS开发中检测和解决内存泄漏的方法,包括静态分析、使用instruments检查内存泄漏以及代码测试等。同时还介绍了最能挣钱的行业,包括互联网行业、娱乐行业、教育行业、智能行业和老年服务行业,并提供了选行业的技巧。 ... [详细]
  • 苹果手机home键设置1.我们点击打开iPhone手机桌面上的设置图标。2.点击打开页面中的通用选项。 ... [详细]
  • 苹果8p电池多少毫安的_iphonexs电池多少毫安,iphonexs换电池多少钱
    使用iPhoneXS手机用户都反映XS的电池不耐用,同一系列的XR相比之下电池续航能力要强很多。iphonexs电池多少毫安,电池不耐用的原因有哪些&# ... [详细]
author-avatar
ys2011一号_139
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有