iOS Keychain理解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS Keychain理解相关的知识,希望对你有一定的参考价值。

参考技术A Keychain 介绍

Keychain Services 是 OS X 和 iOS 都提供一种安全地存储敏感信息的工具,比如,存储用户ID,密码,和证书等。存储这些信息可以免除用户重复输入用户名和密码的过程。Keychain Services 的安全机制保证了存储这些敏感信息不会被窃取。简单说来,Keychain 就是一个安全容器。

Keychain 的结构

Keychain 可以包含任意数量的 keychain item。每一个 keychain item 包含数据和一组属性。对于一个需要保护的 keychain item,比如密码或者私钥(用于加密或者解密的string字节)数据是加密的,会被 keychain 保护起来的;对于无需保护的 keychain item,例如,证书,数据未被加密。

跟keychain item有关系的取决于item的类型;应用程序中最常用的是网络密码(Internet passwrods)和普通的密码。正如你所想的,网络密码像安全域(security domain)、协议、和路径等一些属性。在OSX中,当keychain被锁的时候加密的item没办法访问,如果你想要该问被锁的item,就会弹出一个对话框,需要你输入对应keychain的密码。当然,未有密码的keychain你可以随时访问。但在iOS中,你只可以访问你自已的keychain items;

item可以指定为以下的类型:

externCFTypeRefkSecClassGenericPasswordexternCFTypeRefkSecClassInternetPasswordexternCFTypeRefkSecClassCertificateexternCFTypeRefkSecClassKeyexternCFTypeRefkSecClassIdentity OSX_AVAILABLE_STARTING(MAC_10_7, __IPHONE_2_0);

Keychain的特点

数据并不存放在App的Sanbox中,即使删除了App,资料依然保存在keychain中。如果重新安装了app,还可以从keychain获取数据。

keychain的数据可以用过group方式,让程序可以在App间共享。不过得要相同TeamID

keychain的数据是经过加密的

Keychain的使用

大多数iOS应用需要用到Keychain, 都用来添加一个密码,修改一个已存在Keychain item或者取回密码。Keychain提供了以下的操作

SecItemAdd 添加一个item

SecItemUpdate 更新已存在的item

SecItemCopyMatching  搜索一个已存在的item

SecItemDelete 删除一个keychain item

Talk is Cheap,Show you the Code

根据特定的Service创建一个用于操作KeyChain的Dictionary

+ (NSMutableDictionary*)keyChainQueryDictionaryWithService:(NSString*)serviceNSMutableDictionary*keyChainQueryDictaionary = [[NSMutableDictionaryalloc]init];    [keyChainQueryDictaionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];    [keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrService];    [keyChainQueryDictaionary setObject:service forKey:(id)kSecAttrAccount];returnkeyChainQueryDictaionary;

添加数据

+ (BOOL)addData:(id)data forService:(NSString*)serviceNSMutableDictionary*keychainQuery = [selfkeyChainQueryDictionaryWithService:service];    SecItemDelete((CFDictionaryRef)keychainQuery);    [keychainQuery setObject:[NSKeyedArchiverarchivedDataWithRootObject:data] forKey:(id)kSecValueData];    OSStatus status= SecItemAdd((CFDictionaryRef)keychainQuery,NULL);if(status == noErr) returnYES;    returnNO;

搜索数据

+ (id)queryDataWithService:(NSString*)service idresult;NSMutableDictionary*keyChainQuery = [selfkeyChainQueryDictionaryWithService:service];    [keyChainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];    [keyChainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];CFDataRefkeyData =NULL;if(SecItemCopyMatching((CFDictionaryRef)keyChainQuery, (CFTypeRef*)&keyData) == noErr) @try            result = [NSKeyedUnarchiverunarchiveObjectWithData:(__bridgeNSData*)keyData];        @catch(NSException*exception) NSLog(@"不存在数据");        @finally                        if(keyData) CFRelease(keyData);    returnresult;

更新数据

+ (BOOL)updateData:(id)data forService:(NSString*)serviceNSMutableDictionary*searchDictionary = [selfkeyChainQueryDictionaryWithService:service];if(!searchDictionary) returnNO;    NSMutableDictionary*updateDictionary = [[NSMutableDictionaryalloc] init];    [updateDictionary setObject:[NSKeyedArchiverarchivedDataWithRootObject:data] forKey:(id)kSecValueData];    OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,                                    (CFDictionaryRef)updateDictionary);if(status == errSecSuccess) returnYES;    returnNO;

