Suche…


Syntax

  • kSecClassGenericPassword // Ein Wertschlüssel, der ein Nicht-Internet-Kennwort darstellt
  • kSecClassInternetPassword // Ein Wertschlüssel, der ein Internetkennwort darstellt
  • kSecClassCertificate // Ein Wertschlüssel, der ein Zertifikat darstellt
  • kSecClassCertificate // Ein Wertschlüssel, der einen Schlüssel darstellt
  • kSecClassIdentity // Ein Wertschlüssel, der eine Identität darstellt, dh ein Zertifikat plus einen Schlüssel

Bemerkungen

iOS speichert private Informationen wie Kennwörter, Verschlüsselungsschlüssel, Zertifikate und Identitäten in einem sicheren Speicherbereich, der als Schlüsselbund bezeichnet wird. Dieser Speicherbereich wird vollständig von einem Co-Prozessor namens Secure Enclave verwaltet, der in den Anwendungsprozessor eingebettet ist. Da der Schlüsselbund unter iOS auf Sandbox gesetzt wird, können Schlüsselbundelemente nur von der Anwendung abgerufen werden, die sie zuerst dort ablegt.

In einigen Fällen müssen Sie Keychain Sharing in Xcode aktivieren, um Fehler zu vermeiden.

Um mit dem Schlüsselbund zu interagieren, verwenden wir ein als Keychain Services bezeichnetes Rahmenwerk. Weitere Informationen finden Sie im Programmierhandbuch für Apple Keychain Services .

Da sich Keychain Services unterhalb der Foundation Ebene befindet, können nur CoreFoundation Typen verwendet werden. Daher werden die meisten Objekte intern als CFDictionary dargestellt, das CFDictionary CFString als Schlüssel und eine Vielzahl von CoreFoundation Typen als Werte enthält.

Keychain Services sind zwar Bestandteil des Security Frameworks, der Import von Foundation ist jedoch normalerweise eine gute Option, da er einige Hilfsfunktionen im Backend enthält.

Wenn Sie nicht direkt mit Keychain Services arbeiten möchten, stellt Apple das Swift-Beispielprojekt " Generic Keychain " bereit, das Swift-Typen bereitstellt, die Keychain Services im Hintergrund verwenden.

Passwort zum Schlüsselbund hinzufügen

Jedes Schlüsselbundelement wird meistens als CFDictionary . Sie können NSDictionary jedoch einfach in Objective-C verwenden und Bridging nutzen. In Swift können Sie Dictionary und explizit in CFDictionary .

Sie können ein Kennwort mit dem folgenden Wörterbuch erstellen:

Schnell

var dict = [String : AnyObject]()

Zunächst benötigen Sie ein Schlüssel / Wert-Paar, das den Schlüsselbund darüber informiert, dass dies ein Kennwort ist. Da unser CFString ein String , müssen wir jeden CFString in Swift 3 explizit in einen String umwandeln. CFString kann nicht als Schlüssel für ein Swift-Dictionary verwendet werden, da es nicht Hash-fähig ist.

Schnell

dict[kSecClass as String] = kSecClassGenericPassword

Als nächstes kann unser Passwort eine Reihe von Attributen haben, die es beschreiben und uns helfen, es später zu finden. Hier ist eine Liste von Attributen für generische Kennwörter .

Schnell

// 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

Schließlich benötigen wir unsere tatsächlichen privaten Daten. Denken Sie daran, dies nicht zu lange im Gedächtnis zu behalten. Dies muss CFData .

Schnell

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

Schließlich möchte die Add-Funktion der Schlüsselbunddienste wissen, wie das neu erstellte Schlüsselbundelement zurückgegeben werden soll. Da Sie die Daten nicht lange im Speicher behalten sollten, können Sie nur die Attribute zurückgeben:

Schnell

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Jetzt haben wir unseren Artikel konstruiert. Lassen Sie uns es hinzufügen:

Schnell

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

Dadurch werden die neuen Attribute in das result eingefügt. SecItemAdd das von uns erstellte Wörterbuch sowie einen Hinweis darauf, wo wir unser Ergebnis haben möchten. Die Funktion gibt dann einen OSStatus , der den Erfolg oder einen Fehlercode angibt. Ergebniscodes werden hier beschrieben.

Ein Passwort im Schlüsselbund finden

Um eine Abfrage zu CFDictionary , müssen wir sie als CFDictionary . Sie können NSDictionary in Objective-C oder Dictionary in Swift verwenden und in CFDictionary .

Wir brauchen einen Klassenschlüssel:

Schnell

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

Als Nächstes können wir Attribute angeben, um unsere Suche einzugrenzen:

