这5个类型只是对应于不一样的item,存储的属性有区别,使用上都是同样的。数据库
不一样类型对应的属性:数组
既然苹果是采用SQLite去存储的,那么以上这些不一样item的attribute能够理解是数据库里面表的字段。那么对keychain的操做其实也就是普通数据库的增删改查了。这样也许就会以为那些API也没那么难用了。安全
NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecValueData : [@"1234562" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"loginPassword", }; CFErrorRef error = NULL; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
以这个添加kSecClassGenericPassword item为例,在字典里面咱们设置了如下几个属性:获取权限为当设备处于未锁屏状态,item的类型为kSecClassGenericPassword,item的value为@"123456", item的帐户名为@"account name", item的service为@"loginPassword"。最后,调用SecItemAdd进行插入。使用上有点像CoreData。服务器
NSDictionary *query = @{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrService : @"loginPassword", (__bridge id)kSecAttrAccount : @"account name" }; OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
删除一样也是指定以前存的item的属性,最后调用SecItemDelete这个方法。这边要注意的是劲量用多个字段肯定这个item,(虽然日常开发均可能是惟一)防止删除了其余item;好比咱们把kSecAttrAccount这个属性去掉,那么将会删除全部的kSecAttrService对应value为@"loginPassword"的item;app
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"loginPassword", }; NSDictionary *update = @{ (__bridge id)kSecValueData : [@"654321" dataUsingEncoding:NSUTF8StringEncoding], }; OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
苹果推荐咱们用SecItemUpdate去修改一个已经存在的item,可能咱们喜欢先调用SecItemDelete方法去删除,再添加一个新的。这个主要目的是防止新添的item丢失了原来的部分属性。这个方法须要两个入参,一个字典是用来指定要更新的item,另外一个字典是想要更新的某个属性的value,最后调用SecItemUpdate。框架
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"loginPassword", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef); if (status == errSecSuccess) { NSString *pwd = [[NSString alloc] initWithData:(__bridge NSData * _Nonnull)(dataTypeRef) encoding:NSUTF8StringEncoding]; NSLog(@"==result:%@", pwd); }
查和前面几个操做相似,首先一样是指定属性定位到这个item,最后调用SecItemCopyMatching方法。既然是数据库查询,确定会有记录的条数的问题。本例中使用了kSecMatchLimitOne,表示返回结果集的第一个,固然这个也是默认的。若是是查询出多个,kSecMatchLimitAll可使用这个,那么返回的将是个数组。SecItemCopyMatching方法的入参dataTypeRef,是一个返回结果的引用,会根据不一样的item,返回对应不一样的类型(如NSCFData, NSCFDictionary, NSCFArray等等)。async
刚刚上面是返回存储的value的引用,若是咱们想看看这个item全部的属性怎么办?咱们可使用kSecReturnRefide
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnRef : @YES, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"noraml", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef); if (status == errSecSuccess) { NSDictionary *dict = (__bridge NSDictionary *)dataTypeRef; NSString *acccount = dict[(id)kSecAttrAccount]; NSData *data = dict[(id)kSecValueData]; NSString *pwd = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSString *service = dict[(id)kSecAttrService]; NSLog(@"==result:%@", dict); }
这样,咱们就获得了这个item的全部属性。测试
同一个开发者帐号下(teamID),各个应用之间能够共享item。keychain经过keychain-access-groups
来进行访问权限的控制。在Xcode的Capabilities选项中打开Keychain Sharing便可。ui
每一个group命名开头必须是开发者帐号的teamId。不一样开发者帐号的teamId是惟一的,因此苹果限制了只有同一个开发者帐号下的应用才能够进行共享。若是有多个sharedGroup,在添加的时候若是不指定,默认是第一个group。
添加:
NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked, (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecValueData : [@"1234562" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrAccessGroup : @"XEGH3759AB.com.developer.test", (__bridge id)kSecAttrService : @"noraml1", (__bridge id)kSecAttrSynchronizable : @YES, }; CFErrorRef error = NULL; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
取:
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnRef : @YES, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll, (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrAccessGroup : @"XEGH3759AB.com.developer.test", (__bridge id)kSecAttrService : @"noraml1", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);
只须要添加一个kSecAttrAccessGroup属性便可。
(1)未对应用APP的entitlement(受权)进行配置时,APP使用钥匙串存储时,会默认存储在自身BundleID的条目下。
(2)对APP的entitlement(受权)进行配置后,说明APP有了对某个条目的访问权限。
钥匙串的可视化效果可参见Mac的APP-钥匙串访问。
APP钥匙串访问权限的配置方法:(这里XXXXX模拟器随意,但真机必须为本身开发者帐号ID,不然没法经过编译)
1.新建一个Plist文件,在Plist中的数组中添加能够访问的条目的名字(如KeychainAccessGroups.plist),结构以下:
Plist代码:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>keychain-access-groups</key> <array> <string>XXXXX.GrassInfoAppFamily</string> </array> </dict> </plist>
2.在Build-setting中进行配置,搜索entitlement,注意路径别配置错:
这个属性,决定了咱们item在什么条件下能够获取到里面的内容,咱们在添加item的时候,能够添加这个属性,来加强数据的安全性,具体的主要有如下几个:
kSecAttrAccessibleWhenUnlocked
kSecAttrAccessibleAfterFirstUnlock
kSecAttrAccessibleAlways
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
kSecAttrAccessibleAlwaysThisDeviceOnly
每一个意思都很明确,item默认就是kSecAttrAccessibleWhenUnlocked。也就是在设备未锁屏的状况下。这个也是苹果推荐的。kSecAttrAccessibleAlways,这个苹果在WWDC中也说了,不建议使用,苹果本身已经都弃用了。kSecAttrAccessibleAfterFirstUnlock这个是在设备第一次解锁后,可使用。这个最多见的就是后台唤醒功能里面,若是须要访问某个item,那么须要使用这个属性,否则是访问不了item的数据的。最后几个DeviceOnly相关的设置,若是设置了,那么在手机备份恢复到其余设备时,是不能被恢复的。一样iCloud也不会同步到其余设备,由于在其余设备上是解密不出来的。
keychain item能够备份到iCloud上,咱们只须要在添加item的时候添加@{(__bridge id)kSecAttrSynchronizable : @YES,}。若是想同步到其余设备上也能使用,请避免使用DeviceOnly设置或者其余和设备相关的控制权限。
ACL是iOS8新增的API,iOS9以后对控制权限进行了细化。在原来的基础上加了一层本地验证,主要是配合TouchID一块儿使用。对于咱们使用者来讲,在以前的item操做是同样的,只是在添加的时候,加了一个SecAccessControlRef对象。
CFErrorRef error = NULL; SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlUserPresence, &error); if (error) { NSLog(@"failed to create accessControl"); return; } NSDictionary *query = @{ (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecValueData : [@"accesscontrol test" dataUsingEncoding:NSUTF8StringEncoding], (__bridge id)kSecAttrAccount : @"account name", (__bridge id)kSecAttrService : @"accesscontrol", (__bridge id)kSecAttrAccessControl : (__bridge id)accessControl, }; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
咱们只须要建立SecAccessControlRef对象,主要是两个参数,一个是kSecAttrAccessible,另外一个是SecAccessControlCreateFlags。在字典里面添加(__bridge id)kSecAttrAccessControl : (__bridge id)accessControl便可。
SecAccessControlCreateFlags:
kSecAccessControlUserPresence
item经过锁屏密码或者Touch ID进行验证,Touch ID能够不设置,增长或者移除手指都能使用item。
kSecAccessControlTouchIDAny
item只能经过Touch ID验证,Touch ID 必须设置,增长或移除手指都能使用item。
kSecAccessControlTouchIDCurrentSet
item只能经过Touch ID进行验证,增长或者移除手指,item将被删除。
kSecAccessControlDevicePasscode
item经过锁屏密码验证访问。
kSecAccessControlOr
若是设置多个flag,只要有一个知足就能够。
kSecAccessControlAnd
若是设置多个flag,必须全部的都知足才行。
kSecAccessControlPrivateKeyUsage
私钥签名操做
kSecAccessControlApplicationPassword
额外的item密码,可让用户本身设置一个访问密码,这样只有知道密码才能访问。
获取操做和之前的都是同样的,只是加了一个提示信息kSecUseOperationPrompt,用来讲明调用意图:
NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnData : @YES, (__bridge id)kSecAttrService : @"accesscontrol", (__bridge id)kSecUseOperationPrompt : @"获取存储密码", }; CFTypeRef dataTypeRef = NULL; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef); if (status == errSecSuccess) { NSString *pwd = [[NSString alloc] initWithData:(__bridge NSData * _Nonnull)(dataTypeRef) encoding:NSUTF8StringEncoding]; NSLog(@"==result:%@", pwd); }
Secure Enclave 首次出如今iPhone 5s中,就是协处理器M7,用来保护指纹数据。SE里面的数据咱们用户层面代码是访问不了的,哪怕系统越狱了,也没法访问到里面数据。只有特定的代码才能去访问(CPU 切换成Monitor Mode)。SE自己也集成了加密库,加密解密相关的都在SE内部完成,这样应用程序只能拿到最后的结果,而没法拿到原始的数据。(关于Secure Enclave 能够搜些资料了解下,这里就不展开了)。在iOS9以后苹果开放了一个新的属性:kSecAttrTokenIDSecureEnclave,也就是将数据保存到SE里面,固然只是key。
如何使用:
//生成ECC公私钥 CFErrorRef error = NULL; SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAccessControlPrivateKeyUsage | kSecAccessControlTouchIDAny, &error); if (error) { NSLog(@"failed to create accessControl"); return; } NSDictionary *params = @{ (__bridge id)kSecAttrTokenID: (__bridge id)kSecAttrTokenIDSecureEnclave, (__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeEC, (__bridge id)kSecAttrKeySizeInBits: @256, (__bridge id)kSecPrivateKeyAttrs: @{ (__bridge id)kSecAttrAccessControl: (__bridge_transfer id)accessControl, (__bridge id)kSecAttrIsPermanent: @YES, (__bridge id)kSecAttrLabel: @"ECCKey", }, }; SecKeyRef publickKey, privateKey; OSStatus status = SecKeyGeneratePair((__bridge CFDictionaryRef)params, &publickKey, &privateKey); [self handleError:status]; if (status == errSecSuccess) { CFRelease(privateKey); CFRelease(publickKey); } //签名 NSDictionary *query = @{ (__bridge id)kSecClass: (__bridge id)kSecClassKey, (__bridge id)kSecAttrKeyClass: (__bridge id)kSecAttrKeyClassPrivate, (__bridge id)kSecAttrLabel: @"ECCKey", (__bridge id)kSecReturnRef: @YES, (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, (__bridge id)kSecUseOperationPrompt: @"签名数据" }; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Retrieve the key from the keychain. No authentication is needed at this point. SecKeyRef privateKey; OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&privateKey); if (status == errSecSuccess) { // Sign the data in the digest/digestLength memory block. uint8_t signature[128]; size_t signatureLength = sizeof(signature); uint8_t digestData[16]; size_t digestLength = sizeof(digestData); status = SecKeyRawSign(privateKey, kSecPaddingPKCS1, digestData, digestLength, signature, &signatureLength); if (status == errSecSuccess) { NSLog(@"sign success"); } CFRelease(privateKey); } else { } });
以上代码就是生成了一对公私钥(ECC 256),私钥会保存在SE中,而公钥交给应用程序。签名操做的时候,好像咱们取到了私钥,可是实际上咱们并不能拿到私钥,只是私钥在SE中的一个引用。加密的操做也是在SE中完成,最后返回给咱们签名的数据。
苹果在这边举了个简单例子,如何利用Touch ID进行登陆。客户端生成一对公私钥,公钥发给服务器,客户端在经过Touch ID校验后,加密一段内容(私钥签名操做),将内容和结果发送给服务器,服务器取出公钥进行验签。若是一致,则经过验证。
上面这个图就是普通item的一个解密流程。应用程序经过API访问item,在keychain里面取出加密的item,将加密的item,传递给SE解密,解密完返回给keychain,最后返回给应用。
iOS8后,苹果将中间的keychain框架进行了拆分,增长了本地受权认证:
这个最大的用途就是和Touch ID进行结合,来提升咱们的数据安全性。当咱们取item的时候,若是须要Touch ID进行验证,在SE里面,若是经过验证那么将对数据进行解密,并返回给keychain,最后返回给应用程序。
iOS9以后的keyStore也放进了SE里面,进一步提升了安全性。至于keychain的安全性在非越狱下的确是安全的,可是一旦手机越狱,应用能够访问到其余应用程序item,或者经过Keychain-Dumper导出keychain数据,那么就不是很安全了。因此在咱们存进钥匙串的数据,不要直接存一些敏感信息,在程序中加一层数据保护。
参考:
安全白皮书
Keychain and Authentication with Touch ID
Protecting Secrets with the Keychain
Security and Your Apps
我在官方文档中并未找到相关的更新:https://developer.apple.com/documentation/security/keychain_services
你们仍是能够放心用的
iOS 10.3 还未正式发布,beta 版中一个关于keychain 特性的小修改,就已经引发了普遍的关注。
改动以下:
若是 App 被删除,以前存储于 keychain 中的数据也会一同被清除。
若是使用了 keychain group,只要当 group 全部相关的 App 被删除时,keychain 中的数据才会被删除。
这一改动,虽未经官方公布。但已在论坛帖子里获得了 Apple 员工的确认,原文以下:
This is an intentional change in iOS 10.3 to protect user privacy. Information that can identify a user should not be left on the device after the app that created it has been removed.
It has never been a part of the API contract that keychain items created by an app would survive when the app is removed. This has always been an implementation detail.
If a keychain item is shared with other apps, it won’t be deleted until those other apps have been deleted as well.
若是这是这样的话,那么keychain存在还有什么意义么?
还有苹果如今愈来愈注重用户的隐私,就前几天对于使用JSPatch热更新的机制的应用发送的邮件来看,苹果彷佛要在这方面有动做了,我想说,苹果爸爸此次难道真的要为Swift和OC两个亲儿子出头了吗?
其实我也以为 app 都删了 keychain 还在是挺不合理的一件事儿。在隐私保护上仍是能够看得出 Apple 仍是一直在做为。
因为苹果频繁的更新,以前的一些东西已经不能使用https://forums.developer.apple.com/message/210531#210531