Store NSDictionary in keychain

2019-01-31 09:31发布

It is possible to store a NSDictionary in the iPhone keychain, using KeychainItemWrapper (or without)? If it's not possible, have you another solution?

7条回答
爷的心禁止访问
2楼-- · 2019-01-31 09:51

I added access group support and simulator safety to Amols solution:

//
//  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
查看更多
登录 后发表回答