最近项目需要存储用户的唯一标识符,但是由于如果用户重装APP,获取到的又会是一个新的UDID。查询了一系列资料下来,可以用Keychain进行存储UDID,然后就算重装了APP,也能从Keychain中读取出之前存储的UDID。

Keychain存储在iOS系统的内部数据库中,由系统进行管理和加密,哪怕APP被删除了,它存储在Keychain中的数据也不会被删除。

So,Keychain使用走起~
首先一定要在Capabilities中打开Keychain,如图:

6B836BEE-1CDC-4D49-8E7E-50490DE2E953.png

好吧,不打开的话进行Keychain操作将会返回一个34018的错误码。。

当然别忘记引入import <Security/Security.h>

Keychain存储的流程如图所示:

B9C180AB-88D0-44B6-905E-37802CC13B6E.png

简单来说,如果要存储一个password,需要先遍历Keychain,看它的password是否已经存在于Keychain中,存在的话就更新它的值,不存在就存储。
所以这就使用到苹果提供的方法:

// 查询
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);// 添加
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result);// 更新
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);// 删除
OSStatus SecItemDelete(CFDictionaryRef query)

接下来看看操作Keychain常用的key-value.

kSecClass:有五个值,分别为kSecClassGenericPassword(通用密码--也是接下来使用的)、kSecClassInternetPassword(互联网密码)、kSecClassCertificate(证书)、kSecClassKey(密钥)、kSecClassIdentity(身份)kSecAttrService:服务
kSecAttrServer:服务器域名或IP地址
kSecAttrAccount:账号
kSecAttrAccessGroup: 可以在应用之间共享keychain中的数据
kSecMatchLimit:返回搜索结果,kSecMatchLimitOne(一个)、kSecMatchLimitAll(全部)
//

这是基于CoreFundation框架的操作,通过带有以上key-value值的字典进行操作,所以写起来还是有些繁琐的。所幸苹果提供了一个源码Demo。demo中使用service和account以及accessGroup进行password的存储,也就是一个kSecClassGenericPassword类型的Keychain表,一个account对应一个password,可以很方便在APP中进行Key-Value类型的存储。

不过由于它是使用Swift编写的,在我的小项目中不适合引入混编,所以我就按着demo 的逻辑抄了个OC版的出来。个人看来,OC的还是好懂点,去除了各种try catch,思路上会清晰很多。

初始化方法:

