Szukaj…


Składnia

  • kSecClassGenericPassword // Klucz wartości reprezentujący hasło inne niż internetowe
  • kSecClassInternetPassword // Klucz wartości reprezentujący hasło internetowe
  • kSecClassCertificate // Klucz wartości reprezentujący certyfikat
  • kSecClassCertificate // Klucz wartości reprezentujący klucz
  • kSecClassIdentity // Klucz wartości reprezentujący tożsamość, który jest certyfikatem plus klucz

Uwagi

iOS przechowuje prywatne informacje, takie jak hasła, klucze szyfrowania, certyfikaty i tożsamości w bezpiecznym obszarze przechowywania zwanym pęku kluczy. Ten obszar pamięci jest zarządzany całkowicie przez koprocesor o nazwie Secure Enclave, który jest wbudowany w procesor aplikacji. Ponieważ brelok jest w piaskownicy w systemie iOS, elementy pęku kluczy mogą być pobierane tylko przez aplikację, która je tam umieściła.

W niektórych przypadkach musisz włączyć udostępnianie pęku kluczy w funkcjach Xcode, aby uniknąć błędów.

W celu interakcji z pękiem kluczy używamy frameworka zwanego Keychain Services. Aby uzyskać więcej informacji, zobacz Podręcznik programowania pęku kluczy Apple .

Ponieważ Brelok Services jest poniżej Foundation poziomie, jest on ograniczony do korzystania CoreFoundation typy. W rezultacie większość obiektów jest wewnętrznie reprezentowana jako CFDictionary trzymające CFString s jako klucze i różne typy CoreFoundation jako swoje wartości.

Chociaż usługi pęku kluczy są zawarte w ramach Security , importowanie Foundation jest zwykle dobrą opcją, ponieważ zawiera funkcje pomocnicze w wewnętrznej bazie danych.

Ponadto, jeśli nie chcesz bezpośrednio zajmować się Usługami pęku kluczy, Apple udostępnia przykładowy projekt Ogólnego pęku kluczy Swift, który zapewnia typy Swift korzystające z Usług pęku kluczy za kulisami.

Dodawanie hasła do pęku kluczy

Każdy element pęku kluczy jest najczęściej reprezentowany jako CFDictionary . Możesz jednak po prostu użyć NSDictionary w Objective-C i skorzystać z mostkowania, lub w Swift możesz użyć Dictionary i jawnie CFDictionary do CFDictionary .

Możesz zbudować hasło za pomocą następującego słownika:

Szybki

var dict = [String : AnyObject]()

Po pierwsze, potrzebujesz pary klucz / wartość, która informuje pęku kluczy, że to hasło. Należy zauważyć, że ponieważ nasz klucz dict jest String musimy rzucać żadnych CFString do String wyraźnie w Swift 3. CFString nie może być stosowany jako klucz do szybkiej Dictionary, ponieważ nie jest Hashable.

Szybki

dict[kSecClass as String] = kSecClassGenericPassword

Następnie nasze hasło może mieć szereg atrybutów, które je opisają i pomogą nam je później znaleźć. Oto lista atrybutów ogólnych haseł .

Szybki

// The password will only be accessible when the device is unlocked
dict[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked
// Label may help you find it later
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString

Wreszcie potrzebujemy naszych rzeczywistych danych prywatnych. Pamiętaj, aby nie przechowywać tego zbyt długo w pamięci. To musi być CFData .

Szybki

dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData

Wreszcie funkcja dodawania usług pęku kluczy chce wiedzieć, w jaki sposób powinna zwrócić nowo skonstruowany element pęku kluczy. Ponieważ nie powinieneś długo trzymać danych w pamięci, oto jak możesz zwrócić atrybuty:

Szybki

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Teraz zbudowaliśmy nasz przedmiot. Dodajmy to:

Szybki

var result: AnyObject?
let status = withUnsafeMutablePointer(to: &result) {
    SecItemAdd(dict as CFDictionary, UnsafeMutablePointer($0))
}
let newAttributes = result as! Dictionary<String, AnyObject>

To powoduje, że nowe atrybuty dyktują wewnątrz result . SecItemAdd pobiera skonstruowany słownik, a także wskaźnik do miejsca, w którym chcielibyśmy uzyskać nasz wynik. Następnie funkcja zwraca OSStatus wskazujący powodzenie lub kod błędu. Kody wyników są opisane tutaj .

Znajdowanie hasła w pęku kluczy

Aby zbudować zapytanie, musimy je przedstawić jako CFDictionary . Możesz także użyć NSDictionary w Objective-C lub Dictionary w Swift i CFDictionary do CFDictionary .

Potrzebujemy klucza klasy:

Szybki

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword

Następnie możemy określić atrybuty, aby zawęzić nasze wyszukiwanie:

Szybki

// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString

Możemy również określić specjalne klucze modyfikujące wyszukiwanie opisane tutaj .

Na koniec musimy powiedzieć, w jaki sposób chcielibyśmy zwrócić nasze dane. Poniżej poprosimy o zwrócenie samego hasła prywatnego jako obiektu CFData :

Szybki

dict[kSecReturnData as String] = kCFBooleanTrue

Teraz wyszukajmy:

Szybki

var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
    SecItemCopyMatching(dict as CFDictionary, UnsafeMutablePointer($0))
}
// Don't keep this in memory for long!!
let password = String(data: queryResult as! Data, encoding: .utf8)!

