iOS的keyChain是一个相对独立的空间,当我们的程序(App)被替换或者删除时并不会删除保存在keyChain的内容。相对于NSUserDefaults、plist文件保存等一般方式,keychain保存更为安全。所以我们会用keyChain保存一些私密信息,比如密码、证书、设备唯一码(UDID)等等。
我们可以把KeyChain理解为一个Dictionary,所有数据都以key-value的形式存储,可以对这个Dictionary进行add、update、get、delete这四个操作。
数据结构如下:
对于每一个应用来说,KeyChain都有两个访问区,私有区和公共区。私有区是一个sandbox,本程序存储的任何数据都对其他程序不可见,其他应用程序无法访问该区数据。如果要想将存储的内容放在公共区,实现多个应用程序间可以共同访问一些数据,则可以先声明公共区的名称,官方文档管这个名称叫“keychain access group”。声明的方法是新建一个plist文件,名字随便起。如上:“YOUR_APP_ID_HERE.com.jaybin.keychain.test” 就是这个公共访问存储区的名称。
这里我们先介绍 KeyChain 私有区的访问存储,即该私有区只能给自己程序使用。比如保存用户密码,设备唯一码(UDID)等等。要在应用里使用使用keyChain,我们需要导入Security.framework ,keychain的操作接口声明在头文件SecItem.h里。直接使用SecItem.h里方法操作keychain,需要写的代码较为复杂,为减轻开发,我们可以使用一些已经封装好了的工具类。KeychainItemWrapper是apple官方例子“GenericKeychain”里一个访问keychain常用操作的封装类,在官网上下载了GenericKeychain项目后,只需要把“KeychainItemWrapper.h”和“KeychainItemWrapper.m”这两个文件拷贝到项目,并导入Security.framework
。
1、保存账号信息到KeyChain:
/* 初始化一个保存用户帐号的 KeychainItemWrapper */ KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"userAccount" accessGroup:nil]; //向keychain里存储用户名、密码 NSString *username = @"JayBin"; NSString *password = @"123"; [wrapper setObject:username forKey:(id)kSecAttrAccount]; [wrapper setObject:password forKey:(id)kSecValueData];
标识符(Identifier)在后面我们要从keychain中取数据的时候会用到。如果你想要在应用之间共享信息,那么你需要指定访问组(keychain access group)。有同样的访问组的应用才能够访问同样的keychain信息。这里我们先介绍私有区,所以accessGroup选项填“nil”。需要注意的是方法 “- (void)setObject:(id)inObject forKey:(id)key;” 里参数“forKey”的值应该是Security.framework 里头文件“SecItem.h”里定义好的key,用其他字符串做key程序会崩溃。
通过上面的代码操作,我们已经将用户账号信息保存在该应用对应的keychain中,即使该应用被删除了,keychain中的账号信息也不会被删除。
2、从KeyChain获得存储的账号信息:
/* 初始化一个 KeychainItemWrapper */ KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"userAccount" accessGroup:nil]; username = [wrapper objectForKey:(id)kSecAttrAccount]; password = [wrapper objectForKey:(id)kSecValueData]; NSLog(@"name:%@",username); NSLog(@"password:%@",password);
注意,该KeychainItemWrapper 对象的标识符(userAccount)必须和我们上面保存账号信息的标示符一致。accessGroup选项还是填“nil”。当然wrapper对象对应的Key要对应上。
到这里我们还是具体了解一下钥匙串的基本数据结构。
钥匙串中的条目称为SecItem
,但它是存储在CFDictionary
中的。SecItemRef
类型并不存在。SecItem
有五类:通用密码、互联网密码、证书、密钥和身份。在大多数情况下,我们用到的都是通用密码。上面介绍的KeyChainItemWrapper
也就是只使用通用密码。最后,我们需要在钥匙串中搜索需要的内容。密钥有很多个部分可用来搜索,但最好的办法是将自己的标识符赋给它,然后搜索。通用密码条目都包含属性kSecAttrGeneric
,可以用它来存储标识符。这也是KeyChainItemWrapper
的处理方式。
 如上图,每一个keyChain的组成如图,整体是一个字典结构.
