iOS
Schlüsselbund
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")
}