- (instancetype)initWithSevice:(NSString *)service account :(NSString *)account accessGroup:(NSString *)accessGroup {if (self = [super init]) {_service = service;_account = account;_accessGroup = accessGroup;//可以在不同的app当中共享}return self;
}

主要方法有:

- (void)savePassword:(NSString *)password;
- (BOOL)deleteItem;- (NSString *)readPassword;//返回当前accessGroup下的service的所有Keychain Item
+ (NSArray *)passwordItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup;

(具体代码在后面贴吧,防止影响篇幅,有兴趣的朋友也可以下demo看看)

使用起来就是:
存储:

    KeychainWrapper *wrapper = [[KeychainWrapper alloc] initWithSevice:kKeychainService account:self.accountField.text accessGroup:kKeychainAccessGroup];[wrapper savePassword:self.passwordField.text];

读取当前account 的password:

KeychainWrapper *item = [[KeychainWrapper alloc] initWithSevice:service account:acount accessGroup:accessGroup];
[item readPassword];

返回当前accessGroup下的service的所有Keychain:

NSArray *keychains = [KeychainWrapper passwordItemsForService:kKeychainService accessGroup:kKeychainAccessGroup];

踩坑:OSStatus状态错误码:

  • 50: 请求参数错误。我就曾经把kSecAttrService写成了kSecAttrServer,crash到哭了。。。
  • 34018:前面说的Capabilities…

代码:

- (void)savePassword:(NSString *)password {NSData *encodePasswordData = [password dataUsingEncoding:NSUTF8StringEncoding];// if password was existed, updateNSString *originPassword = [self readPassword];if (originPassword.length > 0) {NSMutableDictionary *updateAttributes = [NSMutableDictionary dictionary];updateAttributes[(__bridge id)kSecValueData] = encodePasswordData;NSMutableDictionary *query = [self keychainQueryWithService:_service account:_account accessGroup:_accessGroup];OSStatus statusCode = SecItemUpdate((__bridge CFDictionaryRef)query,(__bridge CFDictionaryRef)updateAttributes);NSAssert(statusCode == noErr, @"Couldn't update the Keychain Item." );}else{// else , addNSMutableDictionary *attributes = [self keychainQueryWithService:_service account:_account accessGroup:_accessGroup];attributes[(__bridge id)kSecValueData] = encodePasswordData;OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil);NSAssert(status == noErr, @"Couldn't add the Keychain Item.");}
}- (NSString *)readPassword {NSMutableDictionary *attributes = [self keychainQueryWithService:_service account:_account accessGroup:_accessGroup];attributes[(__bridge id)kSecMatchLimit] = (__bridge id)(kSecMatchLimitOne);attributes[(__bridge id)kSecReturnAttributes] = (__bridge id _Nullable)(kCFBooleanTrue);attributes[(__bridge id)kSecReturnData] = (__bridge id _Nullable)(kCFBooleanTrue);CFMutableDictionaryRef queryResult = nil;OSStatus keychainError = noErr;keychainError = SecItemCopyMatching((__bridge CFDictionaryRef)attributes,(CFTypeRef *)&queryResult);if (keychainError == errSecItemNotFound) {if (queryResult) CFRelease(queryResult);return nil;}else if (keychainError == noErr) {if (queryResult == nil){return nil;}NSMutableDictionary *resultDict = (__bridge NSMutableDictionary *)queryResult;NSData *passwordData = resultDict[(__bridge id)kSecValueData];NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding];return password;}else{NSAssert(NO, @"Serious error.\n");if (queryResult) CFRelease(queryResult);}return nil;
}- (NSMutableDictionary *)keychainQueryWithService:(NSString *)service account:(NSString *)account accessGroup:(NSString *)accessGroup {NSMutableDictionary *query = [NSMutableDictionary dictionary];query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;query[(__bridge id)kSecAttrService] = service;query[(__bridge id)kSecAttrAccount] = account;query[(__bridge id)kSecAttrAccessGroup] = accessGroup;return query;
}+ (NSArray *)passwordItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup {NSMutableDictionary *query = [[[self alloc]init] keychainQueryWithService:service account:nil accessGroup:accessGroup];query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;query[(__bridge id)kSecReturnAttributes] = (__bridge id _Nullable)(kCFBooleanTrue);query[(__bridge id)kSecReturnData] = (__bridge id _Nullable)(kCFBooleanFalse);CFMutableArrayRef queryResult = nil;OSStatus status = noErr;status = SecItemCopyMatching((__bridge CFDictionaryRef)query,(CFTypeRef *)&queryResult);if (status == errSecItemNotFound) {return @[];}else if (status == noErr) {NSArray<NSDictionary *> *resultArray = (__bridge NSArray<NSDictionary *> *)queryResult;if (resultArray.count == 0){return @[];}NSMutableArray *passwordItems = [NSMutableArray array];for (NSDictionary *result in resultArray) {NSString *acount = result[(__bridge id)kSecAttrAccount];if (acount.length > 0) {KeychainWrapper *item = [[KeychainWrapper alloc] initWithSevice:service account:acount accessGroup:accessGroup];[passwordItems addObject:item];}}return passwordItems;}else {NSAssert(NO, @"Serious error.\n");if (queryResult) CFRelease(queryResult);return @[];}
}- (BOOL)deleteItem {// Delete the existing item from the keychain.NSMutableDictionary *query = [self keychainQueryWithService:self.service account:self.account accessGroup:self.accessGroup];OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);if (status != noErr && status != errSecItemNotFound) {return NO;}return true;
}

demo
参考:
https://developer.apple.com/library/prerelease/content/documentation/Security/Conceptual/keychainServConcepts/iPhoneTasks/iPhoneTasks.html#//apple_ref/doc/uid/TP30000897-CH208-SW1
http://www.360doc.com/content/15/0708/15/20918780_483580050.shtml
https://my.oschina.net/w11h22j33/blog/206713

