Zoeken…


Syntaxis

  • kSecClassGenericPassword // Een waardesleutel die een niet-internetwachtwoord vertegenwoordigt
  • kSecClassInternetPassword // Een waardesleutel die een internetwachtwoord vertegenwoordigt
  • kSecClassCertificate // Een waardesleutel die een certificaat vertegenwoordigt
  • kSecClassCertificate // Een waardesleutel die een sleutel vertegenwoordigt
  • kSecClassIdentity // Een waardesleutel die een identiteit voorstelt, wat een certificaat plus een sleutel is

Opmerkingen

iOS slaat privé-informatie zoals wachtwoorden, coderingssleutels, certificaten en identiteiten op in een beveiligde opslagruimte die de Keychain wordt genoemd. Dit opslaggebied wordt volledig beheerd door een co-processor, de Secure Enclave, die is ingebed in de applicatieprocessor. Omdat de Keychain op iOS in een sandbox wordt geplaatst, kunnen sleutelhangeritems alleen worden opgehaald door de applicatie die ze daar in de eerste plaats plaatst.

In sommige gevallen moet u Keychain Sharing inschakelen in Xcode-mogelijkheden om fouten te voorkomen.

Voor interactie met de sleutelhanger gebruiken we een AC-raamwerk genaamd Keychain Services. Zie Apple's Keychain Services-programmeerhandleiding voor meer informatie.

Omdat Keychain Services lager is dan het Foundation niveau, is het beperkt tot het gebruik van CoreFoundation typen. Als gevolg hiervan worden de meeste objecten intern weergegeven als CFDictionary s die CFString s als hun sleutels houden en een verscheidenheid aan CoreFoundation typen als hun waarden.

Hoewel Keychain Services is opgenomen als onderdeel van het Security , is het importeren van Foundation meestal een goede optie, omdat het enkele helperfuncties in de backend bevat.

Als u bovendien niet direct met Keychain Services wilt werken, biedt Apple het generieke Keychain Swift-voorbeeldproject dat Swift-types biedt die Keychain Services achter de schermen gebruiken.

Een wachtwoord toevoegen aan de sleutelhanger

Elk sleutelhangeritem wordt meestal weergegeven als een CFDictionary . Je kunt echter gewoon NSDictionary in Objective-C en profiteren van bridging, of in Swift kun je Dictionary en expliciet casten naar CFDictionary .

U kunt een wachtwoord maken met het volgende woordenboek:

Snel

var dict = [String : AnyObject]()

Eerst hebt u een sleutel / waarde-paar nodig dat de Keychain laat weten dat dit een wachtwoord is. Omdat onze dict-sleutel een String , moeten we elke CFString expliciet in Swift 3 naar een String casten. CFString mag niet als sleutel voor een Swift-woordenboek worden gebruikt, omdat deze niet Hashable is.

Snel

dict[kSecClass as String] = kSecClassGenericPassword

Vervolgens kan ons wachtwoord een reeks kenmerken hebben om het te beschrijven en ons te helpen het later te vinden. Hier is een lijst met attributen voor generieke wachtwoorden .

Snel

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

Ten slotte hebben we onze eigenlijke privégegevens nodig. Zorg ervoor dat u dit niet te lang in het geheugen bewaart. Dit moet CFData .

Snel

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

Ten slotte wil de functie Keychain Services toevoegen weten hoe deze het nieuw gebouwde sleutelhangeritem moet retourneren. Omdat je de gegevens niet lang in het geheugen zou moeten vasthouden, kun je als volgt alleen de attributen retourneren:

Snel

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Nu hebben we ons artikel geconstrueerd. Laten we het toevoegen:

Snel

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

Dit plaatst de nieuwe attributen dict in het result . SecItemAdd neemt in het woordenboek dat we hebben gebouwd, evenals een pointer naar waar we ons resultaat willen. De functie retourneert vervolgens een OSStatus die op succes of een foutcode duidt. Resultaatcodes worden hier beschreven.

Een wachtwoord vinden in de sleutelhanger

Om een query te construeren, moeten we deze als een CFDictionary . Je kunt ook NSDictionary in Objective-C of Dictionary in Swift en casten naar CFDictionary .

