2012-03-30 9 views
18

Es posible almacenar NSDictionary en el llavero iPhone, usando KeychainItemWrapper (o sin)? Si no es posible, ¿tiene otra solución?Tienda NSDiccionario en llavero

+0

Sí, pero cuando leí los datos, tengo una referencia a un NSString vacía. – malinois

Respuesta

6

Codificación: [dic description]
Decodificación: [dic propertyList]

+0

No puedo antes de mañana ... – malinois

1

Puede almacenar cualquier cosa, solo necesita serializarlo.

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary]; 

Debería poder almacenar esa información en el llavero.

+2

*** Error de aserción en - [KeychainItemWrapper writeToKeychain] 'No se pudo agregar el elemento de llavero'. – malinois

+0

Tendrás que proporcionar más detalles, entonces. Puede haber muchas razones para 'No se pudo agregar el elemento de llavero'. – wbyoung

25

Debe serializar correctamente el NSDictionary antes de almacenarlo en el llavero. Usando :

[dic description] 
[dic propertyList] 

que va a terminar con una colección de sólo NSDictionaryNSString objetos. Si desea mantener los tipos de datos de los objetos, puede usar 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); 
} 

El NSDictionary devuelto por la segunda llamada a NSPropertyListSerialization mantendrá los tipos de datos originales dentro de la colección NSDictionary.

+1

He editado el código para reflejar con mayor precisión cómo se usa con KeychainItemWrapper. –

+4

Almacena los datos en 'kSecAttrService', que no es un campo encriptado. Creo que quisiste usar 'kSecValueData' aquí, que es la carga cifrada. –

+0

Tu código no funciona en ios7 por algún motivo. Consideraría actualizarlo para ser más claro. Por ejemplo, dices que necesitamos usar [dic description] pero en tu ejemplo no hay una variable dic. – user798719

0

Descubrí que el envoltorio de llavero solo quiere cuerdas. Ni siquiera NSData. Entonces, para almacenar un diccionario, deberá hacer lo que Bret sugirió, pero con un paso adicional para convertir la serialización de NSData a una cadena. De esta manera:

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

lectura de vuelta:

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

No todos los datos son válidos UTF-8 por lo que esto no funcionará. La mejor opción es codificar a Base64. – zaph

+0

Podría funcionar; después de todo, el XML comienza reclamando la codificación UTF-8, . Creo que Apple codifica datos como Base64 en el XML (ver https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/SerializePlist/SerializePlist.html para ver un ejemplo). Si eso falla, su respaldo a Base64 es una buena idea. –

14

Usando la dependencia KeychainItemWrapper requiere modificar el código/muestra de la biblioteca para aceptar NSData como la carga útil cifrada, que no es a prueba de futuro. Además, al hacer la secuencia de conversión NSDictionary > NSData > NSString solo para que pueda usar KeychainItemWrapper es ineficiente: KeychainItemWrapper convertirá de nuevo su cadena en NSData, para encriptarla.

Aquí hay una solución completa que resuelve lo anterior utilizando directamente la biblioteca de llavero. Se implementa como una categoría por lo que utilizar de esta manera:

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

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

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


y aquí está la Categoría:

@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 

De hecho, se puede modificar fácilmente para almacenar cualquier tipo de objeto serializable en el llavero, no solo un diccionario. Simplemente realice una representación NSData del objeto que desea almacenar.

11

Se realizaron algunos cambios menores en la categoría Dts. Convertido a ARC y utilizando NSKeyedArchiver para almacenar objetos personalizados.

@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

Se ve bien. Usé el suyo, excepto que hice de deleteFromKeychainWithKey un método de clase, así que también pude realizar una limpieza general sin tener el diccionario. – Fervus

+0

¡¡¡Genial !!!!!!!!! –

+0

Funciona como un encanto. Agregué las mejores partes de KeychainItemWrapper. – dogsgod

0

I añadido soporte grupo de acceso y seguridad simulador para solución 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 
Cuestiones relacionadas