Tutaj SecItemCopyMatching pobiera słownik zapytań i wskaźnik do miejsca, w którym chcesz przejść do wyniku. Zwraca OSStatus z kodami wynikowymi. Oto możliwości.

Aktualizowanie hasła w pęku kluczy

Jak zwykle potrzebujemy najpierw CFDictionary do przedstawienia elementu, który chcemy zaktualizować. Musi on zawierać wszystkie stare wartości elementu, w tym stare dane prywatne. Następnie pobiera CFDictionary dowolnych atrybutów lub samych danych, które chcesz zmienić.

Najpierw stwórzmy klucz klasy i listę atrybutów. Te atrybuty mogą zawęzić nasze wyszukiwanie, ale jeśli chcesz je zmienić, musisz dołączyć wszelkie atrybuty i stare wartości.

Szybki

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString

Teraz musimy dodać stare dane:

Szybki

dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData

Teraz utwórzmy te same atrybuty, ale inne hasło:

Szybki

var newDict = [String : AnyObject]()
newDict[kSecClass as String] = kSecClassGenericPassword
// Label
newDict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
newDict[kSecAttrAccount as String] = "My Name" as CFString
// New password
newDict[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData

Teraz przekazujemy go do usług pęku kluczy:

Szybki

let status = SecItemUpdate(dict as CFDictionary, newDict as CFDictionary)

SecItemUpdate zwraca kod stanu. Wyniki opisano tutaj .

Usuwanie hasła z pęku kluczy

Potrzebujemy tylko jednej rzeczy, aby usunąć element z pęku kluczy: CFDictionary z atrybutami opisującymi elementy do usunięcia. Wszelkie elementy, które pasują do słownika zapytań, zostaną trwale usunięte, więc jeśli zamierzasz usunąć tylko jeden element, pamiętaj, aby podać szczegółowe informacje dotyczące zapytania. Jak zawsze możemy użyć NSDictionary w Objective-C lub w Swift możemy użyć Dictionary a następnie rzutować na CFDictionary .

Słownik zapytań, w tym kontekście zawiera wyłącznie klucz klasy opisujący, czym jest element i atrybuty opisujące informacje o elemencie. Uwzględnienie ograniczeń wyszukiwania, takich jak kSecMatchCaseInsensitive jest niedozwolone.

Szybki

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString

A teraz możemy go po prostu usunąć:

Szybki

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete zwraca OSStatus . Kody wyników są opisane tutaj .

Pęku kluczy Dodawanie, aktualizowanie, usuwanie i znajdowanie operacji za pomocą jednego pliku.

Pęku kluczy. H

#import <Foundation/Foundation.h>
typedef void (^KeychainOperationBlock)(BOOL successfulOperation, NSData *data, OSStatus status);

@interface Keychain : NSObject

-(id) initWithService:(NSString *) service_ withGroup:(NSString*)group_;

-(void)insertKey:(NSString *)key withData:(NSData *)data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)updateKey:(NSString*)key withData:(NSData*) data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)removeDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;
-(void)findDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;

@end

Keychain.m

#import "Keychain.h"
#import <Security/Security.h>

@implementation Keychain

{
    NSString * keychainService;
    NSString * keychainGroup;
}

-(id) initWithService:(NSString *)service withGroup:(NSString*)group
{
    self =[super init];
    if(self) {
        keychainService = [NSString stringWithString:service];
        if(group) {
            keychainGroup = [NSString stringWithString:group];
        }
    }
    
    return  self;
}

-(void)insertKey:(NSString *)key
        withData:(NSData *)data
  withCompletion:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary * dict =[self prepareDict:key];
    [dict setObject:data forKey:(__bridge id)kSecValueData];
    [dict setObject:keychainService forKey:(id)kSecAttrService];
    
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
    if(errSecSuccess != status) {
        DLog(@"Unable add item with key =%@ error:%d",key,(int)status);
        if (completionBlock) {
            completionBlock(errSecSuccess == status, nil, status);
        }
    }
    if (status == errSecDuplicateItem) {
        [self updateKey:key withData:data withCompletion:^(BOOL successfulOperation, NSData *updateData, OSStatus updateStatus) {
            if (completionBlock) {
                completionBlock(successfulOperation, updateData, updateStatus);
            }
            DLog(@"Found duplication item -- updating key with data");
        }];
    }
}

