官方文档地址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服务器
App的用户首先选择文件传输协议(FTP)服务器。App调用SecItemCopyMatching
,向其传递包含标识钥匙串项目的属性的字典。
如果密码在keychain上,则该函数将密码返回给App,App将其发送到FTP服务器以对用户进行身份验证。如果认证成功,则例程结束。如果认证失败,App将显示一个对话框来请求用户名和密码。
如果密码不在keychain上,则SecItemCopyMatching
返回errSecItemNotFound
结果代码。在这种情况下,App显示一个对话框来请求用户名和密码。(该对话框还应该包含一个“取消”按钮,但是该选择从图中省略,以防止流程图变得过于复杂。)
从用户获得密码后,App继续对FTP服务器进行用户身份验证。当认证成功时,应用程序可以认为用户输入的信息是有效的。然后应用程序显示另一个对话框,询问用户是否将密码保存在钥匙串上。如果用户选择否,则例程结束。如果用户选择是,那么应用程序在结束例程之前调用SecItemAdd函数(如果这是一个新的Keychain项目)或SecItemUpdate
函数(更新现有的钥匙串项目)。
四、KeyChain实战
首先要在target里设置keychain
Xcode会在project里生成一个配置文件
.h文件
代码语言:javascript复制#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文件
代码语言:javascript复制#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