Buscar..


Sintaxis

  • kSecClassGenericPassword // Una clave de valor que representa una contraseña que no es de Internet
  • kSecClassInternetPassword // Una clave de valor que representa una contraseña de Internet
  • kSecClassCertificate // Una clave de valor que representa un certificado
  • kSecClassCertificate // Una clave de valor que representa una clave
  • kSecClassIdentity // Una clave de valor que representa una identidad, que es un certificado más una clave

Observaciones

iOS almacena información privada como contraseñas, claves de cifrado, certificados e identidades en un área de almacenamiento seguro llamada Llavero. Esta área de almacenamiento es administrada completamente por un co-procesador llamado Secure Enclave, que está integrado dentro del procesador de la aplicación. Debido a que el llavero se encuentra en un espacio aislado en iOS, los elementos del llavero solo pueden ser recuperados por la aplicación que los colocó allí en primer lugar.

En algunos casos, debe activar el uso compartido de llaves en Xcode para evitar errores.

Para interactuar con el llavero, usamos un marco de CA llamado Servicios de llavero. Para obtener más información, consulte la Guía de programación de servicios de llavero de Apple .

Debido a que Keychain Services está por debajo del nivel Foundation , está restringido al uso de los tipos CoreFoundation . Como resultado, la mayoría de los objetos se representan internamente como CFDictionary s con CFString s como sus claves y una variedad de tipos de CoreFoundation como sus valores.

Mientras que Keychain Services se incluye como parte del marco de Security , la importación de Foundation suele ser una buena opción, ya que incluye algunas funciones de ayuda en el backend.

Además, si no desea tratar directamente con Keychain Services, Apple ofrece el proyecto de muestra Swift de Keychain genérico que proporciona tipos Swift que usan los servicios de Keychain entre bastidores.

Agregando una contraseña al llavero

Cada elemento de llavero se representa con mayor frecuencia como un CFDictionary . Sin embargo, puede simplemente usar NSDictionary en Objective-C y aprovechar el puenteo, o en Swift puede usar Dictionary y convertir explícitamente a CFDictionary .

Podrías construir una contraseña con el siguiente diccionario:

Rápido

var dict = [String : AnyObject]()

Primero, necesita un par de clave / valor que le permita al llavero saber que esta es una contraseña. Tenga en cuenta que dado que nuestra clave dict es una String , debemos convertir cualquier CFString a una String explícitamente en Swift 3. CFString no se puede usar como la clave de un Diccionario Swift porque no es Hashable.

Rápido

dict[kSecClass as String] = kSecClassGenericPassword

A continuación, nuestra contraseña puede tener una serie de atributos para describirla y ayudarnos a encontrarla más adelante. Aquí hay una lista de atributos para contraseñas genéricas .

Rápido

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

Por último, necesitamos nuestros datos privados reales. Asegúrese de no mantener esto en la memoria por mucho tiempo. Esto debe ser CFData .

Rápido

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

Finalmente, la función de agregar Servicios de llavero quiere saber cómo debe devolver el elemento de llavero recién construido. Dado que no debe conservar los datos mucho tiempo en la memoria, aquí le explicamos cómo solo podría devolver los atributos:

Rápido

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Ahora hemos construido nuestro artículo. Vamos a añadirlo:

Rápido

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

Esto coloca los nuevos atributos dictados dentro del result . SecItemAdd el diccionario que construimos, así como un puntero hacia donde nos gustaría obtener nuestro resultado. La función luego devuelve un OSStatus indica éxito o un código de error. Los códigos de resultados se describen aquí .

Encontrar una contraseña en el llavero

Para construir una consulta, necesitamos representarla como un CFDictionary . También puede usar NSDictionary en Objective-C o Dictionary en Swift y convertir a CFDictionary .

Necesitamos una clave de clase:

Rápido

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

A continuación, podemos especificar atributos para limitar nuestra búsqueda:

Rápido

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

También podemos especificar claves modificadoras de búsqueda especiales descritas aquí .

Finalmente, necesitamos decir cómo nos gustaría que nos devolvieran nuestros datos. A continuación, solicitaremos que solo la contraseña privada sea devuelta como un objeto CFData :

Rápido

dict[kSecReturnData as String] = kCFBooleanTrue

Ahora, busquemos:

Rápido

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

Aquí, SecItemCopyMatching un diccionario de consulta y un puntero hacia donde desea que vaya el resultado. Devuelve un OSStatus con un código de resultado. Aquí están las posibilidades.

Actualizando una contraseña en el llavero

Como de costumbre, primero necesitamos un CFDictionary para representar el elemento que queremos actualizar. Esto debe contener todos los valores antiguos para el artículo, incluidos los datos privados antiguos. Luego toma un CFDictionary de cualquier atributo o los datos en sí que le gustaría cambiar.

Así que primero, vamos a construir una clave de clase y una lista de atributos. Estos atributos pueden limitar nuestra búsqueda, pero debe incluir los atributos y los valores antiguos si los va a cambiar.

Rápido

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

Ahora debemos agregar los datos antiguos:

Rápido

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

Ahora vamos a crear los mismos atributos pero una contraseña diferente:

Rápido

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

Ahora, solo lo pasamos a Keychain Services:

Rápido

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

SecItemUpdate devuelve un código de estado. Los resultados se describen aquí .

Eliminar una contraseña del llavero

Solo necesitamos una cosa para eliminar un elemento del llavero: un CFDictionary con atributos que describen los elementos que se eliminarán. Cualquier elemento que coincida con el diccionario de consulta se eliminará de forma permanente, por lo que si solo tiene la intención de eliminar un solo elemento, asegúrese de ser específico con su consulta. Como siempre, podemos usar un NSDictionary en Objective-C o en Swift podemos usar un Dictionary y luego convertirlo en CFDictionary .

Un diccionario de consultas, en este contexto, incluye exclusivamente una clave de clase para describir qué es el elemento y los atributos para describir información sobre el elemento. No se permite la inclusión de restricciones de búsqueda como kSecMatchCaseInsensitive .

Rápido

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

Y ahora podemos simplemente eliminarlo:

Rápido

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete devuelve un OSStatus . Los códigos de resultados se describen aquí .

Llavero Añadir, actualizar, eliminar y buscar operaciones utilizando un archivo.

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

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

Llavero de control de acceso (TouchID con contraseña de retorno)

Keychain permite guardar elementos con el atributo SecAccessControl especial que permitirá obtener elementos de Keychain solo después de que el usuario sea autenticado con Touch ID (o código de acceso si se permite dicha reserva). Solo se notifica a la aplicación si la autenticación fue exitosa o no, la IU completa es administrada por iOS.

Primero, se debe crear el objeto SecAccessControl:

Rápido

let error: Unmanaged<CFError>?

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

A continuación, agréguelo al diccionario con la clave kSecAttrAccessControl (que se excluye mutuamente con la clave kSecAttrAccessible que ha estado usando en otros ejemplos):

Rápido

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

Y guárdalo como lo has hecho antes:

Rápido

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

Para acceder a los datos almacenados, solo consulte Keychain para obtener una clave. Keychain Services presentará un cuadro de diálogo de autenticación al usuario y devolverá los datos, o nada, dependiendo de si se proporcionó una huella dactilar adecuada o si se hizo coincidir el código de acceso.

Opcionalmente, se puede especificar una cadena de solicitud:

Rápido

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

Preste atención a que el status será err si el usuario rechazó, canceló o falló la autorización.

Rápido

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow