Asegurando los datos de iOS en reposo cifrado

En esta publicación, veremos los usos avanzados del cifrado para datos de usuario en aplicaciones iOS. Comenzaremos con una mirada de alto nivel al cifrado AES, y luego veremos algunos ejemplos de cómo implementar el cifrado AES en Swift.

En la última publicación, aprendió cómo almacenar datos usando el llavero, lo cual es bueno para pequeños fragmentos de información como claves, contraseñas y certificados.. 

Si está almacenando una gran cantidad de datos personalizados que desea que estén disponibles solo después de que el usuario o el dispositivo se autentiquen, entonces es mejor cifrar los datos utilizando un marco de cifrado. Por ejemplo, puede tener una aplicación que puede archivar mensajes privados de chat guardados por el usuario o fotos privadas tomadas por el usuario, o que pueden almacenar los detalles financieros del usuario. En estos casos, es probable que desee utilizar cifrado.

Hay dos flujos comunes en las aplicaciones para cifrar y descifrar datos de aplicaciones iOS. O al usuario se le presenta una pantalla de contraseña o la aplicación se autentica con un servidor que devuelve una clave para descifrar los datos. 

Nunca es una buena idea reinventar la rueda cuando se trata de cifrado. Por lo tanto, vamos a utilizar el estándar AES proporcionado por la biblioteca Common Crypto de iOS.

AES

AES es un estándar que encripta los datos dada una clave. La misma clave utilizada para cifrar los datos se utiliza para descifrar los datos. Existen diferentes tamaños de clave, y AES256 (256 bits) es la longitud preferida para usarse con datos confidenciales.

RNCryptor es un contenedor de cifrado popular para iOS que admite AES. RNCryptor es una excelente opción porque lo pone en funcionamiento muy rápidamente sin tener que preocuparse por los detalles subyacentes. También es de código abierto para que los investigadores de seguridad puedan analizar y auditar el código..  

Por otro lado, si su aplicación trata con información muy confidencial y cree que su aplicación será específica y resquebrajada, es posible que desee escribir su propia solución. La razón de esto es que cuando muchas aplicaciones usan el mismo código, puede facilitar el trabajo del pirata informático, lo que les permite escribir una aplicación de cracking que encuentra patrones comunes en el código y les aplica parches.. 

Sin embargo, tenga en cuenta que escribir su propia solución solo ralentiza a un atacante y evita ataques automatizados. La protección que obtiene de su propia implementación es que un pirata informático tendrá que dedicar tiempo y dedicación a descifrar su aplicación solo. 

Ya sea que elija una solución de terceros o opte por su propia solución, es importante conocer los sistemas de encriptación. De esa manera, puede decidir si un marco particular que desea usar es realmente seguro. Por lo tanto, el resto de este tutorial se centrará en escribir su propia solución personalizada. Con el conocimiento que aprenderá de este tutorial, podrá saber si está utilizando un marco particular de forma segura. 

Comenzaremos con la creación de una clave secreta que se utilizará para cifrar sus datos..

Crear una clave

Un error muy común en el cifrado AES es usar la contraseña de un usuario directamente como clave de cifrado. ¿Qué pasa si el usuario decide usar una contraseña común o débil? ¿Cómo obligamos a los usuarios a usar una clave que sea lo suficientemente aleatoria y fuerte (tiene suficiente entropía) para el cifrado y luego hacer que la recuerden?? 

La solucion es estiramiento clave. El estiramiento de la clave deriva una clave de una contraseña mediante el hash muchas veces con una sal. La sal es solo una secuencia de datos aleatorios, y es un error común omitir esta sal: la sal le da a la clave su entropía de vital importancia, y sin la sal, la misma clave se derivaría si alguien utilizara la misma contraseña. más.

Sin la sal, se podría usar un diccionario de palabras para deducir claves comunes, que luego se podrían usar para atacar los datos del usuario. Esto se llama un "ataque del diccionario". Las tablas con claves comunes que corresponden a contraseñas sin sal se utilizan para este propósito. Se llaman "tablas del arco iris".

Otra dificultad al crear una sal es usar una función de generación de números aleatorios que no fue diseñada para la seguridad. Un ejemplo es el rand () Función en C, a la que se puede acceder desde Swift. Esta salida puede llegar a ser muy predecible.! 

Para crear una sal segura, utilizaremos la función. SecRandomCopyBytes para crear bytes aleatorios criptográficamente seguros, es decir, números que son difíciles de predecir. 

Para usar el código, deberá agregar lo siguiente a su encabezado puente:
#importar

Aquí está el comienzo del código que crea una sal. Vamos a añadir a este código a medida que avanzamos:

var salt = Datos (cuenta: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Vacío en let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) // ... 