删除数据

+ (BOOL)deleteDataWithService:(NSString*)serviceNSMutableDictionary*keyChainDictionary = [selfkeyChainQueryDictionaryWithService:service];    OSStatus status = SecItemDelete((CFDictionaryRef)keyChainDictionary);if(status == noErr) returnYES;    returnNO;

Keychain 共享数据

先开启Keychain share,选中项目的Target -> Capabilities -> Keychain Groups。打开这个选项。

open_keychain.png

同时在你的项目会生成一个entitlements文件。里面会有Access group,值应该是

$(AppIdentifierPrefix)cn.xxx.KeyChainLearn

AppIdentifierPrefix表示发布者的一个身份,这个可以在苹果开发者后台可以找得到。

可以从在Info.plist文件中新增一组key-value

Key: AppIdentifierPrefix

Value: $(AppIdentifierPrefix)

最后创建Keychain Item的时候,需要指定的相应的group

NSString*perfix = [[[NSBundlemainBundle]infoDictionary]objectForKey:@"AppIdentifierPrefix"];NSString*groupString = [NSStringstringWithFormat:@"%@cn.xiaozhi.KeyChainLearn",perfix];[keyChainQueryDictaionary setObject:groupString forKey:(id)kSecAttrAccessGroup];

在其他需要共享数据的应用也是重复以上的操作,不过得保证AppIdentifierPrefix相同。

总结

在保存一些重要的信息的时候,我们可以使用Keychain,但不是绝对安全的,可以把数据加密后再放在Keychain。Keychain还可以通过iCloud备份跟跨设备共享。

在 iOS 中使用 Keychain 存储的密钥生成 OpenSSL 证书签名请求

【中文标题】在 iOS 中使用 Keychain 存储的密钥生成 OpenSSL 证书签名请求【英文标题】:Generating an OpenSSL Certificate Signing Request in iOS with Keychain stored keys 【发布时间】:2012-11-07 12:26:57 【问题描述】:

我正在尝试在 iOS 中生成 CSR。由于显然 Apple 的 iOS 安全框架不包含生成 CSR 的方法,因此我不得不为我的项目编译 OpenSSL 源代码。

现在我想知道如何将这些方法与我之前在 Keychain 中生成的密钥一起使用。也就是说,我需要将 SecKeyRef 类型转换为 EVP_PKEY 等 OpenSSL 类型。这将允许我调用 OpenSSL 方法 X509_REQ_set_pubkey。

有谁知道实现这一目标的方法吗?

【问题讨论】:

相关,请参阅Simple Certificate Enrollment Protocol (SCEP)。 Peter Gutmann 于 2015 年 5 月接管了它的维护工作,因此它应该向前推进。 【参考方案1】:

自己找到了解决方案。

首先,您需要从钥匙串中提取密钥作为 NSData。

- (NSData *) getKeyDataWithIdentifier:(NSString *) identifier

    NSData * keyBits = nil;
    NSMutableDictionary * keyQuery = [[NSMutableDictionary alloc] init];
    NSData * encodedId = [identifier dataUsingEncoding:NSUTF8StringEncoding];
    [keyQuery setObject:encodedId forKey:kSecAttrApplicationTag];
    [keyQuery setObject:kSecClassKey forKey:kSecClass];
    [keyQuery setObject:[NSNumber numberWithBool:YES] forKey:kSecReturnData];
    [keyQuery setObject:kSecAttrKeyTypeRSA forKey:kSecAttrKeyType];

    OSStatus sanityCheck = SecItemCopyMatching((CFDictionaryRef)keyQuery, (CFTypeRef *)&keyBits);

    if (sanityCheck != noErr) 
        NSLog(@"Error: %ld", sanityCheck);
    

    return keyBits;

现在我们需要将此数据转换为 unsigned char 并将其提供给方法 d2i_RSAPublicKey

