文件保护用来保护**数据**,而钥匙串则用来保护**秘密**。在这里,秘密是指用来访问其他数据的一小段数据。最常见的秘密就是密码和私钥了。
钥匙串由操作系统保护,在设备锁定时会进行加密处理。实际上,它的工作原理跟文件保护很像。不幸的是,Keychain API并不友好,所以许多开发人员为Keychain API做了一些包装。不过,笔者推荐使用的是苹果GenericKeychain
示例代码中的KeyChainItemWrapper
。本节将在简要介绍底层的数据结构之后介绍它。
KeyChainItemWrapper
有一些问题,其中最突出的问题就是它并不兼容ARC。可以将KeyChainItemWrapper.m文件的ARC关掉,或者增加需要的__bridge
类型转换。在网络上搜索GenericKeyChain ARC,可以找到一些做过这方面工作的人写的文章。它并不像我们想的那样方便。有若干种方法都能用来完成这个任务。虽然这些方法都还不成熟,尚不足在这里推荐,但一切都在改进。关注一下iosptl.com上的更新。
钥匙串中的条目称为SecItem
,但它是存储在CFDictionary
中的。SecItemRef
类型并不存在。SecItem
有五类:通用密码、互联网密码、证书、密钥和身份。在大多数情况下,我们用到的都是通用密码。许多问题都是开发人员尝试用互联网密码造成的。互联网密码要复杂得多,而且相比之下优势寥寥无几,除非开发Web浏览器,否则没必要用它。KeyChainItemWrapper
只使用通用密码,这也是我喜欢它的原因之一。iOS应用很少将密钥和身份存储起来,所以我们在本书中不会讨论这方面的内容。只有公钥的证书通常应该存储在文件中,而不是钥匙串中。
最后,我们需要在钥匙串中搜索需要的内容。密钥有很多个部分可用来搜索,但最好的办法是将自己的标识符赋给它,然后搜索。通用密码条目都包含属性kSecAttrGeneric
,可以用它来存储标识符。这也是KeyChainItemWrapper
的处理方式。
钥匙串中的条目都有几个可搜索的**属性**和一个加密过的**值**。对于通用密码条目,比较重要的属性有账户(kSecAttrAccount
)、服务(kSecAttrService
)和标识符(kSecAttrGeneric
)。而值通常是密码。
有了这个背景,现在我们可以看看如何使用KeyChainItemWrapper
了。首先,如下代码所示,可以用initWithIdentifier:accessGroup:
来创建一个密码。稍后介绍访问组的概念,先将它保留为nil
。
现在可以对wrapper
进行读取和写入了,就跟使用NSDictionary
一样。它会自动跟钥匙串同步。__bridge
强制转换用来将Core Foundation的常量传给一个使用ARC的Cocoa方法。
注意,我使用的是个Core Foundation类型(kSecAttrAccount
),这里它需要用到id
(objectForKey:
),不要用类型转换,也不要用__bridge
。这是一个新特性。
KeyChainItemWrapper
会缓存读入数据,但不会缓存写出的数据。向钥匙串中写数据的成本很高,所以我们不会频繁写入。钥匙串不适合用来存储经常改变的敏感数据。那类数据应该保存到加密文件中,可以参考15.3节的内容。