ios下 KeyChain 的研究

       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”)。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。