1.kSecClass key 定义属于那一种类型的keyChain(通用密码、互联网密码、证书、密钥和身份)
2.不同的类型包含不同的Attributes,这些attributes定义了这个item的具体信息
3.每个item可以包含一个密码项来存储对应的密码
一般的使用方式如下:
/* 初始化一个 KeychainItemWrapper */ KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"userAccount" accessGroup:nil]; //设置keyChain的类型为通用密码 [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];
当然我们想知道到底iOS系统和第三方应用在Keychain里都存放了哪些信息,我们可以使用Keychain dumper。目前从Keychain中导出数据的最流行工具是ptoomey3的Keychain dumper。详情可以参照此博文http://blog.csdn.net/yiyaaixuexi
。
后面我们介绍Keychain的公共访问存储区,实现不同APP共享Keychain中的数据。
要实现多个应用程序间可以共同访问Keychain的一些数据,要先声明Keychain公共区的名称,官方文档管这个名称叫“keychain access group”。声明的方法是新建一个plist文件,名字随便起。如:“YOUR_APP_ID_HERE.com.jaybin.keychain.test” 就是这个公共访问存储区的名称。这里我们通过新版的Xcode可以简便很多。
Project->Capebilities->Keychain Sharing ,将Keychain Sharing打开。如下图:
上图的keychain groups 填的就是这个公共访问存储区的名称 “YOUR_APP_ID_HERE.com.jaybin.keychain.test”。(当然,共享Keychain数据的多个应用设置的公共访问存储区名称应该是相同的)。我们可以看到下方显示两个“√” ,如果是显示“×” 可以点击 fixit 。这是因为这个文件的路径要配置在 Project->build setting->Code Signing Entitlements
里,否则公共区无效。配置好后,须用你正式的证书签名编译才可通过。否则xcode会弹框告诉你code signing有问题。所以,苹果限制了你只能同公司的产品共享KeyChain数据,别的公司访问不了你公司产品的KeyChain。在新版的Xcode中,将Keychain Sharing打开后,会在项目对应的目录下自动生成对应的Entitlements文件(一个plist文件)。如下图:
并且会在 Project->build setting->Code Signing Entitlements 里自动配置这个文件的路径。
到这里,我们基本上完成了Keychain的公共访问存储区“keychain access group”的设置部署了。需要注意的是,由于“keychain access group”配置好后,须用正式的证书签名编译才可通过。所以,苹果限制了你只能同公司的产品共享KeyChain数据,别的公司访问不了你公司产品的KeyChain。这里是指,相同bundle的应用程序通过设置 group 可以互相共享同组的keychain数据。相同bundle解释就是:比如这里有两个应用程序:
A应用程序使用的provision对应的 bundle id是 com.jaybin.keychain1,B应用程序使用的provision对应的 bundle id是 com.jaybin.keychain2 。那么这两个应用程序就可以共享keychain数据。下面以这两个应用程序为例。(这两个程序必须按照上面的步骤开启了Keychain Sharing,并且设置了相同的“keychain access group”)
应用程序A(bundle id是 com.jaybin.keychain1)向Keychain的公共访问存储区存入账号信息。
/* 初始化一个保存用户帐号的 KeychainItemWrapper */ KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"userAccount" accessGroup:nil]; //向keychain里存储用户名、密码 NSString *username = @"JayBin"; NSString *password = @"123"; [wrapper setObject:username forKey:(id)kSecAttrAccount]; [wrapper setObject:password forKey:(id)kSecValueData];
应用程序B(bundle id是 com.jaybin.keychain2)从Keychain的公共访问存储区中读取应用程序A存储的账号信息。
/* 初始化一个 KeychainItemWrapper */ KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"userAccount" accessGroup:nil]; username = [wrapper objectForKey:(id)kSecAttrAccount]; password = [wrapper objectForKey:(id)kSecValueData]; NSLog(@"name:%@",username); NSLog(@"password:%@",password);
需要注意的是在创建KeychainItemWrapper 对象时并没有指定特定的 group,因为在我们已经开启了Keychain Sharing并且设置了Entitlements,系统则会默认添加你keychain-access-groups里第一个group,即我们上面已经声明的group( “YOUR_APP_ID_HERE.com.jaybin.keychain.test”)。