2011-03-09 14 views
56

Jestem bardzo początkującym programistom iOS, więc wybaczcie mi, jeśli to jest pytanie dla początkujących. Mam prosty mechanizm uwierzytelniania dla mojej aplikacji, który pobiera adres e-mail użytkownika i hasło. Mam też przełącznik, który mówi "Zapamiętaj mnie". Jeśli użytkownik przełącza to ustawienie, chcę zachować adres e-mail/hasło, aby te pola mogły być automatycznie wypełniane w przyszłości.Zapisywanie adresu e-mail/hasła do pęku kluczy w systemie iOS

Zrobiłem to do pracy z zapisaniem do pliku plist, ale wiem, że to nie jest najlepszy pomysł, ponieważ hasło nie jest zaszyfrowane. Znalazłem przykładowy kod do zapisania w pęku kluczy, ale szczerze mówiąc, jestem trochę zagubiony. W przypadku poniższej funkcji nie wiem, jak ją nazwać i jak ją zmodyfikować, aby zapisać również adres e-mail.

Zgaduję zadzwonić byłoby: saveString(@"passwordgoeshere");

Dziękuję za wszelką pomoc !!!

+ (void)saveString:(NSString *)inputString forKey:(NSString *)account { 

    NSAssert(account != nil, @"Invalid account"); 
    NSAssert(inputString != nil, @"Invalid string"); 

    NSMutableDictionary *query = [NSMutableDictionary dictionary]; 

    [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; 
    [query setObject:account forKey:(id)kSecAttrAccount]; 
    [query setObject:(id)kSecAttrAccessibleWhenUnlocked forKey:(id)kSecAttrAccessible]; 

    OSStatus error = SecItemCopyMatching((CFDictionaryRef)query, NULL); 
    if (error == errSecSuccess) { 
     // do update 
     NSDictionary *attributesToUpdate = [NSDictionary dictionaryWithObject:[inputString dataUsingEncoding:NSUTF8StringEncoding] 
                     forKey:(id)kSecValueData]; 

     error = SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attributesToUpdate); 
     NSAssert1(error == errSecSuccess, @"SecItemUpdate failed: %d", error); 
    } else if (error == errSecItemNotFound) { 
     // do add 
     [query setObject:[inputString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData]; 

     error = SecItemAdd((CFDictionaryRef)query, NULL); 
     NSAssert1(error == errSecSuccess, @"SecItemAdd failed: %d", error); 
    } else { 
     NSAssert1(NO, @"SecItemCopyMatching failed: %d", error); 
    } 
} 
+5

Naprawiłem @ kodu anomii do pracy z ARC i umieścić go na Github (I związana z tym odpowiedź i wymienić swoją użytkownika w obu plikach, ale jeśli chcesz dalsze przyznawanie daj mi znać). Zmieniłem trochę formatowanie i uczyniłem nazwy metod nieco bardziej ogólnymi. https://github.com/jeremangnr/JNKeychain – jere

Odpowiedz

100

Napisałem prosty wrapper, który pozwala na zapisywanie wszelkich NSCoding zgodnym obiektu do pęku kluczy. Możesz na przykład przechowywać swój adres e-mail i hasło w NSDictionary i przechowywać NSDictionary do pęku kluczy przy użyciu tej klasy.

SimpleKeychain.h

#import <Foundation/Foundation.h> 

@class SimpleKeychainUserPass; 

@interface SimpleKeychain : NSObject 

+ (void)save:(NSString *)service data:(id)data; 
+ (id)load:(NSString *)service; 
+ (void)delete:(NSString *)service; 

@end 

SimpleKeychain.m

#import "SimpleKeychain.h" 

@implementation SimpleKeychain 

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service { 
    return [NSMutableDictionary dictionaryWithObjectsAndKeys: 
      (id)kSecClassGenericPassword, (id)kSecClass, 
      service, (id)kSecAttrService, 
      service, (id)kSecAttrAccount, 
      (id)kSecAttrAccessibleAfterFirstUnlock, (id)kSecAttrAccessible, 
      nil]; 
} 

+ (void)save:(NSString *)service data:(id)data { 
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; 
    SecItemDelete((CFDictionaryRef)keychainQuery); 
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData]; 
    SecItemAdd((CFDictionaryRef)keychainQuery, NULL); 
} 

+ (id)load:(NSString *)service { 
    id ret = nil; 
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; 
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; 
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; 
    CFDataRef keyData = NULL; 
    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) { 
     @try { 
      ret = [NSKeyedUnarchiver unarchiveObjectWithData:(NSData *)keyData]; 
     } 
     @catch (NSException *e) { 
      NSLog(@"Unarchive of %@ failed: %@", service, e); 
     } 
     @finally {} 
    } 
    if (keyData) CFRelease(keyData); 
    return ret; 
} 

+ (void)delete:(NSString *)service { 
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; 
    SecItemDelete((CFDictionaryRef)keychainQuery); 
} 

@end 
+0

Czy to działa na keycahin osx? – abe

+0

@abe: Spróbuj i zobacz? Zgodnie z dokumentacją wszystkie te same funkcje są dostępne, ale możliwe jest, że 'getKeychainQuery:' musi mieć dodatkowe parametry w słowniku. – Anomie

+0

@Anomie i spróbowałem i znalazłem błędy na wartościach, z których jednym jest kSecClassGenericPassword, ale sprawdziłem dokumentację, która wydaje się być wsparciem w fundacyjnym frameworku i obejmowałem także framework bezpieczeństwa, wszelkie przemyślenia na temat może to być błąd? thx – abe

38

ARC kod gotowy:

KeychainUserPass.h

#import <Foundation/Foundation.h> 

@interface KeychainUserPass : NSObject 

+ (void)save:(NSString *)service data:(id)data; 
+ (id)load:(NSString *)service; 
+ (void)delete:(NSString *)service; 

@end 

KeychainUserPass.m

#import "KeychainUserPass.h" 

@implementation KeychainUserPass 

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service { 
    return [NSMutableDictionary dictionaryWithObjectsAndKeys: 
      (__bridge id)kSecClassGenericPassword, (__bridge id)kSecClass, 
      service, (__bridge id)kSecAttrService, 
      service, (__bridge id)kSecAttrAccount, 
      (__bridge id)kSecAttrAccessibleAfterFirstUnlock, (__bridge id)kSecAttrAccessible, 
      nil]; 
} 

+ (void)save:(NSString *)service data:(id)data { 
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; 
    SecItemDelete((__bridge CFDictionaryRef)keychainQuery); 
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData]; 
    SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL); 
} 

+ (id)load:(NSString *)service { 
    id ret = nil; 
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; 
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData]; 
    [keychainQuery setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; 
    CFDataRef keyData = NULL; 
    if (SecItemCopyMatching((__bridge CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) { 
     @try { 
      ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData]; 
     } 
     @catch (NSException *e) { 
      NSLog(@"Unarchive of %@ failed: %@", service, e); 
     } 
     @finally {} 
    } 
    if (keyData) CFRelease(keyData); 
    return ret; 
} 

+ (void)delete:(NSString *)service { 
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; 
    SecItemDelete((__bridge CFDictionaryRef)keychainQuery); 
} 

@end 
+3

Dziękuję za ARC Gotowa wersja. To działa jak urok. –

+0

Nie ma problemu :) –

+0

flawless, thx !! – mnl

Powiązane problemy