We hebben een klassesleutel nodig:

Snel

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

Vervolgens kunnen we kenmerken opgeven om onze zoekopdracht te verfijnen:

Snel

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

We kunnen ook speciale zoekmodificatietoetsen specificeren die hier worden beschreven.

Ten slotte moeten we zeggen hoe we onze gegevens graag willen retourneren. Hieronder vragen we dat alleen het CFData zelf wordt geretourneerd als een CFData object:

Snel

dict[kSecReturnData as String] = kCFBooleanTrue

Laten we nu zoeken:

Snel

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 neemt SecItemCopyMatching een SecItemCopyMatching en een pointer in naar waar u het resultaat wilt hebben. Het retourneert een OSStatus met resultaatcodes. Hier zijn de mogelijkheden.

Een wachtwoord bijwerken in de sleutelhanger

Zoals gewoonlijk hebben we eerst een CFDictionary nodig om het item weer te geven dat we willen bijwerken. Dit moet alle oude waarden voor het item bevatten, inclusief de oude privégegevens. Vervolgens is er een CFDictionary met alle kenmerken of de gegevens zelf die u wilt wijzigen.

Laten we daarom eerst een klassesleutel en een lijst met attributen samenstellen. Deze attributen kunnen onze zoekopdracht beperken, maar u moet alle attributen en hun oude waarden opnemen als u ze wilt wijzigen.

Snel

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

Nu moeten we de oude gegevens toevoegen:

Snel

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

Laten we nu dezelfde attributen maken, maar een ander wachtwoord:

Snel

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

Nu geven we het gewoon door aan Keychain Services:

Snel

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

SecItemUpdate retourneert een statuscode. Resultaten worden hier beschreven.

Een wachtwoord verwijderen van de sleutelhanger

We hebben slechts één ding nodig om een item uit de Keychain te verwijderen: een CFDictionary met attributen die de te verwijderen items beschrijven. Alle items die overeenkomen met het querywoordenboek worden permanent verwijderd, dus als u slechts één item wilt verwijderen, moet u specifiek zijn voor uw zoekopdracht. Zoals altijd kunnen we een NSDictionary in Objective-C of in Swift kunnen we een Dictionary en vervolgens casten naar CFDictionary .

Een querywoordenboek bevat in deze context uitsluitend een klassesleutel om te beschrijven wat het item is en attributen om informatie over het item te beschrijven. Het opnemen van kSecMatchCaseInsensitive zoals kSecMatchCaseInsensitive is niet toegestaan.

Snel

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

En nu kunnen we het eenvoudig verwijderen:

Snel

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete retourneert een OSStatus . Resultaatcodes worden hier beschreven.

Sleutelhanger toevoegen, bijwerken, verwijderen en zoeken met behulp van één bestand.

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

Sleutelhanger toegangscontrole (TouchID met wachtwoord fallback)

Keychain maakt het mogelijk om items met het speciale SecAccessControl-kenmerk op te slaan, waardoor items alleen van Keychain kunnen worden opgehaald nadat de gebruiker is geverifieerd met Touch ID (of wachtwoord als dergelijke fallback is toegestaan). App krijgt alleen een melding of de authenticatie succesvol was of niet, de hele gebruikersinterface wordt beheerd door iOS.

Eerst moet het SecAccessControl-object worden gemaakt:

Snel

let error: Unmanaged<CFError>?

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

Voeg het vervolgens toe aan het woordenboek met kSecAttrAccessControl-sleutel (die wederzijds exclusief is met kSecAttrAccessible-sleutel die u in andere voorbeelden hebt gebruikt):

Snel

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

En sla het op zoals u eerder hebt gedaan:

Snel

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

Om toegang te krijgen tot opgeslagen gegevens, vraagt u Keychain gewoon om een sleutel. Keychain Services presenteert het authenticatiedialoogvenster aan de gebruiker en retourneert gegevens of nul, afhankelijk van het feit of een geschikte vingerafdruk is verstrekt of de juiste wachtwoordcode is gevonden.

Optioneel kan een promptstring worden opgegeven:

Snel

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

Let op dat de status err als de gebruiker de autorisatie heeft geweigerd, geannuleerd of is mislukt.

Snel

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow