Store NSDictionary in keychain
Solution 1
Encoding : [dic description]
Decoding : [dic propertyList]
Solution 2
You must properly serialize the NSDictionary
before storing it into the Keychain.
Using:
[dic description]
[dic propertyList]
you will end up with a NSDictionary
collection of only NSString
objects. If you want to maintain the data types of the objects, you can use 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);
}
The NSDictionary
returned by the second call to NSPropertyListSerialization
will maintain original data types within the NSDictionary
collection.
Solution 3
Using the KeychainItemWrapper
dependency requires modifying the library/sample code to accept NSData
as the encrypted payload, which is not future proof. Also, doing the NSDictionary > NSData > NSString
conversion sequence just so that you can use KeychainItemWrapper
is inefficient: KeychainItemWrapper
will convert your string back to NSData
anyway, to encrypt it.
Here's a complete solution that solves the above by utilizing the keychain library directly. It is implemented as a category so you use it like this:
// to store your dictionary
[myDict storeToKeychainWithKey:@"myStorageKey"];
// to retrieve it
NSDictionary *myDict = [NSDictionary dictionaryFromKeychainWithKey:@"myStorageKey"];
// to delete it
[myDict deleteFromKeychainWithKey:@"myStorageKey"];
and here's the Category:
@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
In fact, you can modify it easily to store any kind of serializable object in the keychain, not just a dictionary. Just make an NSData
representation of the object you want to store.
Solution 4
Made few minor changes to Dts category. Converted to ARC and using NSKeyedArchiver to store custom objects.
@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
Solution 5
You can store anything, you just need to serialize it.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary];
You should be able to store that data in the keychain.
malinois
Iphone, [apple-push-notifications], C#, Sql Server developer.@michaeldardol
Updated on June 04, 2022Comments
-
malinois almost 2 years
It is possible to store a
NSDictionary
in theiPhone
keychain, usingKeychainItemWrapper
(or without)? If it's not possible, have you another solution? -
malinois about 12 years*** Assertion failure in -[KeychainItemWrapper writeToKeychain] 'Couldn't add the Keychain Item.'
-
wbyoung about 12 yearsYou'll have to provide more details, then. There could be many reasons for 'Couldn't add the Keychain Item.'
-
Bret Deasy over 11 yearsI edited the code to reflect more accurately how this is used with KeychainItemWrapper.
-
Rob Napier about 11 yearsThis stores the data in
kSecAttrService
, which is not an encrypted field. I believe you meant to usekSecValueData
here, which is the encrypted payload. -
user798719 over 10 yearsYour code does not work in ios7 for some reason. Would consider updating it to be more clear. For example, you say that we need to use [dic description] but in your example there is no dic variable.
-
Bret Deasy over 10 years@user798719 - I'm actually saying not to use [dic description] and [dic propertyList] if you want to maintain data types in the NSDictionary object.
-
DTs over 10 yearsThe code doesn't work, passing
NSData
for keykSecValueData
breaks theKeychainItemWrapper
, as internally it expects a value ofNSString
for this key (i.e., a password). This is because it needs to encrypt the payload ofkSecValueData
, and before it can do so it needs to convert it toNSData
. Therefore theKeychainItemWrapper
already does[payloadString dataUsingEncoding:NSUTF8StringEncoding]
internally, and if you passNSData
aspayloadString
, you'll get anUnrecognized selector sent to instance exception
. Check out my answer on this page for more details and a solution. -
zaph over 9 yearsNot all data is valid UTF-8 so this will not work. The best option is to encode to Base64.
-
Graham Perks over 9 yearsIt might work; after all the XML starts out by claiming UTF-8 encoding, <?xml version="1.0" encoding="UTF-8"?>. I believe that Apple encodes data as Base64 in the XML (See developer.apple.com/library/mac/documentation/Cocoa/Conceptual/… for an example). If that does fail, your fallback to Base64 is a good idea.
-
Fervus about 9 yearsLooks good. I used yours except I made deleteFromKeychainWithKey a class method so I could also perform general cleanup without having the dictionary.
-
dogsgod over 8 yearsWorks like a charm. I added the best parts from the KeychainItemWrapper.
-
Ne AS about 7 yearsPlease how can I call dictionaryFromKeychainWithKey?