美文网首页iOS 知识点iOS学习开发iOS Developer
iOS安全-钥匙串服务(iOS Keychain Service

iOS安全-钥匙串服务(iOS Keychain Service

作者: Doris_Lee | 来源:发表于2016-06-23 14:38 被阅读730次

    本文描述iOS中基本的钥匙串访问,内容整理于苹果官方文档。
    本文主要讲述以下内容:

    • 钥匙串中添加一个条目
    • 钥匙串中查找条目
    • 获取钥匙串条目中的属性和数据
    • 改变钥匙串条目中的属性和数据

    注意:在iPhone上,钥匙链的访问权限取决于签名应用程序的描述文件。在应用程序版本中务必要一直使用相同的描述文件。

    向应用程序中添加钥匙串服务

    大多数iOS应用程序使用钥匙串只是向钥匙串中添加一个密码,修改现有的钥匙串条目,或者在需要的时候检索一个密码。钥匙串服务提供了以下方法来完成这些任务:

    • SecItemAdd 向钥匙串中添加一个条目
    • SecItemUpdate 更改钥匙串中已有的条目
    • SecItemCopyMatching 查找钥匙串条目并提取信息

    下图展示了应用程序如何使用这些函数来访问互联网的FTP服务器的流程图。

    使用iPhone Keychain Services 访问网络服务器流程图

    应用程序用户从选择一个文件传输协议(FTP)服务器开始。应用程序调用SecItemCopyMatching,传一个包含确定密钥串条目的属性的字典。如果钥匙串里有密码, 函数将密码返回到应用程序,将它发送到FTP服务器对用户进行身份验证。如果身份验证成功,结束。如果身份验证失败,应用程序显示一个对话框,要求输入户名和密码。
    如果钥匙串里没有相应的密码,SecItemCopyMatching返回errSecItemNotFound结果代码。在这种情况下,应用程序显示一个对话框,要求输入户名和密码。(这个对话框还应该包括一个取消按钮,为了避免流程图变得过于复杂已经省略了)。

    从用户那里拿到密码之后,该应用程序继续到FTP服务器验证用户的身份。身份验证成功时,应用程序可以假设用户输入的信息是有效的。应用程序然后显示另一个对话框询问用户是否保存密码钥匙链。如果用户选择不,那么结束。如果用户选择是,则应用程序调用SecItemAdd函数(如果这是一个新的钥匙串条目)或SecItemUpdate函数(更新现有的钥匙串条目),然后结束。

    以下代码展示了一个可能使用钥匙串服务功能典型的应用程序,为通用项目获取和设置密码。使用同样的方法你可以获取和设置钥匙串条目属性(如用户名或服务名称)。
    以下代码来自苹果官方
    注意引入Security.Framework

    KeychainWrapper.h

    #import <Foundation/Foundation.h>
    #import <Security/Security.h>
    
    @interface KeychainWrapper : NSObject{
    
      NSMutableDictionary        *keychainData;
      NSMutableDictionary        *genericPasswordQuery;
    }
    
    @property (nonatomic, strong) NSMutableDictionary *keychainData;
    @property (nonatomic, strong) NSMutableDictionary *genericPasswordQuery;
    
    - (void)mySetObject:(id)inObject forKey:(id)key;
    - (id)myObjectForKey:(id)key;
    - (void)resetKeychainItem;
    
    @end
    

    KeychainWrapper.m

    //Unique string used to identify the keychain item:
    static const UInt8 kKeychainItemIdentifier[]    = "com.ios.doris";
    
    @implementation KeychainWrapper
    
    
    - (id)init
    {
        if ((self = [super init])) {
        
        OSStatus keychainErr = noErr;
        // Set up the keychain search dictionary:
        genericPasswordQuery = [[NSMutableDictionary alloc] init];
        // This keychain item is a generic password.
        [genericPasswordQuery setObject:(__bridge id)kSecClassGenericPassword
                                 forKey:(__bridge id)kSecClass];
        // The kSecAttrGeneric attribute is used to store a unique string that is used
        // to easily identify and find this keychain item. The string is first
        // converted to an NSData object:
        NSData *keychainItemID = [NSData dataWithBytes:kKeychainItemIdentifier
                                                length:strlen((const char *)kKeychainItemIdentifier)];
        [genericPasswordQuery setObject:keychainItemID forKey:(__bridge id)kSecAttrGeneric];
        // Return the attributes of the first match only:
        [genericPasswordQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
        // Return the attributes of the keychain item (the password is
        //  acquired in the secItemFormatToDictionary: method):
        [genericPasswordQuery setObject:(__bridge id)kCFBooleanTrue
                                 forKey:(__bridge id)kSecReturnAttributes];
        
        //Initialize the dictionary used to hold return data from the keychain:
        CFMutableDictionaryRef outDictionary = nil;
        // If the keychain item exists, return the attributes of the item:
        keychainErr = SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery,
                                          (CFTypeRef *)&outDictionary);
        if (keychainErr == noErr) {
            // Convert the data dictionary into the format used by the view controller:
            self.keychainData = [self secItemFormatToDictionary:(__bridge_transfer NSMutableDictionary *)outDictionary];
        } else if (keychainErr == errSecItemNotFound) {
            // Put default values into the keychain if no matching
            // keychain item is found:
            [self resetKeychainItem];
            if (outDictionary) CFRelease(outDictionary);
        } else {
            // Any other error is unexpected.
            NSAssert(NO, @"Serious error.\n");
            if (outDictionary) CFRelease(outDictionary);
        }
    }
    return self;
    }
    

    存到钥匙串:
    - (void)mySetObject:(id)inObject forKey:(id)key
    {
    if (inObject == nil) return;
    id currentObject = [keychainData objectForKey:key];
    if (![currentObject isEqual:inObject])
    {
    [keychainData setObject:inObject forKey:key];
    [self writeToKeychain];
    }
    }

    从钥匙串中取:
    - (id)myObjectForKey:(id)key
    {
    return [keychainData objectForKey:key];
    }

    // Reset the values in the keychain item, or create a new item if it
    // doesn't already exist:
    

    重置钥匙串中的数据,或者不存在是创建相应条目:
    - (void)resetKeychainItem
    {
    if (!keychainData) //Allocate the keychainData dictionary if it doesn't exist yet.
    {
    self.keychainData = [[NSMutableDictionary alloc] init];
    }
    else if (keychainData)
    {
    // Format the data in the keychainData dictionary into the format needed for a query
    // and put it into tmpDictionary:
    NSMutableDictionary *tmpDictionary =
    [self dictionaryToSecItemFormat:keychainData];
    // Delete the keychain item in preparation for resetting the values:
    OSStatus errorcode = SecItemDelete((__bridge CFDictionaryRef)tmpDictionary);
    NSAssert(errorcode == noErr, @"Problem deleting current keychain item." );
    }

    // Default generic data for Keychain Item:
    [keychainData setObject:@"" forKey:(__bridge id)kSecAttrLabel];
    [keychainData setObject:@"" forKey:(__bridge id)kSecAttrDescription];
    [keychainData setObject:@"" forKey:(__bridge id)kSecAttrAccount];
    [keychainData setObject:@"" forKey:(__bridge id)kSecAttrService];
    [keychainData setObject:@"" forKey:(__bridge id)kSecAttrComment];
    [keychainData setObject:@"" forKey:(__bridge id)kSecValueData];
    }
    
    // Implement the dictionaryToSecItemFormat: method, which takes the attributes that
    // you want to add to the keychain item and sets up a dictionary in the format
    // needed by Keychain Services:
    - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert
    {
    // This method must be called with a properly populated dictionary
    // containing all the right key/value pairs for a keychain item search.
    
    // Create the return dictionary:
    NSMutableDictionary *returnDictionary =
    [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert];
    
    // Add the keychain item class and the generic attribute:
    NSData *keychainItemID = [NSData dataWithBytes:kKeychainItemIdentifier
                                            length:strlen((const char *)kKeychainItemIdentifier)];
    [returnDictionary setObject:keychainItemID forKey:(__bridge id)kSecAttrGeneric];
    [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    
    // Convert the password NSString to NSData to fit the API paradigm:
    NSString *passwordString = [dictionaryToConvert objectForKey:(__bridge id)kSecValueData];
    [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding]
                         forKey:(__bridge id)kSecValueData];
    return returnDictionary;
    }
    

    从钥匙串中取出数据转为字典
    - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert
    {
    // This method must be called with a properly populated dictionary
    // containing all the right key/value pairs for the keychain item.

    // Create a return dictionary populated with the attributes:
    NSMutableDictionary *returnDictionary = [NSMutableDictionary
                                             dictionaryWithDictionary:dictionaryToConvert];
    
    // To acquire the password data from the keychain item,
    // first add the search key and class attribute required to obtain the password:
    [returnDictionary setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    // Then call Keychain Services to get the password:
    CFDataRef passwordData = NULL;
    OSStatus keychainError = noErr; //
    keychainError = SecItemCopyMatching((__bridge CFDictionaryRef)returnDictionary,
                                        (CFTypeRef *)&passwordData);
    if (keychainError == noErr)
    {
        // Remove the kSecReturnData key; we don't need it anymore:
        [returnDictionary removeObjectForKey:(__bridge id)kSecReturnData];
        
        // Convert the password to an NSString and add it to the return dictionary:
        NSString *password = [[NSString alloc] initWithBytes:[(__bridge_transfer NSData *)passwordData bytes]
                                                      length:[(__bridge NSData *)passwordData length] encoding:NSUTF8StringEncoding];
        [returnDictionary setObject:password forKey:(__bridge id)kSecValueData];
    }
    // Don't do anything if nothing is found.
    else if (keychainError == errSecItemNotFound) {
        NSAssert(NO, @"Nothing was found in the keychain.\n");
        if (passwordData) CFRelease(passwordData);
    }
    // Any other error is unexpected.
    else
    {
        NSAssert(NO, @"Serious error.\n");
        if (passwordData) CFRelease(passwordData);
    }
    
    return returnDictionary;
    }
    

    写到钥匙串的具体实现:

    - (void)writeToKeychain
    {
    CFDictionaryRef attributes = nil;
    NSMutableDictionary *updateItem = nil;
    
    // If the keychain item already exists, modify it:
    if (SecItemCopyMatching((__bridge CFDictionaryRef)genericPasswordQuery,
                            (CFTypeRef *)&attributes) == noErr)
    {
        // First, get the attributes returned from the keychain and add them to the
        // dictionary that controls the update:
        updateItem = [NSMutableDictionary dictionaryWithDictionary:(__bridge_transfer NSDictionary *)attributes];
        
        // Second, get the class value from the generic password query dictionary and
        // add it to the updateItem dictionary:
        [updateItem setObject:[genericPasswordQuery objectForKey:(__bridge id)kSecClass]
                       forKey:(__bridge id)kSecClass];
        
        // Finally, set up the dictionary that contains new values for the attributes:
        NSMutableDictionary *tempCheck = [self dictionaryToSecItemFormat:keychainData];
        //Remove the class--it's not a keychain attribute:
        [tempCheck removeObjectForKey:(__bridge id)kSecClass];
        
        // You can update only a single keychain item at a time.
        OSStatus errorcode = SecItemUpdate(
                                           (__bridge CFDictionaryRef)updateItem,
                                           (__bridge CFDictionaryRef)tempCheck);
        NSAssert(errorcode == noErr, @"Couldn't update the Keychain Item." );
    } else {
        // No previous item found; add the new item.
        // The new value was added to the keychainData dictionary in the mySetObject routine,
        // and the other values were added to the keychainData dictionary previously.
        // No pointer to the newly-added items is needed, so pass NULL for the second parameter:
        OSStatus errorcode = SecItemAdd(
                                        (__bridge CFDictionaryRef)[self dictionaryToSecItemFormat:keychainData],
                                        NULL);
        NSAssert(errorcode == noErr, @"Couldn't add the Keychain Item." );
        if (attributes) CFRelease(attributes);
    }
    }
    
    @end
    

    在这个示例中,通用属性是用来创建一个独一无二的字符串,可以用来轻松识别钥匙串条目。你也可以使用标准的属性,如服务名称和用户名。

    运行调试:

    详细代码和示例程序:https://github.com/lilufeng/KeychainDemo

    相关文章

      网友评论

      • plantseeds:[wrapper mySetObject:_userName.text forKey:(id)kSecAttrAccount]; 这些key 必须用apple提供的那些吗?像kSecAttrAccount、kSecValueData, 能不能自定义
        Doris_Lee:@lgpursuing 是的,应该是不能自定义,你可以自己测试一下,我没有深入研究 :pensive:

      本文标题:iOS安全-钥匙串服务(iOS Keychain Service

      本文链接:https://www.haomeiwen.com/subject/yvindttx.html