Ahora estamos listos para hacer estiramientos clave. Afortunadamente, ya tenemos una función a nuestra disposición para hacer el estiramiento real: la función de derivación de clave basada en contraseña (PBKDF2). PBKDF2 realiza una función muchas veces para derivar la clave; aumentar el número de iteraciones aumenta el tiempo que llevaría operar un conjunto de teclas durante un ataque de fuerza bruta. Se recomienda utilizar PBKDF2 para generar su clave..

var setupSuccess = true var key = Data (repitiendo: 0, count: kCCKeySizeAES256) var salt = Data (count: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Vacío en let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) si saltStatus == errSecSuccess let passwordData = password.data (utilizando: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer) en let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm) (kCCPBKDF2), password, passwordData.count, saltBytes, salt.count  else else setupSuccess = false

Llave del lado del servidor

Es posible que ahora se esté preguntando sobre los casos en los que no desea que los usuarios proporcionen una contraseña dentro de su aplicación. Quizás ya se estén autenticando con un esquema de inicio de sesión único. En este caso, haga que su servidor genere una clave AES de 256 bits (32 bytes) usando un generador seguro. La clave debe ser diferente para diferentes usuarios o dispositivos. Al autenticarse con su servidor, puede pasar al servidor un ID de dispositivo o de usuario a través de una conexión segura, y puede devolver la clave correspondiente. 

Este esquema tiene una gran diferencia. Si la clave proviene del servidor, la entidad que controla ese servidor tiene la capacidad de poder leer los datos cifrados si el dispositivo o los datos se obtuvieron alguna vez. También existe la posibilidad de que la llave se filtre o se exponga más adelante.. 

Por otro lado, si la clave se deriva de algo que solo el usuario conoce, la contraseña del usuario, solo el usuario puede descifrar esos datos. Si está protegiendo información como datos financieros privados, solo el usuario debe poder desbloquear los datos. Si esa información es conocida por la entidad de todos modos, puede ser aceptable que el servidor desbloquee el contenido a través de una clave del lado del servidor..

Modos e IVs

Ahora que tenemos una clave, vamos a cifrar algunos datos. Existen diferentes modos de cifrado, pero usaremos el modo recomendado: cifrado de bloque de cadena (CBC). Esto opera en nuestros datos un bloque a la vez.. 

Un error común con CBC es el hecho de que cada siguiente bloque de datos sin cifrar está en XOR con el bloque encriptado anterior para fortalecer el cifrado. El problema aquí es que el primer bloque nunca es tan único como todos los demás. Si un mensaje a cifrar comenzara de la misma manera que otro mensaje a cifrar, la salida encriptada inicial sería la misma, y ​​eso le daría al atacante una pista para descubrir cuál podría ser el mensaje.. 

Para sortear esta posible debilidad, comenzaremos a guardar los datos con lo que se llama un vector de inicialización (IV): un bloque de bytes aleatorios. El IV será XOR con el primer bloque de datos del usuario, y como cada bloque depende de todos los bloques procesados ​​hasta ese momento, garantizará que todo el mensaje se cifrará de forma única, incluso si tiene los mismos datos que otro. mensaje. En otras palabras, los mensajes idénticos cifrados con la misma clave no producirán resultados idénticos. Por lo tanto, si bien las sales y las IV se consideran públicas, no deben ser secuenciales ni reutilizadas.. 

Vamos a utilizar el mismo seguro SecRandomCopyBytes función para crear el IV.

var iv = Data.init (count: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) en let ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) si ivStatus! = errSecSuccess setupSuccess = false

Poniendolo todo junto

Para completar nuestro ejemplo, usaremos el CCCrypt funcionar con cualquiera kCCEncrypt o kCCDecrypt. Debido a que estamos utilizando un cifrado de bloque, si el mensaje no encaja bien en un múltiplo del tamaño del bloque, deberemos indicar a la función que agregue automáticamente el relleno al final. 

Como es habitual en el cifrado, es mejor seguir los estándares establecidos. En este caso, el estándar PKCS7 define cómo rellenar los datos. Le decimos a nuestra función de encriptación que use este estándar suministrando el KCCOptionPKCS7Padding opción. Poniéndolo todo junto, aquí está el código completo para cifrar y descifrar una cadena.

class func encryptData (_ clearTextData: Data, withPassword password: String) -> Dictionary var setupSuccess = true var outDictionary = Dictionary.init () var key = Data (repitiendo: 0, count: kCCKeySizeAES256) var salt = Data (count: 8) salt.withUnsafeMutableBytes (saltBytes: UnsafeMutablePointer) -> Vacío en let saltStatus = SecRandomCopyBytes (kSecRandomDefault, salt.count, saltBytes) si saltStatus == errSecSuccess let passwordData = password.data (utilizando: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer) en let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm) (kCCPBKDF2), password, passwordData.count, saltBytes, salt.count  else else setupSuccess = false var iv = Data.init (count: kCCBlockSizeAES128) iv.withUnsafeMutableBytes (ivBytes: UnsafeMutablePointer) en let ivStatus = SecRandomCopyBytes (kSecRandomDefault, kCCBlockSizeAES128, ivBytes) if ivStatus! count: size key.count, ivBytes, clearTextBytes, clearTextData.count, encryptedBytes, size, y numberOfBytesEncrypted) if cryptStatus == Int32 en el que se trata el sistema de adicción a la persona mayorista (nombre de archivo) = iv outDictionary ["EncryptionSalt"] = salt return outDictionary;  