-(void)findDataForKey:(NSString *)key
  withCompletionBlock:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary *dict = [self prepareDict:key];
    [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    [dict setObject:keychainService forKey:(id)kSecAttrService];
    [dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    CFTypeRef result = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);
    
    if( status != errSecSuccess) {
        DLog(@"Unable to fetch item for key %@ with error:%d",key,(int)status);
        if (completionBlock) {
            completionBlock(errSecSuccess == status, nil, status);
        }
    } else {
        if (completionBlock) {
            completionBlock(errSecSuccess == status, (__bridge NSData *)result, status);
        }
    }
}

-(void)updateKey:(NSString *)key
        withData:(NSData *)data
  withCompletion:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary * dictKey =[self prepareDict:key];
    
    NSMutableDictionary * dictUpdate =[[NSMutableDictionary alloc] init];
    [dictUpdate setObject:data forKey:(__bridge id)kSecValueData];
    [dictUpdate setObject:keychainService forKey:(id)kSecAttrService];
    OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)dictKey, (__bridge CFDictionaryRef)dictUpdate);
    if( status != errSecSuccess) {
        DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
    }
    if (completionBlock) {
        completionBlock(errSecSuccess == status, nil, status);
    }
}

-(void)removeDataForKey:(NSString *)key
    withCompletionBlock:(KeychainOperationBlock)completionBlock {
    NSMutableDictionary *dict = [self prepareDict:key];
    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict);
    if( status != errSecSuccess) {
        DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
    }
    if (completionBlock) {
        completionBlock(errSecSuccess == status, nil, status);
    }
}

#pragma mark Internal methods

-(NSMutableDictionary*) prepareDict:(NSString *) key {
    
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    
    NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
    [dict setObject:keychainService forKey:(__bridge id)kSecAttrService];
    [dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
    
    //This is for sharing data across apps
    if(keychainGroup != nil) {
        [dict setObject:keychainGroup forKey:(__bridge id)kSecAttrAccessGroup];
    }
    
    return  dict;
}

@end

Kontrola dostępu do pęku kluczy (TouchID z rezerwowym hasłem)

Keychain pozwala zapisywać elementy ze specjalnym atrybutem SecAccessControl, który pozwoli uzyskać element z pęku kluczy dopiero po uwierzytelnieniu użytkownika za pomocą Touch ID (lub kodu dostępu, jeśli taka awaria jest dozwolona). Aplikacja jest powiadamiana tylko o tym, czy uwierzytelnienie powiodło się, czy nie, cały interfejs użytkownika jest zarządzany przez system iOS.

Najpierw należy utworzyć obiekt SecAccessControl:

Szybki

let error: Unmanaged<CFError>?

guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .userPresence, &error) else {
    fatalError("Something went wrong")
}

Następnie dodaj go do słownika za pomocą klucza kSecAttrAccessControl (który wyklucza się wzajemnie z kluczem kSecAttrAccessible, którego używałeś w innych przykładach):

Szybki

var dictionary = [String : Any]()

dictionary[kSecClass as String] = kSecClassGenericPassword
dictionary[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
dictionary[kSecAttrAccount as String] = "My Name" as CFString
dictionary[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData
dictionary[kSecAttrAccessControl as String] = accessControl

I zapisz to tak jak wcześniej:

Szybki

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

Aby uzyskać dostęp do przechowywanych danych, po prostu zapytaj Keychain o klucz. Usługi pęku kluczy przedstawią użytkownikowi okno dialogowe uwierzytelnienia i zwrócą dane lub zero w zależności od tego, czy dostarczono odpowiedni odcisk palca, czy pasowano hasło.

Opcjonalnie można podać ciąg zachęty:

Szybki

var query = [String: Any]()

query[kSecClass as String] = kSecClassGenericPassword
query[kSecReturnData as String] = kCFBooleanTrue
query[kSecAttrAccount as String] = "My Name" as CFString
query[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
query[kSecUseOperationPrompt as String] = "Please put your fingers on that button" as CFString

var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
    SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}

Zwróć uwagę, że status będzie err jeśli użytkownik odmówi, anuluje lub nie powiedzie się autoryzacja.

Szybki

if status == noErr {
    let password = String(data: queryResult as! Data, encoding: .utf8)!
    print("Password: \(password)")
} else {
    print("Authorization not passed")
}


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow