Advertisement
  1. Code
  2. iOS SDK

Asegurar los datos de iOS en reposo: Cifrado

by
Read Time:12 minsLanguages:
This post is part of a series called Securing iOS Data at Rest.
Securing iOS Data at Rest: The Keychain

Spanish (Español) translation by Esther (you can also view the original English article)

En este post, veremos los usos avanzados del cifrado de los datos de los usuarios en las aplicaciones de iOS. Comenzaremos con una mirada de alto nivel sobre el cifrado AES, y luego pasaremos a ver algunos ejemplos de cómo implementar el cifrado AES en Swift.

En el último post, aprendiste a almacenar datos usando el llavero, que es bueno para pequeñas piezas de información como claves, contraseñas y certificados.

Si estás almacenando una gran cantidad de datos personalizados que quieres que estén disponibles solo después de que el usuario o el dispositivo se autentique, entonces es mejor encriptar los datos usando un marco de encriptación. Por ejemplo, puedes tener una aplicación que pueda archivar mensajes de chat privados guardados por el usuario o fotos privadas tomadas por el usuario, o que pueda almacenar los detalles financieros del usuario. En estos casos, es probable que quieras utilizar la encriptación.

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

Nunca es buena idea reinventar la rueda cuando se trata de encriptación. 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 datos dada una clave. La misma clave utilizada para cifrar los datos se utiliza para descifrarlos. Hay diferentes tamaños de clave, y AES256 (256 bits) es la longitud preferida para ser utilizada con datos sensibles.

RNCryptor es una popular envoltura de cifrado para iOS que soporta AES. RNCryptor es una gran opción porque te pone en marcha muy rápidamente sin tener que preocuparte 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 tu aplicación trata con información muy sensible y crees que tu aplicación será objeto de ataques y crackeos, es posible que quieras escribir tu propia solución. La razón es que cuando muchas aplicaciones utilizan el mismo código, esto puede facilitar el trabajo del hacker, permitiéndole escribir una aplicación de cracking que encuentre patrones comunes en el código y les aplique parches.

Sin embargo, recuerda que escribir tu propia solución solo retrasa a un atacante y evita los ataques automatizados. La protección que obtienes de tu propia implementación es que un hacker tendrá que gastar tiempo y dedicación en crackear tu aplicación solamente.

Tanto si eliges una solución de terceros como si optas por desarrollar la tuya propia, es importante que conozcas el funcionamiento de los sistemas de encriptación. De este modo, puedes decidir si un marco concreto que quieres utilizar es realmente seguro. Por lo tanto, el resto de este tutorial se centrará en escribir tu propia solución personalizada. Con los conocimientos que aprenderás en este tutorial, podrás saber si estás utilizando un determinado framework de forma segura.

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

Crea una llave

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

La solución es el estiramiento de la llave. El estiramiento de claves deriva una clave a partir de una contraseña mediante el hash de la misma 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, que es de vital importancia, y sin la sal, se obtendría la misma clave si otra persona utilizara la misma contraseña.

Sin la sal, se podría utilizar un diccionario de palabras para deducir claves comunes, que luego podrían utilizarse para atacar los datos del usuario. Esto se llama "ataque de diccionario". Para ello, se utilizan tablas con claves comunes que corresponden a contraseñas sin saltear. Se llaman "mesas arco iris".

Otro escollo a la hora de crear una sal es utilizar una función generadora de números aleatorios que no haya sido diseñada para la seguridad. Un ejemplo es la función rand() en C, a la que se puede acceder desde Swift. ¡Esta salida puede acabar siendo muy predecible!

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

Para utilizar el código, tendrás que añadir lo siguiente en tu cabecera de puente:#import <CommonCrypto/CommonCrypto.h>

Aquí está el inicio del código que crea una sal. Iremos ampliando este código a medida que avancemos:

Ahora estamos listos para hacer los 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 amplía el tiempo que se tardaría en operar sobre un conjunto de claves durante un ataque de fuerza bruta. Te recomiendo utilizar PBKDF2 para generar tu clave.

Clave del lado del servidor

Es posible que te preguntes ahora sobre los casos en los que no quieres exigir a los usuarios que proporcionen una contraseña dentro de tu aplicación. Tal vez ya se estén autenticando con un esquema de inicio de sesión único. En este caso, haz que tu servidor genere una clave AES de 256 bits (32 bytes) utilizando un generador seguro. La clave debe ser diferente para los distintos usuarios o dispositivos. Al autentificarte con tu servidor, puedes pasarle un ID de dispositivo o de usuario a través de una conexión segura, y éste puede enviar la clave correspondiente de vuelta.

Este esquema tiene una diferencia importante. Si la clave proviene del servidor, la entidad que controla ese servidor tiene la capacidad de poder leer los datos encriptados si alguna vez se obtuviera el dispositivo o los datos. También existe la posibilidad de que la clave se filtre o se exponga en un momento posterior.

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

Modos y IVs

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

Un escollo común con CBC es el hecho de que cada bloque de datos sin cifrar siguiente es XOR con el bloque cifrado anterior para hacer el cifrado más fuerte. El problema aquí es que el primer bloque nunca es tan único como los demás. Si un mensaje a encriptar empezara igual que otro mensaje a encriptar, la salida encriptada inicial sería la misma, y eso daría a un atacante una pista para averiguar cuál podría ser el mensaje.

Para sortear esta potencial debilidad, comenzaremos los datos a guardar 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 dado que cada bloque depende de todos los bloques procesados hasta ese momento, garantizará que todo el mensaje será encriptado de forma única, incluso si tiene los mismos datos que otro mensaje. En otras palabras, mensajes idénticos encriptados con la misma clave no producirán resultados idénticos. Por lo tanto, aunque las sales e intravenosas se consideran públicas, no deben ser secuenciales ni reutilizadas.

Usaremos la misma función segura SecRandomCopyBytes para crear el IV.

Ponerlo todo en común

Para completar nuestro ejemplo, utilizaremos la función CCCrypt con kCCEncrypt o kCCDecrypt. Como estamos utilizando un cifrado por bloques, si el mensaje no cabe bien en un múltiplo del tamaño del bloque, tendremos que decirle a la función que añada automáticamente relleno al final.

Como es habitual en el cifrado, lo mejor es seguir las normas establecidas. En este caso, el estándar PKCS7 define cómo rellenar los datos. Le decimos a nuestra función de encriptación que utilice este estándar suministrando la opción KCCOptionPKCS7Padding. Poniendo todo junto, aquí está el código completo para cifrar y descifrar una cadena.

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

Por último, he aquí una prueba para asegurarse de que los datos se descifran correctamente después del cifrado:

En nuestro ejemplo, empaquetamos toda la información necesaria y la devolvemos como un diccionario para que todas las piezas puedan ser utilizadas posteriormente para desencriptar los datos con éxito. Solo necesitas almacenar el IV y la sal, ya sea en el llavero o en tu servidor.

Conclusión

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

Es un riesgo enorme cuando se pierde o se roba el dispositivo de un usuario, especialmente con los recientes exploits para acceder a un dispositivo bloqueado. Aunque muchas de las vulnerabilidades del sistema se solucionan con una actualización de software, el dispositivo en sí sólo es tan seguro como el código de acceso del usuario y la versión de iOS. Por lo tanto, depende del desarrollador de cada aplicación proporcionar una fuerte protección de los datos sensibles que se almacenan.

Todos los temas tratados hasta ahora hacen uso de los frameworks de Apple. Les dejo una idea para que la piensen. ¿Qué sucede cuando la biblioteca de cifrado de Apple es atacada?

Cuando una arquitectura de seguridad comúnmente utilizada se ve comprometida, todas las aplicaciones que dependen de ella también lo están. Cualquiera de las bibliotecas enlazadas dinámicamente de iOS, especialmente en los dispositivos con jailbreak, puede ser parcheada y cambiada por otras maliciosas.

Sin embargo, una biblioteca estática que se incluye en el binario de tu aplicación está protegida de este tipo de ataques porque si intentas parchearla, acabas cambiando el binario de la aplicación. Esto romperá la firma de código de la aplicación, impidiendo su lanzamiento. Si importaras y utilizaras, por ejemplo, OpenSSL para tu cifrado, tu aplicación no sería vulnerable a un ataque generalizado de la API de Apple. Puedes compilar OpenSSL tú mismo y enlazarlo estáticamente en tu aplicación.

Así que siempre hay más que aprender, y el futuro de la seguridad de las aplicaciones en iOS siempre está evolucionando. La arquitectura de seguridad de iOS ya admite incluso dispositivos criptográficos y tarjetas inteligentes. Para terminar, ya conoces las mejores prácticas para asegurar los datos en reposo, así que te toca seguirlas.

Mientras tanto, echa un vistazo a otros contenidos sobre el desarrollo y la seguridad de las aplicaciones de iOS.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.