- (void) generateCSR:(NSData *) keyData

    X509_REQ *req = NULL;
    X509_NAME *name= NULL;
    EVP_PKEY *key;
    const unsigned char * bits = (unsigned char *) [keyData bytes];
    int length = [keyData length];

    if ((req=X509_REQ_new()) == NULL) 
        NSLog(@"big error");
        return;
    

    RSA * rsa = NULL;
    key=EVP_PKEY_new();
    d2i_RSAPublicKey(&rsa, &bits, length);
    EVP_PKEY_assign_RSA(key,rsa);
    name = X509_REQ_get_subject_name(req);
    X509_REQ_set_pubkey(req, key);

    /* This function creates and adds the entry, working out the
     * correct string type and performing checks on its length.
     * Normally we'd check the return value for errors...
             */
    X509_NAME_add_entry_by_txt(name,"CN",
                               MBSTRING_ASC, "My certificate request", -1, -1, 0);
    X509_REQ_print_fp(stdout, req);

这会在 OpenSSL(未签名)中生成一个简单的 CSR,其中包含一个公钥和一个通用名称,并将其打印到标准输出。

【讨论】:

【参考方案2】:

嗯,密钥库的东西不适合我们,所以我们生成了它们并将它们存储为文件。如果有人需要这个,我会把它留在这里。

+ (void)generateCsrAndKeyAtPath:(NSString *)csrPath KeyPath:(NSString *)keyPath Username:(NSString *)username 
    int i;
    RSA *rsakey;
    X509_REQ *req;
    X509_NAME *subj;
    EVP_PKEY *pkey;
    EVP_MD *digest;
    FILE *fp;

    structentry[ENTRIES - 1].value = [username UTF8String];

    // standard set up for OpenSSL
    OpenSSL_add_all_algorithms();
    ERR_load_crypto_strings();

    // Generate the RSA key; we don't assign a callback to monitor progress
    // since generating keys is fast enough these days
    rsakey = RSA_generate_key(2048, RSA_F4, NULL, NULL);

    // Create evp obj to hold our rsakey
    if (!(pkey = EVP_PKEY_new()))
        NSLog(@"Could not create EVP object");

    if (!(EVP_PKEY_set1_RSA(pkey, rsakey)))
        NSLog(@"Could not assign RSA key to EVP object");

    // create request object
    if (!(req = X509_REQ_new()))
        NSLog(@"Failed to create X509_REQ object");
    X509_REQ_set_pubkey(req, pkey);

    // create and fill in subject object
    if (!(subj = X509_NAME_new()))
        NSLog(@"Failed to create X509_NAME object");

    for (i = 0; i < ENTRIES; i++) 
        int nid;                  // ASN numeric identifier
        X509_NAME_ENTRY *ent;

        if ((nid = OBJ_txt2nid(structentry[i].key)) == NID_undef) 
            fprintf(stderr, "Error finding NID for %s\n", structentry[i].key);
            NSLog(@"Error on lookup");
        
        if (!(ent = X509_NAME_ENTRY_create_by_NID(NULL, nid, MBSTRING_ASC,
                structentry[i].value, -1)))
            NSLog(@"Error creating Name fewfwefewf from NID");

        if (X509_NAME_add_entry(subj, ent, -1, 0) != 1)
            NSLog(@"Error adding fewfwefewf to Name");
    
    if (X509_REQ_set_subject_name(req, subj) != 1)
        NSLog(@"Error adding subject to request");

    // request is filled in and contains our generated public key;
    // now sign it
    digest = (EVP_MD *) EVP_sha1();

    if (!(X509_REQ_sign(req, pkey, digest)))
        NSLog(@"Error signing request");

    // write output files
    if (!(fp = fopen([csrPath UTF8String], "wb")))
        NSLog(@"Error writing to request file");
    if (PEM_write_X509_REQ(fp, req) != 1)
        NSLog(@"Error while writing request");
    fclose(fp);

    if (!(fp = fopen([keyPath UTF8String], "w")))
        NSLog(@"Error writing to private key file");
    if (PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, 0, NULL) != 1)
        NSLog(@"Error while writing private key");
    fclose(fp);

    EVP_PKEY_free(pkey);
    X509_REQ_free(req);

【讨论】:

以上是关于iOS Keychain理解的主要内容,如果未能解决你的问题,请参考以下文章

iOS -- keyChain

IOS开发之——keychain使用介绍

iOS keychain报错 25293

iOS的密码管理系统 Keychain的介绍和使用

iOS的密码管理系统 Keychain的介绍和使用

iOS逆向工程之KeyChain与Snoop-it