将NSDictionary存储在钥匙串中

使用KeychainItemWrapper (或不使用)可以将一个NSDictionary存储在iPhone钥匙KeychainItemWrapper ? 如果这是不可能的,你有另一种解决scheme吗?

您必须在将NSDictionary存储到Keychain之前正确序列化NSDictionary 。 使用:

 [dic description] [dic propertyList] 

你将最终得到一个只有NSString对象的NSDictionary集合。 如果要维护对象的数据types,可以使用NSPropertyListSerialization

 KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:@"arbitraryId" accessGroup:nil] NSString *error; //The following NSData object may be stored in the Keychain NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:dictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; [keychain setObject:dictionaryRep forKey:kSecValueData]; //When the NSData object object is retrieved from the Keychain, you convert it back to NSDictionary type dictionaryRep = [keychain objectForKey:kSecValueData]; NSDictionary *dictionary = [NSPropertyListSerialization propertyListFromData:dictionaryRep mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if (error) { NSLog(@"%@", error); } 

第二次调用NSPropertyListSerialization返回的NSDictionary将维护NSDictionary集合中的原始数据types。

使用KeychainItemWrapper依赖关系需要修改库/示例代码来接受NSData作为encryption的有效载荷,这是未来的certificate。 此外,做NSDictionary > NSData > NSString转换序列,以便您可以使用KeychainItemWrapper效率低下: KeychainItemWrapper将您的string转换回NSData无论如何,encryption它。

这是一个完整的解决scheme,直接利用钥匙串库来解决上述问题。 它被实现为一个类别,所以你使用它是这样的:

 // to store your dictionary [myDict storeToKeychainWithKey:@"myStorageKey"]; // to retrieve it NSDictionary *myDict = [NSDictionary dictionaryFromKeychainWithKey:@"myStorageKey"]; // to delete it [myDict deleteFromKeychainWithKey:@"myStorageKey"]; 

