Seguridad en aplicaciones iOS

Buenas tardes, Habr! Le presento la traducción de un artículo sobre Arlind Aliu sobre los principios básicos de la seguridad de los datos confidenciales en las aplicaciones de iOS "Application Security Musts for every iOS App" .

La seguridad de las aplicaciones es uno de los aspectos más importantes del desarrollo de software. Los usuarios de la aplicación esperan que la información que proporcionan esté protegida de forma segura. Por lo tanto, no puede simplemente proporcionar información confidencial a alguien.

Afortunadamente, en este artículo discutiremos los errores que los desarrolladores cometen en sus aplicaciones, así como las formas de resolverlos.
Continúa debajo del corte.

Almacenamiento de datos en el lugar equivocado.


Realicé un estudio de varias aplicaciones desde la AppStore y muchas cometen el mismo error: la información confidencial se almacena donde no debería estar.
Si almacena datos personales en UserDefaults , los pone en riesgo. UserDefaults se almacenan en un archivo con una lista de propiedades, que se encuentra dentro de la carpeta "Configuración" en su aplicación. Los datos se almacenan en la aplicación sin el menor indicio de cifrado.

Al instalar un programa de terceros en Mac, como iMazing, ni siquiera tiene que hackear el teléfono, sino que inmediatamente ve todos los datos de UserDefaults de la aplicación instalada desde la AppStore. Dichos programas le permiten ver y administrar datos de aplicaciones instaladas en el iPhone. Puede obtener fácilmente UserDefaults de cualquier aplicación.
Esta es la razón principal por la que decidí escribir un artículo: encontré un montón de aplicaciones en la AppStore que almacenan datos en UserDefaults , como: tokens, suscripciones activas y renovables, la cantidad de dinero disponible, etc. Todos estos datos pueden obtenerse y utilizarse fácilmente con intenciones maliciosas, desde administrar suscripciones pagas en la aplicación hasta piratear a nivel de red y, lo que es peor.

Y ahora sobre cómo almacenar datos.

Recuerde, solo una pequeña cantidad de información debe almacenarse en UserDefaults , como la configuración dentro de la aplicación, es decir, datos que no son confidenciales para el usuario.

Use los servicios de seguridad dedicados de Apple para almacenar información personal. El servicio Keychain API le permite almacenar una cierta cantidad de datos de usuario en una base de datos cifrada. Allí puede almacenar contraseñas y otros datos importantes para el usuario, como la información de la tarjeta de crédito o incluso pequeñas notas importantes.
Además, puede haber claves y certificados cifrados con los que trabajas.

Servicio API de llavero


El siguiente es un ejemplo de cómo guardar la contraseña de un usuario en el llavero.

class KeychainService { func save(_ password: String, for account: String) { let password = password.data(using: String.Encoding.utf8)! let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecValueData as String: password] let status = SecItemAdd(query as CFDictionary, nil) guard status == errSecSuccess else { return print("save error") } } 

Parte del diccionario kSecClass: kSecClassGenericPassword significa que la información que debe cifrarse es la contraseña. Luego agregamos la nueva contraseña al llavero llamando al método SecItemAdd . Recuperar datos de un paquete es similar a guardar.

 func retrivePassword(for account: String) -> String? { let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: account, kSecMatchLimit as String: kSecMatchLimitOne, kSecReturnData as String: kCFBooleanTrue] var retrivedData: AnyObject? = nil let _ = SecItemCopyMatching(query as CFDictionary, &retrivedData) guard let data = retrivedData as? Data else {return nil} return String(data: data, encoding: String.Encoding.utf8) } 

Escribamos un pequeño cheque sobre la exactitud de guardar y recibir datos.

 func testPaswordRetrive() { let password = "123456" let account = "User" keyChainService.save(password, for: account) XCTAssertEqual(keyChainService.retrivePassword(for: account), password) } 

A primera vista, puede parecer que la API Keychain es bastante difícil de usar, especialmente si necesita guardar más de una contraseña, por lo que le recomiendo que use el patrón Facade para estos fines. Le permitirá guardar y modificar datos según las necesidades de la aplicación.

Si desea saber más sobre este patrón, así como sobre cómo crear un contenedor simple para subsistemas complejos, este artículo lo ayudará. También en Internet está lleno de bibliotecas abiertas que ayudan a usar Keychain API, por ejemplo, SAMKeychain y SwiftKeychainWrapper .

Guardar contraseña y autorización


En mi carrera de desarrollo, constantemente enfrento el mismo problema. Los desarrolladores almacenan contraseñas en la aplicación o crean una solicitud al servidor, que envía el nombre de usuario y la contraseña directamente.

Si almacena datos en UserDefault , luego de leer la información de la primera parte del artículo, ya comprende cuánto arriesga. Al almacenar las contraseñas en el llavero, aumenta considerablemente el nivel de seguridad de su aplicación, pero nuevamente, antes de guardar información confidencial en cualquier lugar, primero debe cifrarla.

Supongamos que un hacker puede atacarnos a través de nuestra red. Por lo tanto, recibirá contraseñas en forma de texto sin formato. Es mejor, por supuesto, descifrar todas las contraseñas.

Cifrado de datos personales


El hash puede parecer un poco abrumador si lo hace usted mismo, por lo que en este artículo usaremos la biblioteca CryptoSwift . Ha recopilado muchos algoritmos de cifrado confiables estándar utilizados en Swift.

Intentemos guardar y recuperar la contraseña del llavero utilizando los algoritmos CryptoSwift .

 func saveEncryptedPassword(_ password: String, for account: String) { let salt = Array("salty".utf8) let key = try! HKDF(password: Array(password.utf8), salt: salt, variant: .sha256).calculate().toHexString() keychainService.save(key, for: account) } 

La función anterior registra el nombre de usuario y la contraseña y los guarda en Keychain como una cadena encriptada.

Veamos que pasa adentro:

- El nombre de usuario y la contraseña se escriben en la variable salt como una cadena
- sha256 llena el hash SHA-2
- HKDF es una función de generación de claves ( KDF ) basada en el código de autenticación de mensajes ( HMAC )

Creamos la variable de sal para complicar la tarea a los hackers. Solo podíamos encriptar la contraseña, pero en este caso, el atacante puede tener una lista de las contraseñas más utilizadas, las encriptará sin problemas y las comparará con nuestra contraseña encriptada. Luego, encontrar la contraseña para una cuenta específica no es difícil.
Ahora podemos iniciar sesión con nuestra cuenta y la clave generada.

 authManager.login(key, user) 

Por supuesto, el servidor debe saber qué está cifrado en nuestra variable de sal. El backend podrá comparar claves usando el mismo algoritmo para identificar al usuario.
El uso de este enfoque mejorará en gran medida la seguridad de su aplicación.

Como finalización


Nunca descuides la seguridad de tu aplicación. En este artículo, primero descubrimos cuáles podrían ser las consecuencias al almacenar datos confidenciales en UserDefaults y por qué se necesita Keychain.

En la segunda parte, hablaremos sobre un nivel de seguridad más serio, encriptando los datos antes de guardarlos, y también discutiremos cómo transferir correctamente la información con datos personales al servidor.

Source: https://habr.com/ru/post/es430532/


All Articles