Sök…


Syntax

  • kSecClassGenericPassword // En värdnyckel som representerar ett lösenord som inte är internet
  • kSecClassInternetPassword // En värdeknapp som representerar ett internetlösenord
  • kSecClassCertificate // En värdnyckel som representerar ett certifikat
  • kSecClassCertificate // En värdeknapp som representerar en nyckel
  • kSecClassIdentity // En värdnyckel som representerar en identitet, som är ett certifikat plus en nyckel

Anmärkningar

iOS lagrar privat information som lösenord, krypteringsnycklar, certifikat och identiteter i ett säkert lagringsområde som kallas Nyckelring. Detta lagringsområde hanteras fullständigt av en co-processor som kallas Secure Enclave, som är inbäddad i applikationsprocessorn. Eftersom Nyckelringen är sandlådad på iOS kan nyckelringsprodukter bara hämtas av applikationen som placerar dem där i första hand.

I vissa fall måste du aktivera delning av nyckelring i Xcode-funktioner för att undvika fel.

För att interagera med nyckelringen använder vi ett ac-ramverk som heter Keychain Services. Mer information finns i Apples programguide för Keychain Services .

Eftersom Keychain Services är under Foundation nivån är det begränsat till att använda CoreFoundation typer. Som ett resultat är de flesta objekt internt representerade som CFDictionary håller CFString som sina nycklar och olika CoreFoundation typer som sina värden.

Medan Keychain Services ingår som en del av Security är import av Foundation vanligtvis ett bra alternativ eftersom det innehåller vissa hjälpfunktioner i backend.

Om du inte vill hantera Keychain Services direkt, tillhandahåller Apple det generella exempelprojektet för Keychain Swift som tillhandahåller Swift-typer som använder Keychain Services bakom kulisserna.

Lägga till ett lösenord i nyckelringen

Varje nyckelringsprodukt representeras oftast som en CFDictionary . Du kan dock helt enkelt använda NSDictionary i NSDictionary -C och dra nytta av överbryggning, eller i Swift kan du använda Dictionary och uttryckligen CFDictionary till CFDictionary .

Du kan skapa ett lösenord med följande ordlista:

Snabb

var dict = [String : AnyObject]()

Först behöver du ett nyckel- / värdepar som låter nyckelringen veta att det här är ett lösenord. Observera att eftersom vår dict-nyckel är en String måste vi kasta någon CFString till en String uttryckligen i Swift 3. CFString kanske inte kan användas som nyckel till en Swift Dictionary eftersom den inte är Hashable.

Snabb

dict[kSecClass as String] = kSecClassGenericPassword

Därefter kan vårt lösenord ha en serie attribut för att beskriva det och hjälpa oss att hitta det senare. Här är en lista med attribut för generiska lösenord .

Snabb

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

Slutligen behöver vi våra faktiska privata uppgifter. Se till att inte hålla det här i minnet för länge. Detta måste vara CFData .

Snabb

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

Slutligen vill funktionen Lägg till nyckelringstjänster veta hur den ska returnera den nybyggda nyckelringsposten. Eftersom du inte borde hålla fast vid uppgifterna så länge i minnet, så här kan du bara returnera attributen:

Snabb

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Nu har vi konstruerat vårt föremål. Låt oss lägga till det:

Snabb

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

Detta lägger de nya attributen in i result . SecItemAdd tar in den ordlistan vi konstruerade, samt en pekare där vi vill ha vårt resultat. Funktionen returnerar sedan en OSStatus indikerar framgång eller en felkod. Resultatkoder beskrivs här .

Hitta ett lösenord i nyckelringen

För att konstruera en fråga måste vi representera den som en CFDictionary . Du kan också använda NSDictionary i NSDictionary -C eller Dictionary i Swift och CFDictionary till CFDictionary .

Vi behöver en klassnyckel:

Snabb

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

Därefter kan vi ange attribut för att begränsa vår sökning:

Snabb

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

Vi kan också specificera speciella sökmodifieringsnycklar som beskrivs här .

Slutligen måste vi säga hur vi vill att våra data ska returneras. Nedan kommer vi att begära att bara det privata lösenordet returneras som ett CFData objekt:

Snabb

dict[kSecReturnData as String] = kCFBooleanTrue

Nu ska vi söka:

Snabb

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

Här tar SecItemCopyMatching in en frågeförordlista och en pekare dit du vill att resultatet ska gå. Det returnerar en OSStatus med en resultatkod. Här är möjligheterna.

Uppdatera ett lösenord i nyckelringen

Som vanligt behöver vi först en CFDictionary att representera det objekt vi vill uppdatera. Detta måste innehålla alla gamla värden för objektet, inklusive gamla privata data. Sedan krävs det en CFDictionary av alla attribut eller själva data som du vill ändra.

Så först ska vi konstruera en klassnyckel och en lista med attribut. Dessa attribut kan begränsa vår sökning men du måste inkludera alla attribut och där gamla värden om du kommer att ändra dem.

Snabb

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 måste vi lägga till gamla data:

Snabb

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

Låt oss nu skapa samma attribut men ett annat lösenord:

Snabb

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 skickar vi det bara till Keychain Services:

Snabb

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

SecItemUpdate returnerar en statuskod. Resultaten beskrivs här .

Ta bort ett lösenord från nyckelringen

Vi behöver bara en sak för att radera ett objekt från nyckelringen: en CFDictionary med attribut som beskriver objekten som ska tas bort. Alla objekt som matchar frågeformuläret kommer att raderas permanent, så om du bara tänker ta bort ett enda objekt måste du vara specifik med din fråga. Som alltid kan vi använda en NSDictionary i NSDictionary -C eller i Swift kan vi använda en Dictionary och sedan CFDictionary till CFDictionary .

En frågeformulär innehåller i detta sammanhang uteslutande en klassnyckel för att beskriva vad objektet är och attribut för att beskriva information om objektet. Inkludering av kSecMatchCaseInsensitive som kSecMatchCaseInsensitive är inte tillåtet.

Snabb

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

Och nu kan vi helt enkelt ta bort det:

Snabb

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete returnerar ett OSStatus . Resultatkoder beskrivs här .

Nyckelring Lägg till, uppdatera, ta bort och hitta operationer med en fil.

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

Nyckelringskontroll (TouchID med lösenordsfall)

Nyckelring gör det möjligt att spara objekt med speciellt SecAccessControl-attribut som gör det möjligt att hämta objekt från nyckelring endast efter att användaren kommer att verifieras med Touch ID (eller lösenord om sådant fallback är tillåtet). App meddelas endast om autentiseringen var framgångsrik eller inte, hela UI hanteras av iOS.

Först ska SecAccessControl-objekt skapas:

Snabb

let error: Unmanaged<CFError>?

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

Lägg sedan till den i ordboken med kSecAttrAccessControl-nyckel (som är ömsesidigt exklusivt med kSecAttrAccessible-nyckel som du har använt i andra exempel):

Snabb

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

Och spara det som du gjort tidigare:

Snabb

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

För att få åtkomst till lagrade data, fråga bara nyckelring för en nyckel. Keychain Services kommer att presentera autentiseringsdialogrutan för användaren och returnera data eller noll beroende på om lämpligt fingeravtryck tillhandahölls eller lösenordet matchades.

Valfritt kan snabbsträng specificeras:

Snabb

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

Var uppmärksam på att status kommer att bli err om användaren avböjde, annullerade eller misslyckades behörighet.

Snabb

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow