2012-03-30 118 views
18

可以使用KeychainItemWrapper(或不使用)在iPhone鑰匙串中存儲NSDictionary? 如果這是不可能的,你有另一種解決方案?將NSDictionary存儲在鑰匙串中

+0

是的,但是當我讀到的數據,我有一個空的NSString參考。 – malinois 2012-03-30 18:51:25

回答

6

編碼:[dic description]
解碼:[dic propertyList]

+0

我不能在明天之前... – malinois 2012-03-31 14:24:05

1

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

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary]; 

您應該能夠將數據存儲在鑰匙串中。

+2

*** - [KeychainItemWrapper writeToKeychain]中的聲明失敗'無法添加鑰匙串項目'。 – malinois 2012-03-30 19:50:03

+0

然後,您必須提供更多詳細信息。 「無法添加鑰匙串項目」可能有很多原因。 – wbyoung 2012-03-30 20:48:21

25

你必須正確地將其存儲到鑰匙串之前序列化NSDictionary。 使用:

[dic description] 
[dic propertyList] 

,你會最終有一個NSDictionary集合只NSString對象。如果要維護對象的數據類型,則可以使用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); 
} 

第二調用NSPropertyListSerializationNSDictionary集合中保持原始數據類型返回的NSDictionary

+1

我編輯了代碼,以更準確地反映如何使用KeychainItemWrapper。 – 2012-11-16 01:24:59

+4

這將數據存儲在不是加密字段的'kSecAttrService'中。我相信你打算在這裏使用'kSecValueData',這是加密的有效載荷。 – 2013-04-23 16:57:19

+0

由於某些原因,您的代碼在ios7中不起作用。會考慮更新它更清楚。例如,你說我們需要使用[dic description],但在你的例子中沒有dic變量。 – user798719 2013-09-02 19:04:58

0

我發現鑰匙串包裝只需要字符串。甚至沒有NSData。因此,要存儲一個字典,您必須按照Bret的建議來做,但需要額外的步驟將NSData序列化轉換爲字符串。就像這樣:

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); 
    } 
} 
+0

並非所有的數據都是有效的UTF-8,所以這是行不通的。最好的選擇是編碼到Base64。 – zaph 2015-01-09 04:02:27

+0

它可能工作;畢竟XML是通過聲明UTF-8編碼開始的,<?xml version =「1.0」encoding =「UTF-8」?>。我相信Apple在XML中將數據編碼爲Base64(請參閱https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/SerializePlist/SerializePlist.html中的示例)。如果確實失敗了,那麼回退到Base64是個好主意。 – 2015-01-09 15:49:46

14

使用KeychainItemWrapper依賴性要求修改庫/示例代碼接受NSData爲加密的有效載荷,這是不是未來的證明。另外,執行NSDictionary > NSData > NSString轉換序列只是爲了您可以使用KeychainItemWrapper效率低下:KeychainItemWrapper無論如何都會將您的字符串轉換回NSData,以對其進行加密。

這是一個完整的解決方案,通過直接利用鑰匙串庫來解決上述問題。

// 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表示形式。

11

對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 
+0

看起來不錯。我用你的,除了我做了deleteFromKeychainWithKey一個類的方法,所以我也可以執行一般的清理,而不需要字典。 – Fervus 2015-04-09 14:38:47

+0

好!!!!!!!!! – 2015-04-11 07:11:09

+0

工程就像一個魅力。我從KeychainItemWrapper中添加了最好的部分。 – dogsgod 2016-02-11 20:02:14

0

我加訪問組的支持和模擬器安全Amols解決方案:

// 
// 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