Cualquier aplicación que guarde los datos del usuario debe cuidar la seguridad y privacidad de esos datos. Como hemos visto con las recientes violaciones de datos, puede haber consecuencias muy serias por no proteger los datos almacenados de sus usuarios. En este tutorial, aprenderá algunas de las mejores prácticas para proteger los datos de sus usuarios..
En la publicación anterior, aprendió cómo proteger archivos usando la API de protección de datos. La protección basada en archivos es una característica poderosa para el almacenamiento seguro de datos masivos. Pero podría ser una exageración el proteger una pequeña cantidad de información, como una clave o contraseña. Para este tipo de artículos, el llavero es la solución recomendada..
El llavero es un gran lugar para almacenar cantidades más pequeñas de información, como cadenas sensibles e ID que persisten incluso cuando el usuario elimina la aplicación. Un ejemplo puede ser un dispositivo o un token de sesión que su servidor devuelve a la aplicación al registrarse. Ya sea que lo llame una cadena secreta o un token único, el llavero se refiere a todos estos elementos como contraseñas.
Hay algunas bibliotecas de terceros populares para servicios de llavero, como Strongbox (Swift) y SSKeychain (Objective-C). O, si desea un control completo sobre su propio código, puede utilizar directamente la API de Keychain Services, que es una API de C.
Explicaré brevemente cómo funciona el llavero. Puede pensar en el llavero como una base de datos típica donde ejecuta consultas en una tabla. Las funciones de la API de llavero requieren una CFDiccionario
Objeto que contiene atributos de la consulta..
Cada entrada en el llavero tiene un nombre de servicio. El nombre del servicio es un identificador: a llave para lo que sea valor Desea almacenar o recuperar en el llavero. Para permitir que un elemento de llavero se almacene solo para un usuario específico, a menudo también querrá especificar un nombre de cuenta.
Debido a que cada función de llavero toma un diccionario similar con muchos de los mismos parámetros para realizar una consulta, puede evitar el código duplicado al hacer una función auxiliar que devuelve este diccionario de consulta..
Importar seguridad //… clase func passwordQuery (servicio: cadena, cuenta: cadena) -> Diccionariolet dictionary = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: cuenta, kSecAttrService as String: service, checon, checon, checon, checon, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, chi, gi, chi, chi, chi, chi, chi, g)
Este código configura la consulta. Diccionario
con sus nombres de cuenta y servicio, y le dice al llavero que almacenaremos una contraseña.
De manera similar a cómo puede establecer el nivel de protección para archivos individuales (como vimos en la publicación anterior), también puede establecer los niveles de protección para su elemento de llavero usando la kSecAttrAccessible
llave.
los SecItemAdd ()
La función agrega datos al llavero. Esta función lleva un Datos
Objeto, que lo hace versátil para almacenar muchos tipos de objetos. Usando la función de consulta de contraseña que creamos anteriormente, almacenemos una cadena en el llavero. Para ello, solo tenemos que convertir el Cuerda
a Datos
.
@discardableResult class func setPassword (_ password: String, service: String, account: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) deletePassword (service: service , cuenta: cuenta) // eliminar contraseña si pasa una cadena vacía. Podría cambiarse para pasar nil para eliminar la contraseña, etc. si! Password.isEmpty var dictionary = passwordQuery (service: service, account: account) permite dataFromString = password.data (usando: String.Encoding.utf8, allowLossyConversion: false) dictionary [ kSecValueData as String] = dataFromString status = SecItemAdd (dictionary as CFDictionary, nil) return status == errSecSuccess
Para evitar inserciones duplicadas, el código de arriba primero elimina la entrada anterior, si la hay. Vamos a escribir esa función ahora. Esto se logra usando el SecItemDelete ()
función.
@discardableResult class func deletePassword (service: String, account: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) let dictionary = passwordQuery (service: service, account : account) status = SecItemDelete (dictionary as CFDictionary); estado de retorno == errSecSuccess
A continuación, para recuperar una entrada del llavero, use el SecItemCopyMatching ()
función. Devolverá un Cualquier objeto
que coincide con su consulta.
class func password (service: String, account: String) -> String // devolver cadena vacía si no se encuentra, podría devolver un var status: OSStatus = -1 var resultString = "" if! (service.isEmpty) &&! (account.isEmpty) var passwordData: AnyObject? var dictionary = passwordQuery (service: service, account: account) dictionary [kSecReturnData as String] = kCFBooleanTrue dictionary [kSecMatchLimit as String] = kSecMatchLimitOne status = descontro en el estado de los animales / malvados / niños ¿como? Data resultString = String (data: retrievedData, encoding: String.Encoding.utf8)! devolver resultadoString
En este código, establecemos la kSecReturnData
parámetro a kCFBooleanTrue
. kSecReturnData
significa que los datos reales del artículo serán devueltos. Una opción diferente podría ser devolver los atributos (kSecReturnAttributes
) del artículo. La llave lleva un CFBoolean
tipo que contiene las constantes kCFBooleanTrue
o kCFBooleanFalse
. Estamos estableciendo kSecMatchLimit
a kSecMatchLimitOne
de modo que solo se devolverá el primer elemento encontrado en el llavero, en lugar de un número ilimitado de resultados.
El llavero también es el lugar recomendado para almacenar objetos de clave pública y privada, por ejemplo, si su aplicación funciona y necesita almacenar EC o RSA SecKey
objetos.
La principal diferencia es que, en lugar de decirle al llavero que almacene una contraseña, podemos decirle que guarde una clave. De hecho, podemos ser específicos al establecer los tipos de claves almacenadas, por ejemplo, si son públicas o privadas. Todo lo que hay que hacer es adaptar la función de ayuda de consulta para trabajar con el tipo de clave que desea.
Las claves generalmente se identifican mediante una etiqueta de dominio inverso como com.mydomain.mykey En lugar de nombres de servicio y cuenta (ya que las claves públicas se comparten abiertamente entre diferentes compañías o entidades). Tomaremos el servicio y las cadenas de cuenta y las convertiremos en una etiqueta. Datos
objeto. Por ejemplo, el código anterior adaptado para almacenar un RSA Private SecKey
se vería así:
class func keyQuery (service: String, account: String) -> Dictionarylet tagString = "com.mydomain". + servicio + "." + cuenta let tag = tagString.data (usando: .utf8)! // Almacénelo como Datos, no como una Cadena, deje dictionary = [kSecClass como Cadena: ] return dictionary @discardableResult class func setKey (_ clave: SecKey, service: String, account: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) deleteKey (service: service, account: account) var dictionary = keyQuery (service: service, account: account) dictionary [kSecValueRef as String] = key status = SecItemAdd (dictionary as CFDictionary, nil); return status == errSecSuccess @discardableResult class func deleteKey (service: String, account: String) -> Bool var status: OSStatus = -1 if! (service.isEmpty) &&! (account.isEmpty) let dictionary = keyQuery (service: service, account: account) status = SecItemDelete (dictionary como CFDictionary); estado de retorno == errSecSuccess tecla de función de clase (servicio: Cadena, cuenta: Cadena) -> SecKey? var item: CFTypeRef? if! (service.isEmpty) &&! (account.isEmpty) var dictionary = keyQuery (service: service, account: account) dictionary [kSecReturnRef as String] = kCFBooleanTrue dictionary [kSecMatchLimitOne SecItpac tcpcpcpac tcpaccpcpcpcccpcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc) &ít); devolver artículo como! SecKey?
Artículos asegurados con el kSecAttrAccessibleWhenUnlocked
El indicador solo se desbloquea cuando el dispositivo está desbloqueado, pero se basa en que el usuario tenga una contraseña o una identificación táctil configurada en primer lugar.
los solicitud de contraseña
la credencial permite que los elementos del llavero se aseguren con una contraseña adicional. De esta manera, si el usuario no tiene configurada una contraseña o un ID de toque, los elementos seguirán siendo seguros y agregarán una capa adicional de seguridad si tienen una contraseña establecida..
Como ejemplo de ejemplo, después de que su aplicación se autentique con su servidor, su servidor podría devolver la contraseña a través de HTTPS que se requiere para desbloquear el elemento del llavero. Esta es la forma preferida de proporcionar esa contraseña adicional. No se recomienda codificar una contraseña en el binario.
Otro escenario podría ser recuperar la contraseña adicional de una contraseña provista por el usuario en su aplicación; sin embargo, esto requiere más trabajo para asegurar adecuadamente (utilizando PBKDF2). Veremos cómo proteger las contraseñas proporcionadas por el usuario en el siguiente tutorial..
Otro uso de la contraseña de la aplicación es para almacenar una clave confidencial, por ejemplo, una que no querría estar expuesta solo porque el usuario aún no haya configurado un código de acceso.
solicitud de contraseña
solo está disponible en iOS 9 y superior, por lo que necesitará un respaldo que no use solicitud de contraseña
Si está apuntando a versiones más bajas de iOS. Para usar el código, deberá agregar lo siguiente a su encabezado puente:
#importar#importar
El siguiente código establece una contraseña para la consulta Diccionario
.
if #available (iOS 9.0, *) // Use esto en lugar de kSecAttrAccessible para la consulta var error: Unmanaged? dejar que AccessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, SecAccessControlCreateFlags.applicationPassword, y error) si AccessControl = nil diccionario [kSecAttrAccessControl como String] = AccessControl dejar que localAuthenticationContext = LAContext.init () dejar que theApplicationPassword = .data "passwordFromServer" (usando: Cadena .Codificación.utf8)! diccionario localAuthenticationContext.setCredential (theApplicationPassword, escriba: LACredentialType.applicationPassword) [kSecUseAuthenticationContext as String] = localAuthenticationContext
Tenga en cuenta que nos propusimos kSecAttrAccessControl
sobre el Diccionario
. Esto se usa en lugar de kSecAttrAccessible
, que se estableció previamente en nuestra contraseñaQuery
método. Si intentas usar ambos, obtendrás un OSStatus
-50
error.
A partir de iOS 8, puede almacenar datos en el llavero al que solo se puede acceder después de que el usuario se autentique correctamente en el dispositivo con Touch ID o un código de acceso. Cuando sea el momento de que el usuario se autentique, Touch ID tendrá prioridad si se configura, de lo contrario se presenta la pantalla de código de acceso. Guardar el llavero no requerirá que el usuario se autentique, pero la recuperación de los datos.
Puede configurar un elemento de llavero para requerir la autenticación del usuario al proporcionar un conjunto de objetos de control de acceso para .usuarioPresencia
. Si no se configura una contraseña, cualquier solicitud de llavero con .usuarioPresencia
fallará.
if #available (iOS 8.0, *) let accessControl = SecAccessControlCreateWithFlags (kCFAllocatorDefault, kSecAttrAccessible) .En este caso, este no es un tipo de valor.
Esta función es buena cuando quiere asegurarse de que su aplicación está siendo utilizada por la persona correcta. Por ejemplo, sería importante que el usuario se autentique antes de poder iniciar sesión en una aplicación bancaria. Esto protegerá a los usuarios que hayan dejado su dispositivo desbloqueado, para que no se pueda acceder a la banca.
Además, si no tiene un componente del lado del servidor para su aplicación, puede usar esta función para realizar la autenticación del lado del dispositivo.
Para la consulta de carga, puede proporcionar una descripción de por qué el usuario necesita autenticarse.
diccionario [kSecUseOperationPrompt as String] = "Autenticar para recuperar x"
Al recuperar los datos con SecItemCopyMatching ()
, La función mostrará la IU de autenticación y esperará a que el usuario use la identificación táctil o ingrese el código de acceso. Ya que SecItemCopyMatching ()
bloqueará hasta que el usuario haya finalizado la autenticación, deberá llamar a la función desde un subproceso en segundo plano para permitir que el subproceso de la interfaz de usuario principal se mantenga receptivo.
DispatchQueue.global (). Async status = SecItemCopyMatching (diccionario como CFDictionary, y passwordData) si status == errSecSuccess si se deja retrievedData = passwordData as? Datos DispatchQueue.main.async // ... hacer el resto del trabajo en el hilo principal
Una vez más, estamos estableciendo kSecAttrAccessControl
en la consulta Diccionario
. Tendrá que eliminar kSecAttrAccessible
, que se estableció previamente en nuestra contraseñaQuery
método. El uso de ambos a la vez dará lugar a una OSStatus
-50 error.
En este artículo, has realizado un recorrido por la API de Keychain Services. Junto con la API de protección de datos que vimos en la publicación anterior, el uso de esta biblioteca forma parte de las mejores prácticas para proteger datos.
Sin embargo, si el usuario no tiene un código de acceso o ID de toque en el dispositivo, no hay cifrado para ninguno de los marcos. Debido a que las aplicaciones de iOS suelen utilizar los Servicios de llavero y las API de protección de datos, a veces son atacadas por atacantes, especialmente en dispositivos con jailbreak. Si su aplicación no funciona con información altamente confidencial, esto puede ser un riesgo aceptable. Si bien iOS está actualizando constantemente la seguridad de los marcos, todavía estamos a merced de que el usuario actualice el sistema operativo, utilice un código de acceso seguro y no jailbreak a su dispositivo.
El llavero está diseñado para datos más pequeños y es posible que tenga una mayor cantidad de datos para asegurar que sea independiente de la autenticación del dispositivo. Si bien las actualizaciones de iOS agregan algunas funciones nuevas e increíbles, como la contraseña de la aplicación, es posible que aún sea necesario admitir versiones de iOS más bajas y tener una seguridad sólida. Por alguna de estas razones, es posible que desee cifrar los datos usted mismo.
El artículo final de esta serie trata sobre el cifrado de los datos utilizando el cifrado AES, y aunque es un enfoque más avanzado, le permite tener un control total sobre cómo y cuándo se cifran sus datos..
Así que estad atentos. Y mientras tanto, echa un vistazo a algunas de nuestras otras publicaciones sobre el desarrollo de aplicaciones para iOS!