iOS keyChain 的使用

详细资料,请参看苹果官方文档Keychain Services Reference 。

ios中的keychain,用于保存用户的机密信息,对keychain的操作有4种,就是 增,删,改,查:

SecItemCopyMatching

Returns one or more keychain items that match a search query, or copies attributes of specific keychain items. 

SecItemAdd
Adds one or more items to a keychain.

SecItemUpdate
Modifies items that match a search query.

SecItemDelete
Deletes items that match a search query. 

 


每个操作,都需要定义相应的CFDictionary,下面看看如何定义。

操作函数需要的CFDictionary中的信息,介绍如下:

A dictionary containing an item class key-value pair (“Keychain Item Class Keys and Values” (page 11)) and optional attribute key-value pairs (“Attribute Item Keys and Values” (page 16)) specifying the item's attribute values. 

上边提到了2种key-value pair,一种是 item class key-value pair,一种是optional attribute key-value pair,首先要确定item所用的class,之后根据不同的class会用到不同的attribute。

item class key-value pair,它被描述成Keychain Item Class Keys and Values。它的key是kSecClass ,它的value有5种:

 

kSecClassGenericPassword,//存储普通的密码,
kSecClassInternetPassword,//存储网络密码
kSecClassCertificate,//存储证书,证书中包含共有密匙
kSecClassKey,//存储密匙,其实就是私有密匙
kSecClassIdentity,//存储Identity item,包括一个证书和一个密匙

 

这5种value都有相应的attribute的key-value pair 可以使用,请参看官方文档。这些attribute key-value pair 的key都是固定的常量; 他们的value,有的是常量, 有的是NSString之类的变量,可以写入自定义内容。另外,这些attribute key-value pair 有的指定了检索的条件,有的指定了检索的返回值,等等,功能不同。


下面看看如何添加一个普通密码到keychain中
- (void)addIdentityIntoKeyChain
{ 
    OSStatus sanityCheck = noErr;    

    NSDictionary *dic = [NSDictionarydictionaryWithObjectsAndKeys: 

                         (__bridge id)(kSecClassGenericPassword) ,kSecClass,

                         @"this a my description",kSecAttrDescription,

                         [@"1234"dataUsingEncoding:NSUTF8StringEncoding], kSecValueData,

                //        [@"5678" dataUsingEncoding:NSUTF8StringEncoding], kSecValueRef,

                         nil];    

    NSLog(@"add dic is %@",dic);   

    CFDictionaryRef dicRef = ( __bridgeCFDictionaryRef)dic;    

    sanityCheck = SecItemAdd(dicRef, nil);

    NSLog(@"%ld",sanityCheck);    

}

再看看针对上面这个item的2种不同的查询,

第一种,查询具体的密码,即我们真正需要保存的东西

- (void)getPass2
{
    NSLog(@"pass 2===========");    

    NSDictionary *queryDictionary = [NSDictionarydictionaryWithObjectsAndKeys:

                                     @"this a my description",kSecAttrDescription,

                                     kCFBooleanTrue, kSecReturnData,

                                     kSecClassGenericPassword, kSecClass,

                                     nil];    

    CFTypeRef handle = NULL;

    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryDictionary, &handle);    

    NSLog(@"status is %ld",status);    

    if (status == noErr) {

        NSData *data = (__bridge NSData *)handle;

        NSString *str = [[NSStringalloc] initWithData:data encoding:NSUTF8StringEncoding];

        NSLog(@"str is %@",str);        

    }    

}

第二种,查询这个item的各种属性,不查询具体的密码

- (NSString *)getPasswordFromKeyChain

{

    NSLog(@"pass 1===========");

    NSDictionary *queryDictionary = [NSDictionarydictionaryWithObjectsAndKeys:

                                    @"this a my description",kSecAttrDescription,

                                    kCFBooleanTrue, kSecReturnAttributes,

                                    kSecClassGenericPassword, kSecClass,

                                    nil];   

    CFTypeRef handle = NULL;

    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryDictionary, &handle);    

    NSLog(@"status is %ld",status);    

    if (status == noErr) {

       NSDictionary *dic = (__bridge_transfer NSDictionary*) handle ;

        NSLog(@"dic is %@",dic);        

    }    

   return@"";

} 

 

请查看具体的输出,来理解这2中查询的不同。 

 

2014-03-14 10:50:15.338 KeyChainTest[25074:907] add dic is {
    class = genp;
    desc = "this a my description";
    "v_Data" = <31323334>;
}
2014-03-14 10:50:15.356 KeyChainTest[25074:907] -25299
2014-03-14 10:50:15.359 KeyChainTest[25074:907] pass 1===========
2014-03-14 10:50:15.369 KeyChainTest[25074:907] status is 0
2014-03-14 10:50:15.376 KeyChainTest[25074:907] dic is {
    acct = "";
    agrp = "BDN8QNY54S.com.Kings.test.KeyChainTest";
    cdat = "2013-10-16 06:02:54 +0000";
    desc = "this a my description";
    mdat = "2013-10-16 06:02:54 +0000";
    pdmn = ak;
    svce = "";
}
2014-03-14 10:50:15.381 KeyChainTest[25074:907] pass 2===========
2014-03-14 10:50:15.389 KeyChainTest[25074:907] status is 0
2014-03-14 10:50:15.393 KeyChainTest[25074:907] str is 1234

 


 

另外,在keychain的使用中,还经常涉及一个问题,就是2个程序共享机密信息。这常常发生在以下情况:大型公司专门写了一个用户认证程序,指定的机器可以装上这个程序,用这个程序时要求输入帐号和密码,验证通过后,程序会从服务器取公司的证书和用户的私有密匙,并将他们加入到程序的keychain中 。之后该公司写的其他程序,如果需要帐号和密码,或者证书进行服务器验身份证时,就可以用刚才提到的用户认证程序在keychain中保存的信息。

ios程序默认的keychain是不能够和其他程序共享的,如果想共享自己的信息,那么需要加入到一个Keychain Access Groups中,xcode5在工程设定中可以添加。主要注意的是这个Keychain Access Groups的名字是有限制的,必须和你的provisioning profile向一致,这就保证不可能访问到其他公司的机密信息。

 

转一些别人的心得:

1.相同bundle下生成的程序都可以共享相同group的keyChain.(我对这一条的理解不太一样,我感觉是这样的:bundle Name 分别是 com.companyName.A 和 com.companyName.B,那么这两个程序的keychain group name 就必须是 com.companyName.xxxx)

相同bundle解释下就是:比如:2个程序分别使用的provision对应bundle是com.jv.key1和com.jv.key2,那你配置文件肯定是{Identifer}.com.jv.{name},其中identifer是苹果生成的随机串号,可以在申请证书时看到,复制过来即可,name可以自己取,程序中指定属于哪个Group即可。

2.如果你在 addkey时,没有指定group,则会默认添加你keychain-access-groups里第一个group(这是keychain group中经常写2个group的原因,简历一个自己的group,防止污染主要的keychain group),如果你没有设置Entitlements,则默认使用对应的程序的bundle name,比如com.jv.key1,表示只能给自己程序使用。

3.如果你程序添加的group并不存在你的配置文件中,程序会奔溃,表示无法添加。因此你只能添加你配置文件中支持的keychain。

 

 

posted @ 2013-10-16 14:09  幻化成疯  阅读(1312)  评论(0编辑  收藏  举报