2012-03-30 4 views
18

È possibile memorizzare un NSDictionary nel portachiavi iPhone, utilizzando KeychainItemWrapper (o senza)? Se non è possibile, hai un'altra soluzione?Memorizza NSDictionary nel portachiavi

+0

Sì, ma quando ho letto i dati, ho un riferimento a un NSString vuoto. – malinois

risposta

6

Encoding: [dic description]
decodifica: [dic propertyList]

+0

Non posso prima di domani ... – malinois

1

È possibile memorizzare qualsiasi cosa, è sufficiente serializzarlo.

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary]; 

Dovresti riuscire a memorizzare tali dati nel portachiavi.

+2

*** Errore asserzione in - [KeychainItemWrapper writeToKeychain] 'Impossibile aggiungere l'oggetto portachiavi.' – malinois

+0

Dovrai fornire maggiori dettagli, quindi. Potrebbero esserci molte ragioni per "Impossibile aggiungere l'oggetto portachiavi". – wbyoung

25

Devi correttamente serializzare l'NSDictionary prima di riporla nel portachiavi. Usando:

[dic description] 
[dic propertyList] 

vi ritroverete con una collezione di soli NSDictionaryNSString oggetti. Se si desidera mantenere i tipi di dati degli oggetti, è possibile utilizzare 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); 
} 

Il NSDictionary restituito dalla seconda chiamata a NSPropertyListSerialization manterrà i tipi di dati originali all'interno della collezione NSDictionary.

+1

Ho modificato il codice per riflettere in modo più accurato come viene usato con KeychainItemWrapper. –

+4

Memorizza i dati in 'kSecAttrService', che non è un campo crittografato. Credo che tu intendessi usare 'kSecValueData' qui, che è il carico utile crittografato. –

+0

Il tuo codice non funziona in iOS7 per qualche motivo. Prenderò in considerazione l'idea di aggiornarlo per essere più chiaro. Ad esempio, tu dici che dobbiamo usare [descrizione dic] ma nel tuo esempio non esiste una variabile dic. – user798719

0

Ho trovato che il wrapper portachiavi vuole solo stringhe. Neanche NSData. Quindi per memorizzare un dizionario devi fare come suggerito Bret, ma con un passo in più per convertire la serializzazione NSData in una stringa. Come questo:

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)]; 

lettura indietro:

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

Non tutti i dati sono validi UTF-8, quindi non funzionerà. L'opzione migliore è codificare su Base64. – zaph

+0

Potrebbe funzionare; dopo tutto l'XML inizia sostenendo la codifica UTF-8, . Credo che Apple codifichi i dati come Base64 nell'XML (per un esempio vedi https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/SerializePlist/SerializePlist.html). Se ciò non riesce, la tua ripiego su Base64 è una buona idea. –

14

Utilizzando la KeychainItemWrapper dipendenza richiede la modifica del codice/campione biblioteca di accettare NSData come payload criptato, che non è a prova di futuro. Inoltre, eseguire la sequenza di conversione NSDictionary > NSData > NSString solo per poter utilizzare KeychainItemWrapper è inefficiente: KeychainItemWrapper convertirà comunque la stringa in NSData per crittografarla.

Ecco una soluzione completa che risolve quanto sopra utilizzando direttamente la libreria portachiavi. E 'implementato come una categoria in modo da utilizzare in questo modo:

// to store your dictionary 
[myDict storeToKeychainWithKey:@"myStorageKey"]; 

// to retrieve it 
NSDictionary *myDict = [NSDictionary dictionaryFromKeychainWithKey:@"myStorageKey"]; 

// to delete it 
[myDict deleteFromKeychainWithKey:@"myStorageKey"]; 


ed ecco Categoria:

@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 realtà, è possibile modificare facilmente per memorizzare qualsiasi tipo di oggetto serializzabile in il portachiavi, non solo un dizionario. Basta creare una rappresentazione NSData dell'oggetto che si desidera memorizzare.

11

Apportate alcune modifiche minori alla categoria Dts. Convertito in ARC e utilizzando NSKeyedArchiver per archiviare oggetti personalizzati.

@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

Sembra buono. Ho usato il tuo, tranne che ho fatto deleteFromKeychainWithKey un metodo di classe in modo da poter eseguire anche la pulizia generale senza avere il dizionario. – Fervus

+0

Fantastico !!!!!!!!! –

+0

Funziona come un fascino. Ho aggiunto le parti migliori da KeychainItemWrapper. – dogsgod

0

ho aggiunto il supporto gruppo di accesso e sicurezza simulatore soluzione 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