What follows is working code that lets you maintain a dictionary of data tied to a primary "key" (an EMail address here which could be any primary identifier in your app).
The other keys get stored in a dictionary of arbitrary size, and get placed in what would be the "password" keyChain object.
First, I put this at the bottom of my AppDelegate.h file:
#define kEmail @"email"
#define kPassword @"pass"
#define kSomeOtherItemA @"a_item"
#define kSomeOtherItemB @"b_item"
@interface AppDelegate (Keychain)
- (NSDictionary *)keychainDictionary; // current keyChain dictionary
- (void)eraseKeychain; // total wipe it
- (void)erasePassword; // wipe the password
@end
You can add special methods here as you see fit. By placing this is the AppDelegate.h file, any other class importing that file can use these methods. If you want it private move it to your AppDelegate.m file
This goes in your AppDelegate.m file, up top:
@interface AppDelegate (KeychainPrivate)
- (void)updateKeychainItem:(NSString *)item forKey:(NSString *)key; //the password, or itemA, or itemB
- (id)keychainItemForKey:(NSString *)key; // the password, itemA, itemB, ...
@end
This can go at the bottom of AppDelegate.m or in a separate file:
static char *kcIdentifier = "foo"; // use any string you want instead of "foo"
@implementation AppDelegate (Keychain)
- (KeychainItemWrapper *)keyChainItemWrapper
{
static dispatch_once_t pred;
static KeychainItemWrapper *kciw;
dispatch_once(&pred, ^
{
kciw = [[KeychainItemWrapper alloc] initWithIdentifier:kcIdentifier accessGroup:nil];
} );
return kciw;
}
- (NSDictionary *)keychainDictionary
{
NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithCapacity:3];
KeychainItemWrapper *kcItem = [self keyChainItemWrapper];
NSString *emailAddr = [kcItem objectForKey:(__bridge id)kSecAttrAccount];
if([emailAddr length]) {
NSData *data = [kcItem objectForKey:(__bridge id)kSecValueData];
if([data length]) {
NSKeyedUnarchiver *kua = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSDictionary *dict = [kua decodeObject];
[kua finishDecoding];
[mDict addEntriesFromDictionary:dict];
}
mDict[kEmail] = emailAddr; // last in case it got in the dictionary somehow
}
return mDict;
}
- (void)eraseKeychain
{
KeychainItemWrapper *kcItem = [self keyChainItemWrapper];
[kcItem resetKeychainItem];
}
- (void)erasePassword
{
[self updateKeychainItem:@"" forKey:kPassword];
}
@end
@implementation AppDelegate (KeychainPrivate)
- (void)updateKeychainItem:(NSString *)item forKey:(NSString *)key
{
KeychainItemWrapper *kcItem = [self keyChainItemWrapper];
// NSLog(@"SET KC key=%@ ITEM %@", key, item);
// NSLog(@"CURRENT KEYCHAIN: %@", [self keychainDictionary]);
if([key isEqualToString:kEmail]) {
NSString *emailAddr = [kcItem objectForKey:(__bridge id)kSecAttrAccount];
if(![emailAddr isEqualToString:item]) {
[kcItem setObject:item forKey:(__bridge id)kSecAttrAccount];
// NSLog(@"SET KC Account ITEM %@", item);
}
// NSLog(@"KC Account ITEM %@ ALREADY SET", item);
} else {
NSData *data = [kcItem objectForKey:(__bridge id)kSecValueData];
//NSLog(@"KC get DATA len=%d", [data length]);
NSDictionary *dict;
if([data length]) {
NSKeyedUnarchiver *kua = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
dict = [kua decodeObject];
[kua finishDecoding];
} else {
dict = [NSDictionary dictionary];
}
//NSLog(@"KC OLD DICT %@", dict);
if(![item isEqualToString:dict[key]]) {
//NSLog(@"KC DATA NOT EQUAL");
NSMutableDictionary *mDict = [NSMutableDictionary dictionaryWithDictionary:dict];
mDict[key] = item;
NSMutableData *tmpData = [NSMutableData dataWithCapacity:256];
NSKeyedArchiver *ka = [[NSKeyedArchiver alloc] initForWritingWithMutableData:tmpData];
[ka encodeObject:mDict];
[ka finishEncoding];
//NSLog(@"KC ENCODE MDICT %@", mDict);
[kcItem setObject:tmpData forKey:(__bridge id)kSecValueData];
//NSLog(@"SET KC DATA LEN=%d", [tmpData length]);
}
}
//NSLog(@"JUST UPDATED KEYCHAIN KEY=%@ val=%@ read dict back=%@", key, item, [self keychainDictionary]);
}
- (id)keychainItemForKey:(NSString *)key
{
NSDictionary *dict = [self keychainDictionary];
return dict[key];
}
@end
You also need the KeyChainItemWrapper class. You can get an ARCified and slightly modified (so as to handle a dictionary) KeyChainItemWrapper version. You can really use any KeyChainWrapper file you want, but need to make this change (which is the only change to Apple's code other than ARCifying it):
// Default data for keychain item.
#ifndef PASSWORD_USES_DATA
[keychainItemData setObject:@"" forKey:(__bridge id)kSecValueData];
#else
[keychainItemData setObject:[NSData data] forKey:(__bridge id)kSecValueData];
#endif
As you can see, there are lots of log messages you can uncomment to see it in action if you want.
This code was used in a shipping App.