Schnell

// 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

Wir können auch spezielle Suchmodifizierungsschlüssel angeben, die hier beschrieben werden .

Zum Schluss müssen wir noch sagen, wie wir möchten, dass unsere Daten zurückgegeben werden. Nachfolgend fordern wir an, dass nur das private Kennwort selbst als CFData Objekt zurückgegeben wird:

Schnell

dict[kSecReturnData as String] = kCFBooleanTrue

Nun suchen wir:

Schnell

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)!

Hier führt SecItemCopyMatching ein Abfragewörterbuch und einen Zeiger auf, wohin das Ergebnis gehen soll. Es gibt einen OSStatus mit Ergebniscodes zurück. Hier sind die Möglichkeiten.

Aktualisieren eines Passworts im Schlüsselbund

Wie üblich benötigen wir zuerst ein CFDictionary , um das Element CFDictionary , das wir aktualisieren möchten. Dieser muss alle alten Werte für das Element enthalten, einschließlich der alten privaten Daten. Dann benötigt es ein CFDictionary aller Attribute oder der Daten selbst, die Sie ändern möchten.

Zuerst erstellen wir einen Klassenschlüssel und eine Liste von Attributen. Diese Attribute können unsere Suche einschränken, Sie müssen jedoch alle Attribute und alte Werte angeben, wenn Sie sie ändern.

Schnell

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

Nun müssen wir die alten Daten hinzufügen:

Schnell

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

Jetzt erstellen wir dieselben Attribute, aber ein anderes Passwort:

Schnell

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

Jetzt geben wir es einfach an Keychain Services weiter:

Schnell

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

SecItemUpdate gibt einen Statuscode zurück. Ergebnisse werden hier beschrieben.

Passwort aus dem Schlüsselbund entfernen

Wir benötigen nur eine Sache, um ein Element aus dem Schlüsselbund zu löschen: ein CFDictionary mit Attributen, die die zu löschenden Elemente beschreiben. Alle Elemente, die mit dem Abfragewörterbuch übereinstimmen, werden dauerhaft gelöscht. Wenn Sie also nur ein einzelnes Element löschen möchten, stellen Sie sicher, dass Sie Ihre Abfrage genau angeben. Wie immer können wir ein NSDictionary in Objective-C oder in Swift verwenden, wir können ein Dictionary und dann in CFDictionary .

Ein Abfragewörterbuch enthält in diesem Zusammenhang ausschließlich einen Klassenschlüssel zum Beschreiben des Elements und Attribute zum Beschreiben von Informationen zum Element. Das kSecMatchCaseInsensitive von Sucheinschränkungen wie kSecMatchCaseInsensitive ist nicht zulässig.

Schnell

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

Und jetzt können wir es einfach entfernen:

Schnell

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete gibt einen OSStatus . Ergebniscodes werden hier beschrieben.

Keychain Hinzufügen, Aktualisieren, Entfernen und Suchen mit einer Datei.

Keychain.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

Schlüsselbund-Zugriffskontrolle (TouchID mit Passwort-Fallback)

Mit Keychain können Elemente mit dem speziellen SecAccessControl-Attribut gespeichert werden, mit dem Artikel nur dann von Keychain abgerufen werden können, wenn der Benutzer mit der Touch-ID (oder einem Passcode, wenn ein solcher Fallback zulässig ist) authentifiziert wird. Die App wird nur benachrichtigt, ob die Authentifizierung erfolgreich war oder nicht, die gesamte Benutzeroberfläche wird von iOS verwaltet.

Zunächst sollte das SecAccessControl-Objekt erstellt werden:

Schnell

let error: Unmanaged<CFError>?

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

Fügen Sie es anschließend mit dem Schlüssel kSecAttrAccessControl (der sich gegenseitig mit dem in anderen Beispielen verwendeten Schlüssel kSecAttrAccessible ausschließt) dem Wörterbuch hinzu:

Schnell

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

Und speichern Sie es wie zuvor:

Schnell

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

Um auf gespeicherte Daten zuzugreifen, fragen Sie einfach Keychain nach einem Schlüssel ab. Keychain Services zeigt dem Benutzer ein Authentifizierungsdialogfeld an und gibt Daten oder Null zurück, je nachdem, ob ein geeigneter Fingerabdruck bereitgestellt oder ein Passcode festgelegt wurde.

Optional kann eine Eingabeaufforderungszeichenfolge angegeben werden:

Schnell

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))
}

Achten Sie darauf, dass der status err wenn der Benutzer die Autorisierung abgelehnt, abgebrochen oder nicht bestanden hat.

Schnell

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow