iOS - keychain详解及变化

keychain介绍

iOS keychain 是一个相对独立的空间,保存到keychain钥匙串中的信息不会因为卸载/重装app而丢失。相对于NSUserDefaults、plist文件保存等一般方式,keychain保存更为安全。所以我们会用keyChain保存一些私密信息,比如密码、证书、设备唯一码(把获取到用户设备的唯一ID 存到keychain 里面这样卸载或重装之后还可以获取到id,保证了一个设备一个ID)等等。keychain是用SQLite进行存储的。用苹果的话来说是一个专业的数据库,加密我们保存的数据,可以通过metadata(attributes)进行高效的搜索。keychain适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。

keychain的基本使用

keychain的类型
  • kSecClassGenericPassword

  • kSecClassInternetPassword

  • kSecClassCertificate

  • kSecClassKey

  • kSecClassIdentity

这5个类型只是对应于不同的item,存储的属性有区别,使用上都是一样的。

不同类型对应的属性:

既然苹果是采用SQLite去存储的,那么以上这些不同item的attribute可以理解是数据库里面表的字段。那么对keychain的操作其实也就是普通数据库的增删改查了。这样也许就会觉得那些API也没那么难用了。

以这个添加kSecClassGenericPassword item为例,在字典里面我们设置了以下几个属性:获取权限为当设备处于未锁屏状态,item的类型为kSecClassGenericPassword,item的value为@"123456", item的账户名为@"account name", item的service为@"loginPassword"。最后,调用SecItemAdd进行插入。使用上有点像CoreData。

删除同样也是指定之前存的item的属性,最后调用SecItemDelete这个方法。这边要注意的是劲量用多个字段确定这个item,(虽然平常开发都可能是唯一)防止删除了其他item;比如我们把kSecAttrAccount这个属性去掉,那么将会删除所有的kSecAttrService对应value为@"loginPassword"的item;

苹果推荐我们用SecItemUpdate去修改一个已经存在的item,可能我们喜欢先调用SecItemDelete方法去删除,再添加一个新的。这个主要目的是防止新添的item丢失了原来的部分属性。这个方法需要两个入参,一个字典是用来指定要更新的item,另一个字典是想要更新的某个属性的value,最后调用SecItemUpdate。

查和前面几个操作类似,首先同样是指定属性定位到这个item,最后调用SecItemCopyMatching方法。既然是数据库查询,肯定会有记录的条数的问题。本例中使用了kSecMatchLimitOne,表示返回结果集的第一个,当然这个也是默认的。如果是查询出多个,kSecMatchLimitAll可以使用这个,那么返回的将是个数组。SecItemCopyMatching方法的入参dataTypeRef,是一个返回结果的引用,会根据不同的item,返回对应不同的类型(如NSCFData, NSCFDictionary, NSCFArray等等)。

刚刚上面是返回存储的value的引用,如果我们想看看这个item所有的属性怎么办?我们可以使用kSecReturnRef

这样,我们就得到了这个item的所有属性。

Sharing Items

同一个开发者账号下(TeamId),各个应用之间可以共享item。keychain通过keychain-access-groups
来进行访问权限的控制。在Xcode的Capabilities选项中打开Keychain Sharing即可。

每个group命名开头必须是开发者账号的TeamId。不同开发者账号的TeamId是唯一的,所以苹果限制了只有同一个开发者账号下的应用才可以进行共享。如果有多个sharedGroup,在添加的时候如果不指定,默认是第一个group

添加:

取:

只需要添加一个kSecAttrAccessGroup属性即可。

APP对keychain的访问权限

(1)未对应用APP的entitlement(授权)进行配置时,APP使用钥匙串存储时,会默认存储在自身BundleID的条目下。

(2)对APP的entitlement(授权)进行配置后,说明APP有了对某个条目的访问权限。

 

钥匙串的可视化效果可参见Mac的APP-钥匙串访问。

APP钥匙串访问权限的配置方法:(这里XXXXX模拟器随意,但真机必须为自己开发者账号ID,否则无法通过编译)

1.新建一个Plist文件,在Plist中的数组中添加可以访问的条目的名字(如KeychainAccessGroups.plist),结构如下:

Plist代码:

2.在Build-setting中进行配置,搜索entitlement,注意路径别配置错:

keychain安全相关

kSecAttrAccessible

这个属性,决定了我们item在什么条件下可以获取到里面的内容,我们在添加item的时候,可以添加这个属性,来增强数据的安全性,具体的主要有以下几个:

  • kSecAttrAccessibleWhenUnlocked

  • kSecAttrAccessibleAfterFirstUnlock

  • kSecAttrAccessibleAlways

  • kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly

  • kSecAttrAccessibleWhenUnlockedThisDeviceOnly

  • kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly

  • kSecAttrAccessibleAlwaysThisDeviceOnly

每个意思都很明确,item默认就是kSecAttrAccessibleWhenUnlocked。也就是在设备未锁屏的情况下。这个也是苹果推荐的。kSecAttrAccessibleAlways,这个苹果在WWDC中也说了,不建议使用,苹果自己已经都弃用了。kSecAttrAccessibleAfterFirstUnlock这个是在设备第一次解锁后,可以使用。这个最常见的就是后台唤醒功能里面,如果需要访问某个item,那么需要使用这个属性,不然是访问不了item的数据的。最后几个DeviceOnly相关的设置,如果设置了,那么在手机备份恢复到其他设备时,是不能被恢复的。同样iCloud也不会同步到其他设备,因为在其他设备上是解密不出来的。

iCloud

keychain item可以备份到iCloud上,我们只需要在添加item的时候添加@{(__bridge id)kSecAttrSynchronizable : @YES,}。如果想同步到其他设备上也能使用,请避免使用DeviceOnly设置或者其他和设备相关的控制权限。

Access Control

ACL是iOS8新增的API,iOS9之后对控制权限进行了细化。在原来的基础上加了一层本地验证,主要是配合TouchID一起使用。对于我们使用者来说,在之前的item操作是一样的,只是在添加的时候,加了一个SecAccessControlRef对象。

我们只需要创建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,用来说明调用意图:

Secure Enclave

Secure Enclave 首次出现在iPhone 5s中,就是协处理器M7,用来保护指纹数据。SE里面的数据我们用户层面代码是访问不了的,哪怕系统越狱了,也无法访问到里面数据。只有特定的代码才能去访问(CPU 切换成Monitor Mode)。SE本身也集成了加密库,加密解密相关的都在SE内部完成,这样应用程序只能拿到最后的结果,而无法拿到原始的数据。(关于Secure Enclave 可以搜些资料了解下,这里就不展开了)。在iOS9之后苹果开放了一个新的属性:kSecAttrTokenIDSecureEnclave,也就是将数据保存到SE里面,当然只是key。

如何使用:

以上代码就是生成了一对公私钥(ECC 256),私钥会保存在SE中,而公钥交给应用程序。签名操作的时候,好像我们取到了私钥,但是实际上我们并不能拿到私钥,只是私钥在SE中的一个引用。加密的操作也是在SE中完成,最后返回给我们签名的数据。
苹果在这边举了个简单例子,如何利用Touch ID进行登录。客户端生成一对公私钥,公钥发给服务器,客户端在通过Touch ID校验后,加密一段内容(私钥签名操作),将内容和结果发送给服务器,服务器取出公钥进行验签。如果一致,则通过验证。

如果需要重新创建同名的密钥对,则需要先删除之前的密钥对,否则不能生效,删除的代码参考如下:

上面的代码,在调用 SecKeyRawSign 的时候,需要指纹/人脸确认,才能进行加密操作。但是有时候,我们只希望Secure Enclave 生成安全密钥,并不希望密钥与指纹/人脸关联(比如用来网络通信的加解密,不可能每次网络请求都让用户指纹确认)。我们只需要在创建密钥的时候,去掉验证的时候对于指纹/人脸的要求(移除 kSecAccessControlTouchIDAny/kSecAccessControlBiometryAny),同时去掉对于解锁才能访问的要求(kSecAttrAccessibleWhenUnlockedThisDeviceOnly 替换为: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)。

代码参考如下(如果上面已经生成过密钥,需要先执行一次删除操作):

上述代码如果报错,错误代码详情可以从 SecBase.h 文件查询。

如果需要导出公钥然后传递给服务器进行加密数据,可以参考如下代码:

上述代码只能在 iOS 10.0以及之后的系统才能调用。如果要兼容之前的系统,则需要在创建密钥对的时候,立即从返回的公钥引用中导出。

执行如下代码(低于 iOS 10.0 的系统密钥对创建完成后,无法直接查询公钥,只能查询私钥,导致公钥无法二次导出):

item 解密过程

上面这个图就是普通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

//以下的是有些人说跟新了 但是我测试了一下 删除之后 keychain里面还是有数据的

我在官方文档中并未找到相关的更新: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

参考链接


发布者

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注