iOS KeyChain使用相关推荐

  1. ios keychain 不被清理_iOS签名机制和说明文件【ios企业签名吧】

    IOS签名机制和配置文件.iOS签名机制的作用:保证安装在手机上的应用程序经苹果公式验证和许可.无论是真机调试还是发布App,开发人员都必须经过一些复杂的步骤.以下广州贝壳技术将详细说明. 贝壳科技( ...

  2. iOS keyChain 研究

    一.基本知识 1.方法 SecItemAdd 增 SecItemUpdate 改 SecItemDelete 删 SecItemCopyMatching 查 2.权限 文档上说iOS的keyChain ...

  3. iOS Keychain(钥匙串)原理及使用

    Keychain介绍 Keychain Services 是 macOS 和 iOS 都提供一种安全的存储敏感信息的工具,比如,网络密码:用户访问服务器或者网站,通用密码:用来保存应用程序或者数据库密 ...

  4. html5 ios keychain,iOS Keychain理解

    Keychain 介绍 Keychain Services 是 OS X 和 iOS 都提供一种安全地存储敏感信息的工具,比如,存储用户ID,密码,和证书等.存储这些信息可以免除用户重复输入用户名和密 ...

  5. iOS Keychain和keychain share

    一.keychain介绍(摘抄别人的https://blog.hudongdong.com/ios/356.html) 根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不 ...

  6. html5 ios keychain,iOS 用keychain钥匙串保存账号、设备UUID及APP间共享

    iOS的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每个ios程序都有一个独立的keychain存储.相对于NSUserDefaults.文件保存等一般方式,key ...

  7. html5 ios keychain,iOS10适配之Keychain读写失败

    Keychain iOS设备中的Keychain是一个安全的存储容器,可以用来为不同应用保存敏感信息比如用户名,密码,网络密码,认证令牌,UUID等 Bug 升级正式版Xcode8 运行了下最近的项目 ...

  8. IOS KeyChain

    转自http://my.oschina.net/w11h22j33/blog/206713 一.Keychain 基础 根据苹果的介绍,iOS设备中的Keychain是一个安全的存储容器,可以用来为不 ...

  9. iOS Keychain钥匙串应用间数据共享

    在一个公司中可能有多款产品.对于用户而言,一般使用一个帐号就可以登陆访问该公司的所有的产品.对于这种情况,如果一款手机中装了该公司的两款(或多款)产品,那么我们希望只在其中一款产品中登陆,那么另一款产 ...

最新文章

  1. 第十届中国信息安全大会召开 主打安全创新
  2. 【网络安全】一次实战中对tp5网站getshell方式的测试
  3. StartActivityForResult
  4. 前端笔试题总结---持续更新
  5. (?i) 和 re.sub
  6. ubuntu16.04下Caffe绘制训练过程的loss和accuracy曲线
  7. 【编译器】G++相关编译命令
  8. oracle附加数据库拒绝访问,Windows 添加任务计划报“0x80070005: 拒绝访问”的解决...
  9. 软件测试综合笔试题目及参考答案
  10. 倒立摆及其应用//2021-2-23
  11. vue 或 js 实现 excel表格的导出(笔记)
  12. composite-id class must implement Serializable
  13. mysql rps和tps区别_并发虚拟用户、RPS、TPS的解读
  14. 图书馆管理系统 Java
  15. java中的nio是啥,java中的NIO
  16. 电子计算机技术人才需求,电子与信息技术专业人才需求调研报告.pdf
  17. 【Python】多线程爬取某站高颜值小姐姐照片(共1.62GB)
  18. android调用相机分辨率,Android菜鸟笔记-获取摄像头像素值
  19. HWND与HANDLE的区别
  20. 【莫烦Python】Python 基础教程——学习笔记

热门文章

  1. PTA习题4-11 兔子繁衍问题 (15 分)-好容易入坑
  2. 基于浮云E绘图源码定制开发网络状态图(拓扑图),关联业务对象,并动态更新
  3. 2014热门网络词汇汇总
  4. Gravatar头像简单介绍与使用教程
  5. 力扣 6010. 完成旅途的最少时间
  6. linux双系统启动引导,linux windows 双系统并存与启动引导
  7. JSP+structs图书管理系统
  8. 循序渐进,探寻Excel二次开发.NET源码(3)-ExcelBase类
  9. Endnote中有的文献没有Pages信息怎么办?
  10. Xcode7 网络请求报错:The resource could not be loaded because the App Transport Security policy requir