这里是类别:

 @implementation NSDictionary (Keychain) -(void) storeToKeychainWithKey:(NSString *)aKey { // serialize dict NSString *error; NSData *serializedDictionary = [NSPropertyListSerialization dataFromPropertyList:self format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; // encrypt in keychain if(!error) { // first, delete potential existing entries with this key (it won't auto update) [self deleteFromKeychainWithKey:aKey]; // setup keychain storage properties NSDictionary *storageQuery = @{ (id)kSecAttrAccount: aKey, (id)kSecValueData: serializedDictionary, (id)kSecClass: (id)kSecClassGenericPassword, (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked }; OSStatus osStatus = SecItemAdd((CFDictionaryRef)storageQuery, nil); if(osStatus != noErr) { // do someting with error } } } +(NSDictionary *) dictionaryFromKeychainWithKey:(NSString *)aKey { // setup keychain query properties NSDictionary *readQuery = @{ (id)kSecAttrAccount: aKey, (id)kSecReturnData: (id)kCFBooleanTrue, (id)kSecClass: (id)kSecClassGenericPassword }; NSData *serializedDictionary = nil; OSStatus osStatus = SecItemCopyMatching((CFDictionaryRef)readQuery, (CFTypeRef *)&serializedDictionary); if(osStatus == noErr) { // deserialize dictionary NSString *error; NSDictionary *storedDictionary = [NSPropertyListSerialization propertyListFromData:serializedDictionary mutabilityOption:NSPropertyListImmutable format:nil errorDescription:&error]; if(error) { NSLog(@"%@", error); } return storedDictionary; } else { // do something with error return nil; } } -(void) deleteFromKeychainWithKey:(NSString *)aKey { // setup keychain query properties NSDictionary *deletableItemsQuery = @{ (id)kSecAttrAccount: aKey, (id)kSecClass: (id)kSecClassGenericPassword, (id)kSecMatchLimit: (id)kSecMatchLimitAll, (id)kSecReturnAttributes: (id)kCFBooleanTrue }; NSArray *itemList = nil; OSStatus osStatus = SecItemCopyMatching((CFDictionaryRef)deletableItemsQuery, (CFTypeRef *)&itemList); // each item in the array is a dictionary for (NSDictionary *item in itemList) { NSMutableDictionary *deleteQuery = [item mutableCopy]; [deleteQuery setValue:(id)kSecClassGenericPassword forKey:(id)kSecClass]; // do delete osStatus = SecItemDelete((CFDictionaryRef)deleteQuery); if(osStatus != noErr) { // do something with error } [deleteQuery release]; } } @end 

事实上,你可以很容易地修改它来存储钥匙串中的任何一种可序列化的对象,而不仅仅是一个字典。 只要做一个你想存储的对象的NSData表示。

对Dts类别做了一些小的改动。 转换为ARC并使用NSKeyedArchiver存储自定义对象。

 @implementation NSDictionary (Keychain) -(void) storeToKeychainWithKey:(NSString *)aKey { // serialize dict NSData *serializedDictionary = [NSKeyedArchiver archivedDataWithRootObject:self]; // encrypt in keychain // first, delete potential existing entries with this key (it won't auto update) [self deleteFromKeychainWithKey:aKey]; // setup keychain storage properties NSDictionary *storageQuery = @{ (__bridge id)kSecAttrAccount: aKey, (__bridge id)kSecValueData: serializedDictionary, (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked }; OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef)storageQuery, nil); if(osStatus != noErr) { // do someting with error } } +(NSDictionary *) dictionaryFromKeychainWithKey:(NSString *)aKey { // setup keychain query properties NSDictionary *readQuery = @{ (__bridge id)kSecAttrAccount: aKey, (__bridge id)kSecReturnData: (id)kCFBooleanTrue, (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword }; CFDataRef serializedDictionary = NULL; OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef)readQuery, (CFTypeRef *)&serializedDictionary); if(osStatus == noErr) { // deserialize dictionary NSData *data = (__bridge NSData *)serializedDictionary; NSDictionary *storedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data]; return storedDictionary; } else { // do something with error return nil; } } -(void) deleteFromKeychainWithKey:(NSString *)aKey { // setup keychain query properties NSDictionary *deletableItemsQuery = @{ (__bridge id)kSecAttrAccount: aKey, (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll, (__bridge id)kSecReturnAttributes: (id)kCFBooleanTrue }; CFArrayRef itemList = nil; OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef)deletableItemsQuery, (CFTypeRef *)&itemList); // each item in the array is a dictionary NSArray *itemListArray = (__bridge NSArray *)itemList; for (NSDictionary *item in itemListArray) { NSMutableDictionary *deleteQuery = [item mutableCopy]; [deleteQuery setValue:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; // do delete osStatus = SecItemDelete((__bridge CFDictionaryRef)deleteQuery); if(osStatus != noErr) { // do something with error } } } @end 

编码: [dic description]
解码: [dic propertyList]

你可以存储任何东西,你只需要序列化它。

 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary]; 

您应该能够将这些数据存储在钥匙串中。

我发现钥匙串包装只需要string。 甚至没有NSData。 所以要存储一个字典,你必须按照Bret的build议来做,但是需要额外的步骤来将NSData序列化转换为一个string。 喜欢这个:

 NSString *error; KeychainItemWrapper *keychain = [[KeychainItemWrapper alloc] initWithIdentifier:MY_STRING accessGroup:nil]; NSData *dictionaryRep = [NSPropertyListSerialization dataFromPropertyList:dictToSave format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; NSString *xml = [[NSString alloc] initWithBytes:[dictionaryRep bytes] length:[dictionaryRep length] encoding:NSUTF8StringEncoding]; [keychain setObject:xml forKey:(__bridge id)(kSecValueData)]; 

读回来:

 NSError *error; NSString *xml = [keychain objectForKey:(__bridge id)(kSecValueData)]; if (xml && xml.length) { NSData *dictionaryRep = [xml dataUsingEncoding:NSUTF8StringEncoding]; dict = [NSPropertyListSerialization propertyListWithData:dictionaryRep options:NSPropertyListImmutable format:nil error:&error]; if (error) { NSLog(@"%@", error); } } 

我为Amols解决scheme添加了访问组支持和模拟器安全性:

 // // NSDictionary+SharedKeyChain.h // LHSharedKeyChain // #import <Foundation/Foundation.h> @interface NSDictionary (SharedKeyChain) /** * Returns a previously stored dictionary from the KeyChain. * * @param key NSString The name of the dictionary. There can be multiple dictionaries stored in the KeyChain. * @param accessGroup NSString Access group for shared KeyChains, set to nil for no group. * * @return NSDictionary A dictionary that has been stored in the Keychain, nil if no dictionary for the key and accessGroup exist. */ + (NSDictionary *)dictionaryFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup; /** * Deletes a previously stored dictionary from the KeyChain. * * @param key NSString The name of the dictionary. There can be multiple dictionaries stored in the KeyChain. * @param accessGroup NSString Access group for shared KeyChains, set to nil for no group. */ + (void)deleteFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup; /** * Save dictionary instance to the KeyChain. Any previously existing data with the same key and accessGroup will be overwritten. * * @param key NSString The name of the dictionary. There can be multiple dictionaries stored in the KeyChain. * @param accessGroup NSString Access group for shared KeyChains, set to nil for no group. */ - (void)storeToKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup; @end 

 // // NSDictionary+SharedKeyChain.m // LHSharedKeyChain // #import "NSDictionary+SharedKeyChain.h" @implementation NSDictionary (SharedKeyChain) - (void)storeToKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup; { // serialize dict NSData *serializedDictionary = [NSKeyedArchiver archivedDataWithRootObject:self]; // encrypt in keychain // first, delete potential existing entries with this key (it won't auto update) [NSDictionary deleteFromKeychainWithKey:key accessGroup:accessGroup]; // setup keychain storage properties NSDictionary *storageQuery = @{ (__bridge id)kSecAttrAccount: key, #if TARGET_IPHONE_SIMULATOR // Ignore the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). #else (__bridge id)kSecAttrAccessGroup: accessGroup, #endif (__bridge id)kSecValueData: serializedDictionary, (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked }; OSStatus status = SecItemAdd ((__bridge CFDictionaryRef)storageQuery, nil); if (status != noErr) { NSLog (@"%d %@", (int)status, @"Couldn't save to Keychain."); } } + (NSDictionary *)dictionaryFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup; { // setup keychain query properties NSDictionary *readQuery = @{ (__bridge id)kSecAttrAccount: key, #if TARGET_IPHONE_SIMULATOR // Ignore the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). #else (__bridge id)kSecAttrAccessGroup: accessGroup, #endif (__bridge id)kSecReturnData: (id)kCFBooleanTrue, (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword }; CFDataRef serializedDictionary = NULL; OSStatus status = SecItemCopyMatching ((__bridge CFDictionaryRef)readQuery, (CFTypeRef *)&serializedDictionary); if (status == noErr) { // deserialize dictionary NSData *data = (__bridge NSData *)serializedDictionary; NSDictionary *storedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data]; return storedDictionary; } else { NSLog (@"%d %@", (int)status, @"Couldn't read from Keychain."); return nil; } } + (void)deleteFromKeychainWithKey:(NSString *)key accessGroup:(NSString *)accessGroup; { // setup keychain query properties NSDictionary *deletableItemsQuery = @{ (__bridge id)kSecAttrAccount: key, #if TARGET_IPHONE_SIMULATOR // Ignore the access group if running on the iPhone simulator. // // Apps that are built for the simulator aren't signed, so there's no keychain access group // for the simulator to check. This means that all apps can see all keychain items when run // on the simulator. // // If a SecItem contains an access group attribute, SecItemAdd and SecItemUpdate on the // simulator will return -25243 (errSecNoAccessForItem). #else (__bridge id)kSecAttrAccessGroup: accessGroup, #endif (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll, (__bridge id)kSecReturnAttributes: (id)kCFBooleanTrue }; CFArrayRef itemList = nil; OSStatus status = SecItemCopyMatching ((__bridge CFDictionaryRef)deletableItemsQuery, (CFTypeRef *)&itemList); // each item in the array is a dictionary NSArray *itemListArray = (__bridge NSArray *)itemList; for (NSDictionary *item in itemListArray) { NSMutableDictionary *deleteQuery = [item mutableCopy]; [deleteQuery setValue:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass]; // do delete status = SecItemDelete ((__bridge CFDictionaryRef)deleteQuery); if (status != noErr) { NSLog (@"%d %@", (int)status, @"Couldn't delete from Keychain."); } } } @end