iOS Keychain编程指南

Keychain(钥匙串)服务增、删、改、查

Posted by Ted on October 19, 2016

官方文档地址Keychain Services Programming Guide

一、关于Keychain

Keychain服务为一个或多个用户提供密码,钥匙,证书和笔记的安全存储。 用户可以用一个密码来解锁Keychain,然后任何Keychain服务感知的应用程序都可以使用该Keychain来存储和检索密码。 本指南包含了Keychain服务的概述,讨论了开发者最常使用的功能和数据结构,并提供了如何在您自己的应用程序中使用Keychain服务的示例。

二、iOS Keychain服务的目标

  • Add an item to a keychain
  • Find an item in a keychain
  • Get the attributes and data in a keychain item
  • Change the attributes and data in a keychain item
  • 将项目添加到钥匙串
  • 在钥匙串中找到一个项目
  • 获取钥匙串项目中的属性和数据
  • 更改钥匙串项目中的属性和数据

注意:在iOS中,Keychain权限取决于用于签署应用程序的供应配置文件。 确保在不同版本的应用程序中始终使用相同的配置文件。

三、在APP中使用Keychain

钥匙串项目可以具有几个类型之一。 网络密码用于通过网络访问的服务器和网站,普通密码用于任何其他受密码保护的服务(如数据库或调度应用程序)。 同时,用于建立信任的证书,密钥和身份也可以存储在钥匙串中。 但是,对于所有这些项目类别,您使用相同的一组函数来添加,修改和检索钥匙串项目:

  • SecItemAdd 将项目添加到钥匙串
  • SecItemUpdate 修改现有的钥匙串项目。
  • SecItemCopyMatching 找到一个keychain项目并从中提取信息。

下表:使用iOS钥匙串服务访问Internet服务器

img

App的用户首先选择文件传输协议(FTP)服务器。App调用SecItemCopyMatching,向其传递包含标识钥匙串项目的属性的字典。

如果密码在keychain上,则该函数将密码返回给App,App将其发送到FTP服务器以对用户进行身份验证。如果认证成功,则例程结束。如果认证失败,App将显示一个对话框来请求用户名和密码。

如果密码不在keychain上,则SecItemCopyMatching返回errSecItemNotFound结果代码。在这种情况下,App显示一个对话框来请求用户名和密码。(该对话框还应该包含一个“取消”按钮,但是该选择从图中省略,以防止流程图变得过于复杂。)

从用户获得密码后,App继续对FTP服务器进行用户身份验证。当认证成功时,应用程序可以认为用户输入的信息是有效的。然后应用程序显示另一个对话框,询问用户是否将密码保存在钥匙串上。如果用户选择否,则例程结束。如果用户选择是,那么应用程序在结束例程之前调用SecItemAdd函数(如果这是一个新的Keychain项目)或SecItemUpdate函数(更新现有的钥匙串项目)。

四、KeyChain实战

首先要在target里设置keychain

img

Xcode会在project里生成一个配置文件

img

.h文件

#import <Foundation/Foundation.h>

@interface HTKeychainWrapper : NSObject

+ (void)ht_SetValue:(id)value forKey:(NSString *)key;
+ (id)ht_valueForKey:(NSString *)key;
+ (void)ht_deleteValueForKey:(NSString *)key;

@end

.m文件

#import "HTKeychainWrapper.h"

@implementation HTKeychainWrapper

+ (void)ht_SetValue:(id)value forKey:(NSString *)key{
    NSMutableDictionary *keychainItem = [self getKeychainQueryItem:key];
    SecItemDelete((CFDictionaryRef)keychainItem);
    [keychainItem setObject:[NSKeyedArchiver archivedDataWithRootObject:value] forKey:(id)kSecValueData];
    SecItemAdd((CFDictionaryRef)keychainItem, NULL);
}

+ (NSMutableDictionary *)getKeychainQueryItem:(NSString *)key{
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassGenericPassword,(id)kSecClass,
            key, (id)kSecAttrService,
            key, (id)kSecAttrAccount,
            (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
            nil];
}

+ (id)ht_valueForKey:(NSString *)key{
    id ret = nil;
    NSMutableDictionary *keychainItem = [self getKeychainQueryItem:key];
    [keychainItem setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    [keychainItem setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keychainItem, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", key, e);
        } @finally {
        }
    }
    if (keyData)
        CFRelease(keyData);
    return ret;
}

+(void)ht_deleteValueForKey:(NSString *)key{
    NSMutableDictionary *keychainItem = [self getKeychainQueryItem:key];
    SecItemDelete((CFDictionaryRef)keychainItem);
}

@end