È possibile memorizzare un NSDictionary
nel portachiavi iPhone
, utilizzando KeychainItemWrapper
(o senza)? Se non è possibile, hai un'altra soluzione?Memorizza NSDictionary nel portachiavi
risposta
Encoding: [dic description]
decodifica: [dic propertyList]
Non posso prima di domani ... – malinois
È possibile memorizzare qualsiasi cosa, è sufficiente serializzarlo.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dictionary];
Dovresti riuscire a memorizzare tali dati nel portachiavi.
Devi correttamente serializzare l'NSDictionary
prima di riporla nel portachiavi. Usando:
[dic description]
[dic propertyList]
vi ritroverete con una collezione di soli NSDictionary
NSString
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
.
Ho modificato il codice per riflettere in modo più accurato come viene usato con KeychainItemWrapper. –
Memorizza i dati in 'kSecAttrService', che non è un campo crittografato. Credo che tu intendessi usare 'kSecValueData' qui, che è il carico utile crittografato. –
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
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);
}
}
Non tutti i dati sono validi UTF-8, quindi non funzionerà. L'opzione migliore è codificare su Base64. – zaph
Potrebbe funzionare; dopo tutto l'XML inizia sostenendo la codifica UTF-8, xml version = "1.0" encoding = "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. –
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.
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
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
Fantastico !!!!!!!!! –
Funziona come un fascino. Ho aggiunto le parti migliori da KeychainItemWrapper. – dogsgod
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
Sì, ma quando ho letto i dati, ho un riferimento a un NSString vuoto. – malinois