Ricerca…


Sintassi

  • kSecClassGenericPassword // Una chiave di valore che rappresenta una password non Internet
  • kSecClassInternetPassword // Una chiave di valore che rappresenta una password internet
  • kSecClassCertificate // Una chiave di valore che rappresenta un certificato
  • kSecClassCertificate // Una chiave di valore che rappresenta una chiave
  • kSecClassIdentity // Una chiave di valore che rappresenta un'identità, che è un certificato più una chiave

Osservazioni

iOS memorizza informazioni private come password, chiavi di crittografia, certificati e identità in un'area di archiviazione protetta chiamata Keychain. Questa area di memoria è gestita completamente da un co-processore chiamato Secure Enclave, che è incorporato all'interno del processore dell'applicazione. Poiché il portachiavi è in modalità sandbox su iOS, gli elementi portachiavi possono essere recuperati solo dall'applicazione che li ha messi lì in primo luogo.

In alcuni casi è necessario attivare Condivisione dei portachiavi nelle funzionalità Xcode per evitare errori.

Per interagire con il portachiavi, utilizziamo il framework ac chiamato Keychain Services. Per ulteriori informazioni, consulta la Guida alla programmazione dei servizi Keychain di Apple .

Poiché i servizi portachiavi sono al di sotto del livello Foundation , è limitato all'utilizzo di tipi CoreFoundation . Di conseguenza, la maggior parte degli oggetti sono internamente rappresentati come CFDictionary che CFString s come le loro chiavi e una varietà di tipi CoreFoundation come i loro valori.

Mentre i Keychain Services sono inclusi come parte del framework Security , l'importazione di Foundation è di solito una buona opzione poiché include alcune funzioni di supporto nel back-end.

Inoltre, se non vuoi gestire direttamente i Keychain Services, Apple fornisce il progetto di esempio Generic Keychain Swift che fornisce i tipi Swift che utilizzano i servizi Keychain dietro le quinte.

Aggiunta di una password al portachiavi

Ogni elemento portachiavi è spesso rappresentato come un CFDictionary . Tuttavia, è possibile utilizzare semplicemente NSDictionary in Objective-C e sfruttare il bridging, oppure in Swift è possibile utilizzare Dictionary e CFDictionary espressamente a CFDictionary .

È possibile costruire una password con il seguente dizionario:

veloce

var dict = [String : AnyObject]()

Innanzitutto, è necessaria una coppia chiave / valore che permetta al Portachiavi di sapere che questa è una password. Si noti che poiché la nostra chiave dict è una String è necessario CFString qualsiasi CFString a una String esplicito in Swift 3. CFString non può essere utilizzato come chiave per un dizionario Swift perché non è selezionabile.

veloce

dict[kSecClass as String] = kSecClassGenericPassword

Successivamente, la nostra password potrebbe avere una serie di attributi per descriverla e aiutarci a trovarla in seguito. Ecco una lista di attributi per le password generiche .

veloce

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

Infine, abbiamo bisogno dei nostri dati privati ​​reali. Assicurati di non tenerlo in memoria per troppo tempo. Questo deve essere CFData .

veloce

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

Infine, la funzione di aggiunta dei servizi Keychain vuole sapere come deve restituire l'elemento portachiavi di nuova costruzione. Dal momento che non dovresti tenere i dati molto a lungo nella memoria, ecco come puoi restituire gli attributi:

veloce

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Ora abbiamo costruito il nostro oggetto. Aggiungiamolo:

veloce

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

Questo pone i nuovi attributi dettati all'interno del result . SecItemAdd prende il dizionario che abbiamo costruito e un puntatore a dove vorremmo il nostro risultato. La funzione restituisce quindi un OSStatus indica il successo o un codice di errore. I codici dei risultati sono descritti qui .

Trovare una password nel portachiavi

Per costruire una query, dobbiamo rappresentarla come un CFDictionary . Puoi anche usare NSDictionary in Objective-C o Dictionary in Swift e CFDictionary a CFDictionary .

