keychain service钥匙串服务
来源:互联网 发布:阿里云过户域名流程 编辑:程序博客网 时间:2024/04/30 09:42
keychain service钥匙串服务是iOS提供的用于管理用户密码、密钥、证书、标识的服务,它相当于一个加密容器,app可以把相关的用户信息以钥匙条目的形式存储到其中,钥匙串服务会将所有存储到其中的条目进行加密,并保护起来,只允许创建这个条目的app访问它。
app要把用户信息(钥匙)添加到钥匙串服务(钥匙串)中,在必要时从钥匙串服务中取出用户信息,这些动作需要用到Keychain Services API。Keychain Services API的相关属性和方法声明在< Security/security.h >中。
一、操作钥匙条目的三个方法:
1、添加钥匙条目到钥匙串
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
2、修改钥匙条目
OSStatus SecItemUpdate(CFDictionaryRef query,CFDictionaryRef attributesToUpdate)
3、提取钥匙信息
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
二、添加钥匙条目到钥匙串
添加钥匙条目到钥匙串可以调用SecItemAdd方法,但前提是要先要将用户信息封装成钥匙条目secItem,才可以加入到钥匙串。钥匙条目实际上就是一个钥匙属性字典,将需要存储的用户信息加到这个字典中,再插入一些与钥匙属性相关的键值对,就形成了一个钥匙条目,然后就可以加入到钥匙串里了。这个字典的特殊在于,它有许多预定好的key,用户信息,以及钥匙条目的属性信息,加到该字典(调用setValue:forKey:)指定的key中。
下面来了解钥匙条目字典中都有哪些key。
1、与用户信息相关的key
kSecValueData ,这个key对应CFDataRef类型的value,把要存储的用户信息放到这里,这里的内容如果是秘钥或密码,钥匙串服务就会对它们进行加密存储。
2、与钥匙条目的属性相关的key
根据存储的用户信息的不同,钥匙串服务把钥匙条目分成了几类:普通密码(kSecClassGenericPassword)条目、互联网密码(kSecClassInternetPassword)条目、密钥(kSecClassKey)条目、证书(kSecClassCertificate)条目和标识(kSecClassIdentity)条目。不同类型的钥匙条目有各自的属性key。
(1)普通密码钥匙条目的key有:
kSecAttrAccessible kSecAttrAccessControl kSecAttrAccessGroup kSecAttrCreationDate kSecAttrModificationDate kSecAttrDescription kSecAttrComment kSecAttrCreator kSecAttrType kSecAttrLabel kSecAttrIsInvisible kSecAttrIsNegative kSecAttrAccount kSecAttrService kSecAttrGeneric kSecAttrSynchronizable
(2)网络密码钥匙条目的key有:
kSecAttrAccessible kSecAttrAccessControl kSecAttrAccessGroup kSecAttrCreationDate kSecAttrModificationDate kSecAttrDescription kSecAttrComment kSecAttrCreator kSecAttrType kSecAttrLabel kSecAttrIsInvisible kSecAttrIsNegative kSecAttrAccount kSecAttrSecurityDomain kSecAttrServer kSecAttrProtocol kSecAttrAuthenticationType kSecAttrPort kSecAttrPath kSecAttrSynchronizable
(3)证书钥匙条目的key有:
kSecAttrAccessible kSecAttrAccessControl kSecAttrAccessGroup kSecAttrCertificateType kSecAttrCertificateEncoding kSecAttrLabel kSecAttrSubject kSecAttrIssuer kSecAttrSerialNumber kSecAttrSubjectKeyID kSecAttrPublicKeyHash kSecAttrSynchronizable
(4)秘钥条目的key有:
kSecAttrAccessible kSecAttrAccessControl kSecAttrAccessGroup kSecAttrKeyClass kSecAttrLabel kSecAttrApplicationLabel kSecAttrIsPermanent kSecAttrApplicationTag kSecAttrKeyType kSecAttrKeySizeInBits kSecAttrEffectiveKeySize kSecAttrCanEncrypt kSecAttrCanDecrypt kSecAttrCanDerive kSecAttrCanSign kSecAttrCanVerify kSecAttrCanWrap kSecAttrCanUnwrap kSecAttrSynchronizable
(5)标识钥匙条目的key有:
钥匙串服务中所说的标识identity就是证书(公钥)和秘钥(私钥)的结合。因此标识钥匙条目的属性字典中的key是证书钥匙条目的key加上密钥钥匙条目的key。
(6)部分key的意义,及其对应value的类型
三、从钥匙串中查询钥匙条目
要从钥匙串中查询钥匙条目,需要调用这个方法SecItemCopyMatching
方法,查询的本质其实是匹配,也就说需要先提供有关钥匙条目的相关信息,拿着这些信息到钥匙串中进行匹配,如果能找到有匹配的,就相当于查询到了钥匙条目,然后将它取出。
1、提供有关钥匙条目的相关信息
有关要查询的钥匙条目的信息,是以一个字典的形式提供给SecItemCopyMatching
方法的(第一个参数)。因此要查询钥匙条目,第一件事是新建一个字典(姑且称作查询字典),并插入有关钥匙条目的信息。该字典也是有许多预定义的key的,有些key对应的value是必须要设置的。
查询字典的预定义key有许多,一个查询字典通常包括以下key:
(1)若干钥匙条目的属性key
也就是上面说到的,创建一个钥匙条目时可以用的各种key。
(2)若干与匹配条件相关的key
如果给这种key设置了value就相当于多了一条匹配条件,钥匙条目字典的某个键值对要符合这个条件才匹配。不设置则不生效。
(3)若干与查询结果类型相关的key
2、获取查询结果
提供了查询字典,接下来就是获取查询结果,需要有一个CFTypeRef类型变量来承接查询结果(传入SecItemCopyMatching方法的第二个参数)。
在结果字典中,可以通过指定key来获取到有关钥匙条目的信息
三、实际应用
使用钥匙串服务的一般思路是,在app请求登录的时候,先到钥匙串中找相应的钥匙,如果找到,则登录,如果找不到,则提示用户输入登录信息,如果登录成功,则询问用户是否将保存登录信息,如果同意,则新建钥匙添加到钥匙串。
要在app中使用钥匙串服务,最好就是把钥匙条目的新建、查询、更新、删除等逻辑封装成类,如何实现呢?
官网上有一个利用Keychain Services API的例子,把钥匙条目的新建、查询、更新、删除等逻辑封装成了一个类KeychainWrapper。其实我们可以直接使用这个类就不用自己写了,但是还是有必要了解一下具体实现思路,才能够在需要时修改这个类以适应自己的app。
这个类管理定义了一个字典属性keychainData来存储钥匙条目的信息,提供了类的初始化方法,以及三个操作该钥匙条目的方法:
1、该类的初始化方法
-(instanceType)init;
该方法的实现思路是,新建一个查询字典genericPasswordQuery,配置相关匹配条件,将然后到钥匙串中查找是否有匹配结果,若有则将匹配到的钥匙条目信息存储到keychainData中;若没有就调用重置钥匙条目的方法(下面说),初始化keychainData中的钥匙条目信息。
2、往这个钥匙条目字典中插入键值对
- (void)mySetObject:(id)inObject forKey:(id)key;
这个方法的实现思路是,将新的键值对插入keychainData中。先查找钥匙串中是否存在匹配genericPasswordQuery的钥匙条目,如果存在,则取出,插入新的键值对,并更新钥匙条目;如果不存在,则用keychainData创建钥匙条目。
3、从重置钥匙条目
- (void)resetKeychainItem;
这个方法的实现思路是,先判断keychainData是否为空,如果为空就初始化keychainData,并插入默认的键值对;如果不为空,就去钥匙串里查找匹配keychainData的钥匙条目,删除该钥匙条目,再次向keychainData插入默认的键值对。
4、提取钥匙条目中的信息
- (id)myObjectForKey:(id)key;
这个方法的实现是直接通过key访问keychainData中的钥匙条目信息。
最后附上这个类:
KeychainWrapper.h
#import <Foundation/Foundation.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//Unique string used to identify the keychain item:static const UInt8 kKeychainItemIdentifier[] = "com.apple.dts.KeychainUI\0";@interface KeychainWrapper (PrivateMethods)//The following two methods translate dictionaries between the format used by// the view controller (NSString *) and the Keychain Services API:- (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert;- (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToConvert;// Method used to write data to the keychain:- (void)writeToKeychain;@end
KeychainWrapper.m
#import "KeychainWrapper.h"@implementation KeychainWrapper//Synthesize the getter and setter:@synthesize keychainData, genericPasswordQuery;- (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;}// Implement the mySetObject:forKey method, which writes attributes to the keychain:- (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]; }}// Implement the myObjectForKey: method, which reads an attribute value from a dictionary:- (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:@"Item label" forKey:(__bridge id)kSecAttrLabel]; [keychainData setObject:@"Item description" forKey:(__bridge id)kSecAttrDescription]; [keychainData setObject:@"Account" forKey:(__bridge id)kSecAttrAccount]; [keychainData setObject:@"Service" forKey:(__bridge id)kSecAttrService]; [keychainData setObject:@"Your comment here." forKey:(__bridge id)kSecAttrComment]; [keychainData setObject:@"ssssssss" 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;}// Implement the secItemFormatToDictionary: method, which takes the attribute dictionary// obtained from the keychain item, acquires the password from the keychain, and// adds it to the attribute dictionary:- (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;}// Implement the writeToKeychain method, which is called by the mySetObject routine,// which in turn is called by the UI when there is new data for the keychain. This// method modifies an existing keychain item, or--if the item does not already// exist--creates a new keychain item with the new attribute value plus// default values for the other attributes.- (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
- keychain service钥匙串服务
- iOS 钥匙串KeyChain
- 钥匙串(Keychain)服务编程指南-iOS部分
- iOS钥匙串Keychain浅析
- 钥匙串KeyChain的使用
- icloud无法注销,icloud服务中keyChain钥匙串无法关闭问题的解决。
- Firemonkey访问iOS的钥匙串Keychain
- iOS Keychain (钥匙串)简单封装
- ios - 钥匙串开发(keychain开发)
- 钥匙串 keyChain 存储账号密码
- iOS钥匙串KeyChain相关参数的说明
- iOS keychain 钥匙串访问 错误代码 code =-34018 解决方法
- keychain(钥匙串,设备唯一标示获取)
- IOS 用keychain(钥匙串)保存用户名和密码
- IOS 用keychain(钥匙串)保存用户名和密码
- 用keychain(钥匙串)保存用户名和密码
- iOS钥匙串KeyChain相关参数的说明
- 如何将UUID保存在钥匙串(keyChain中)
- git
- Problem-F
- Activity生命周期详细分析
- ListView相关
- 在ios要实现不定参数的函数
- keychain service钥匙串服务
- android studio 解决External file changes sync
- GridView的用法
- Mac Linux Maven环境变量设置
- CSRF攻击及防御措施
- 字符串中的空格去除问题,左空格,右空格,左右空格
- high memory 映射 ----2
- C# winform中使窗体最小化(NotifyIcon)
- 《Apache Cocoon2.2 学习》前言