Y aquí está el código de descifrado:

class func decryp (fromDictionary dictionary: Dictionary, withPassword password: String) -> Data var setupSuccess = true let encrypted = dictionary ["EncryptionData"] let iv = dictionary ["EncryptionIV"] let salt = dictionary ["EncryptionSalt"] var key = Data (repeat: 0, count : kCCKeySizeAES256) salt? .withUnsafeBytes (saltBytes: UnsafePointer) -> Vaciar en let passwordData = password.data (usando: String.Encoding.utf8)! key.withUnsafeMutableBytes (keyBytes: UnsafeMutablePointer) en let derivationStatus = CCKeyDerivationPBKDF (CCPBKDFAlgorithm (kCCPBKDF2), password, passwordData.count, saltBytes, sal!  var decryptSuccess = false let size = (encriptado? .count)! + kCCBlockSizeAES128 var clearTextData = Data.init (count: size) B keyBytes in CCCrypt (CCOperation (kCCDecrypt), CCAlgorithm (kCCAlgorithmAES128), CCOptions (kCCOptionPKCS7) ! == Int32 (kCCSuccess) clearTextData.count = numberOfBytesDecrypted decryptSuccess = true return decryptSuccess? clearTextData: Data.init (count: 0) 

Finalmente, aquí hay una prueba para asegurar que los datos se descifran correctamente después del cifrado:

class func encryptionTest () let clearTextData = "un texto limpio para cifrar" .data (utilizando: String.Encoding.utf8)! let dictionary = encryptData (clearTextData, withPassword: "123456") let decrypted = decrypted (fromDictionary: dictionary, withPassword: "123456") let decryptedString = String (datos: desencriptados, codificación: String.Encoding.utf8) imprimir ("pulir") result - ", decryptedString ??" Error: No se pudieron convertir los datos en una cadena ")

En nuestro ejemplo, empaquetamos toda la información necesaria y la devolvemos como un Diccionario para que todas las piezas se puedan usar posteriormente para descifrar los datos con éxito. Solo necesita almacenar la IV y la sal, ya sea en el llavero o en su servidor.

Conclusión

Esto completa la serie de tres partes sobre seguridad de datos en reposo. Hemos visto cómo almacenar correctamente las contraseñas, datos confidenciales y grandes cantidades de datos de usuarios. Estas técnicas son la línea de base para proteger la información del usuario almacenada en su aplicación.

Existe un gran riesgo cuando el dispositivo de un usuario se pierde o es robado, especialmente con ataques recientes para obtener acceso a un dispositivo bloqueado. Si bien muchas vulnerabilidades del sistema están parchadas con una actualización de software, el dispositivo en sí es tan seguro como el código de acceso y la versión de iOS del usuario. Por lo tanto, depende del desarrollador de cada aplicación proporcionar una protección sólida de los datos confidenciales que se almacenan. 

Todos los temas tratados hasta ahora hacen uso de los marcos de Apple. Voy a dejar una idea con la que pensar. ¿Qué pasa cuando la biblioteca de cifrado de Apple es atacada?? 

Cuando una arquitectura de seguridad utilizada comúnmente se ve comprometida, todas las aplicaciones que dependen de ella también están comprometidas. Cualquiera de las bibliotecas vinculadas dinámicamente de iOS, especialmente en dispositivos con jailbreak, puede parchearse e intercambiarse con otros maliciosos. 

Sin embargo, una biblioteca estática que se incluye con el binario de tu aplicación está protegida contra este tipo de ataque porque si intentas parchearlo, terminas cambiando el binario de la aplicación. Esto romperá la firma del código de la aplicación, impidiendo que se inicie. Si importó y usó, por ejemplo, OpenSSL para su cifrado, su aplicación no sería vulnerable a un ataque generalizado de la API de Apple. Puede compilar OpenSSL usted mismo y enlazarlo estáticamente en su aplicación.

Por lo tanto, siempre hay más que aprender y el futuro de la seguridad de la aplicación en iOS siempre está evolucionando. ¡La arquitectura de seguridad de iOS incluso ahora admite dispositivos criptográficos y tarjetas inteligentes! Para terminar, ahora conoce las mejores prácticas para proteger los datos en reposo, por lo que depende de usted seguirlos.!

Mientras tanto, consulte algunos de nuestros otros contenidos sobre el desarrollo de aplicaciones de iOS y la seguridad de las aplicaciones..