Abbiamo bisogno di una chiave di classe:

veloce

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

Successivamente, possiamo specificare gli attributi per restringere la nostra ricerca:

veloce

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

Possiamo anche specificare i tasti di modifica di ricerca speciali qui descritti.

Infine, dobbiamo dire come vorremmo che i nostri dati fossero restituiti. Di seguito, ti chiederemo di restituire solo la password privata come oggetto CFData :

veloce

dict[kSecReturnData as String] = kCFBooleanTrue

Ora, cerchiamo:

veloce

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

Qui, SecItemCopyMatching un dizionario di query e un puntatore a cui desideri ottenere il risultato. Restituisce un OSStatus con un codice di risultato. Ecco le possibilità.

Aggiornamento di una password nel portachiavi

Come al solito, abbiamo prima bisogno di un CFDictionary per rappresentare l'elemento che vogliamo aggiornare. Questo deve contenere tutti i vecchi valori per l'elemento, inclusi i vecchi dati privati. Quindi richiede un CFDictionary di qualsiasi attributo o dei dati stessi che si desidera modificare.

Quindi, per prima cosa, costruiamo una chiave di classe e una lista di attributi. Questi attributi possono restringere la nostra ricerca ma devi includere tutti gli attributi e i vecchi valori se li cambierai.

veloce

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

Ora dobbiamo aggiungere i vecchi dati:

veloce

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

Ora creiamo gli stessi attributi ma una password diversa:

veloce

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

Ora, passiamo semplicemente ai servizi portachiavi:

veloce

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

SecItemUpdate restituisce un codice di stato. I risultati sono descritti qui .

Rimozione di una password dal portachiavi

Abbiamo bisogno solo di una cosa per eliminare un oggetto dal Portachiavi: un CFDictionary con attributi che descrivono gli elementi da eliminare. Tutti gli elementi che corrispondono al dizionario di query verranno eliminati in modo permanente, quindi se si intende eliminare un singolo elemento assicurarsi di essere specifici con la query. Come sempre, possiamo usare un NSDictionary in Objective-C o in Swift possiamo usare un Dictionary e quindi CFDictionary a CFDictionary .

Un dizionario di query, in questo contesto include esclusivamente una chiave di classe per descrivere l'elemento e gli attributi per descrivere le informazioni sull'elemento. L'inclusione di restrizioni di ricerca come kSecMatchCaseInsensitive non è consentita.

veloce

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

E ora possiamo semplicemente rimuoverlo:

veloce

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete restituisce un OSStatus . I codici dei risultati sono descritti qui .

Portachiavi Aggiungi, Aggiorna, Rimuovi e Trova operazioni utilizzando un solo file.

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

Controllo accesso portachiavi (TouchID con fallback password)

Il portachiavi consente di salvare gli articoli con l'attributo speciale SecAccessControl che consente di ottenere l'elemento dal Portachiavi solo dopo che l'utente sarà autenticato con Touch ID (o passcode se tale fallback è consentito). L'app viene notificata solo se l'autenticazione ha avuto successo o meno, l'intera UI è gestita da iOS.

In primo luogo, l'oggetto SecAccessControl deve essere creato:

veloce

let error: Unmanaged<CFError>?

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

Successivamente, aggiungilo al dizionario con la chiave kSecAttrAccessControl (che si esclude a vicenda con la chiave kSecAttrAccessible che hai utilizzato in altri esempi):

veloce

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

E salvalo come hai fatto prima:

veloce

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

Per accedere ai dati memorizzati, basta interrogare Keychain per una chiave. I servizi portachiavi presenteranno la finestra di autenticazione all'utente e restituiranno i dati o nil a seconda che sia stata fornita un'appropriata impronta digitale o che sia stato fornito il codice di accesso.

Facoltativamente, è possibile specificare una stringa di richiesta:

veloce

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

Fai attenzione che lo status sarà err se l'utente rifiuta, ha cancellato o ha fallito l'autorizzazione.

veloce

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow