Asegurar y cifrar datos en iOS

Ya sea que esté creando una aplicación móvil o un servicio web, mantener la seguridad de los datos confidenciales es importante y la seguridad se ha convertido en un aspecto esencial de cada producto de software. En este tutorial, le mostraré cómo almacenar de forma segura las credenciales de los usuarios utilizando el llavero de la aplicación y analizaremos el cifrado y descifrado de los datos de los usuarios mediante una biblioteca de terceros..


Introducción

En este tutorial, le enseñaré cómo proteger datos confidenciales en la plataforma iOS. Los datos confidenciales pueden ser las credenciales de la cuenta de un usuario o los detalles de la tarjeta de crédito. El tipo de datos no es tan importante. En este tutorial, usaremos el llavero de iOS y el cifrado simétrico para almacenar de forma segura los datos del usuario. Antes de entrar en los detalles esenciales, me gustaría darles una visión general de lo que vamos a hacer en este tutorial..

Aunque este tutorial se centra en iOS, los conceptos y técnicas también se pueden usar en OS X.

llavero iOS

En iOS y OS X, un llavero es un contenedor encriptado para almacenar contraseñas y otros datos que necesitan ser protegidos. En OS X, es posible limitar el acceso de llavero a usuarios o aplicaciones particulares. Sin embargo, en iOS, cada aplicación tiene su propio llavero al que solo tiene acceso la aplicación. Esto garantiza que los datos almacenados en el llavero sean seguros y no accesibles por terceros..

Tenga en cuenta que el llavero solo debe utilizarse para almacenar pequeños fragmentos de datos, como contraseñas. Con este artículo, espero convencerlo del valor de usar el llavero en iOS y OS X en lugar de, por ejemplo, la base de datos predeterminada de usuario de la aplicación, que almacena sus datos en texto plano sin ningún tipo de seguridad..

En iOS, una aplicación puede usar el llavero a través de API de servicios de llavero. La API proporciona una serie de funciones para manipular los datos almacenados en el llavero de la aplicación. Echa un vistazo a las funciones disponibles en iOS.

  • SecItemAdd Esta función se utiliza para agregar un elemento al llavero de la aplicación.
  • SecItemCopyMatching Utiliza esta función para encontrar un elemento de llavero de propiedad de la aplicación.
  • SecItemDelete Como su nombre lo indica, esta función se puede utilizar para eliminar un elemento del llavero de la aplicación..
  • SecItemUpdate Utilice esta función si necesita actualizar un elemento en el llavero de la aplicación.

los API de servicios de llavero es una API de C, pero espero que no te impida usarla. Cada una de las funciones anteriores acepta un diccionario (CFDictionaryRef), que contiene un par de clave-valor de clase de elemento y pares de clave-valor de atributo opcionales. El significado exacto y el propósito de cada uno quedarán claros una vez que comencemos a usar la API en un ejemplo.


Cifrado y descifrado

Cuando se habla de cifrado, generalmente se conocen dos tipos de cifrado., simétrico y asimétrico cifrado El cifrado simétrico, por un lado, utiliza una clave compartida para cifrar y descifrar datos. El cifrado asimétrico, por otro lado, utiliza una clave para cifrar datos y otra clave separada, pero relacionada, para descifrar datos.

En este tutorial, aprovecharemos la Marco de seguridad Disponible en iOS para cifrar y descifrar datos. Este proceso tiene lugar bajo el capó, por lo que no interactuaremos directamente con este marco. Usaremos cifrado simétrico en nuestra aplicación de ejemplo.

los Marco de seguridad ofrece una serie de otros servicios, como los servicios de aleatorización para generar números aleatorios, certificados, claves y servicios de confianza criptográficamente seguros para administrar certificados, claves públicas y privadas, y políticas de confianza. los Marco de seguridad es un marco de bajo nivel disponible en iOS y OS X con API basadas en C.


Descripción general de la aplicación

En este tutorial, le mostraré cómo puede utilizar la API de Keychain Services y el cifrado simétrico en una aplicación de iOS. Crearemos una pequeña aplicación que almacene de forma segura las fotos tomadas por el usuario..

En este proyecto, usaremos Sam Soffes SSKeychain, un contenedor de Objective-C para interactuar con la API de Keychain Services. Para el cifrado y descifrado, utilizaremos RNCryptor, una biblioteca de cifrado de terceros..


Cifrado de datos con RNCryptor

La biblioteca RNCryptor es una buena opción para cifrar y descifrar datos. El proyecto es utilizado por muchos desarrolladores y mantenido activamente por sus creadores. La biblioteca ofrece una API de Objective-C fácil de usar. Si está familiarizado con Cocoa y Objective-C, lo encontrará fácil de usar. Las principales características de la biblioteca se enumeran a continuación..

  • Cifrado AES-256
  • Modo CBC
  • Estiramiento de contraseña con PBKDF2
  • Contraseña de salazón
  • Aleatorio IV
  • Encrypt-Then-Hash HMAC

Flujo de aplicación

Antes de comenzar a crear la aplicación, permítame mostrarle cómo se verá el flujo típico de la aplicación.

  • Cuando el usuario inicia la aplicación, se le presenta una vista para iniciar sesión.
  • Si aún no ha creado una cuenta, sus credenciales se agregarán al llavero y se habrá registrado..
  • Si ella tiene una cuenta, pero ingresa una contraseña incorrecta, se muestra un mensaje de error.
  • Una vez que ha iniciado sesión, tiene acceso a las fotos que tomó con la aplicación. Las fotos son almacenadas de forma segura por la aplicación..
  • Cada vez que toma una foto con la cámara del dispositivo o elige una foto de su biblioteca de fotos, la foto se cifra y almacena en la aplicación. Documentos directorio.
  • Cada vez que cambia a otra aplicación o el dispositivo se bloquea, se desconecta automáticamente..

Construyendo la Aplicación

Paso 1: Configuración del proyecto

Arranque Xcode y cree un nuevo proyecto seleccionando Solicitud de vista única plantilla de la lista de plantillas.


Nombra el proyecto Fotos seguras y establecer Familia de dispositivos al iPhone. Dile a Xcode dónde quieres guardar el proyecto y pulsa Crear.


Paso 2: Marcos

El siguiente paso es vincular el proyecto con el Seguridad y Servicios básicos móviles marcos Seleccione el proyecto en el Navegador de proyectos a la izquierda, elige el primer objetivo llamado Fotos seguras, y abre el Construir fases pestaña en la parte superior. Ampliar la Enlace Binario Con Bibliotecas cajón y vincular el proyecto contra el Seguridad y Servicios básicos móviles marcos.


Paso 3: Dependencias

Como mencioné anteriormente, usaremos la biblioteca SSKeychain y la biblioteca RNCryptor. Descargue estas dependencias y agréguelas al proyecto. Asegúrese de copiar los archivos a su proyecto y agregarlos a la Fotos seguras objetivo como se muestra en la captura de pantalla a continuación.


Paso 4: Creando Clases

Mostraremos las fotos del usuario en una vista de colección, lo que significa que necesitamos subclases UICollectionViewController tanto como UICollectionViewCell. Seleccionar Nuevo> Archivo ... desde el Expediente menú, crear una subclase de UICollectionViewController, y nombrarlo MTPhotosViewController. Repita este paso una vez más para MTPhotoCollectionViewCell, que es una subclase de UICollectionViewCell.

Paso 5: Crear la interfaz de usuario

Abra el guión gráfico principal del proyecto y actualícelo como se muestra en la siguiente captura de pantalla. El guión gráfico contiene dos controladores de vista, una instancia de MTViewController, que contiene dos campos de texto y un botón, y una instancia de MTPhotosViewController. los MTViewController la instancia está integrada en un controlador de navegación.

También necesitamos crear un segue desde el MTViewController instancia a la MTPhotosViewController ejemplo. Establezca el identificador de segue en photosViewController. los MTPhotosViewController la instancia también debe contener un elemento de botón de barra como se muestra en la captura de pantalla a continuación.


Para hacer todo este trabajo, necesitamos actualizar la interfaz de MTViewController Como se muestra abajo. Declaramos una salida para cada campo de texto y una acción que se activa con el botón. Realiza las conexiones necesarias en el storyboard principal del proyecto..

 #importar  @interface MTViewController: UIViewController @property (débil, no atómico) IBOutlet UITextField * usernameTextField; @ propiedad (débil, no atómica) IBOutlet UITextField * passwordTextField; - (IBAction) inicio de sesión: (id) remitente; @fin

En el MTPhotosViewController clase, declarar una propiedad nombrada nombre de usuario para almacenar el nombre de usuario del usuario que ha iniciado sesión y declarar una acción para el elemento del botón de la barra. No olvides conectar la acción con el elemento del botón de la barra en el guión gráfico principal.

 #importar  @interface MTPhotosViewController: UICollectionViewController @property (copy, nonatomic) NSString * username; - Fotos (IBAction): (id) remitente; @fin

Paso 6: Implementando MTViewController

En MTViewController.m, añadir una declaración de importación para el MTPhotosViewController clase, la Llavero SS clase, y la MTAppDelegate clase. También conformamos el MTViewController clase a la UIAlertViewDelegate protocolo.

 # importación "MTViewController.h" # importación "SSKeychain.h" # importación "MTAppDelegate.h" # importación "MTPhotosViewController.h" @interface MTViewController ()  @fin

El siguiente paso es implementar el iniciar sesión: Acción que declaramos anteriormente. Primero verificamos si el usuario ya ha creado una cuenta buscando la contraseña de la cuenta. Si esto es cierto, usamos el llavero de la aplicación para ver si la contraseña ingresada por el usuario coincide con la almacenada en el llavero. Los métodos proporcionados por el Llavero SS La biblioteca facilita la lectura y manipulación de los datos almacenados en el llavero de la aplicación.

 - (IBAction) inicio de sesión: (id) remitente if (self.usernameTextField.text.length> 0 && self.passwordTextField.text.length> 0) NSString * password = [SSKeychain passwordForService: @ "MyPhotos" cuenta: self.usernameTextField .texto]; if (password.length> 0) if ([self.passwordTextField.text isEqualToString: password]) [self performSegueWithIdentifier: @ "photosViewController" sender: nil];  else UIAlertView * alertView = [[UIAlertView alloc] initWithTitle: @ Mensaje de "Error de inicio de sesión": @ "Combinación de nombre de usuario / contraseña no válida." delegate: nil cancelButtonTitle: @ "OK" otherButtonTitles: nil]; [alertView show];  else else UIAlertView * alertView = [[UIAlertView alloc] initWithTitle: @ Mensaje de "Nueva cuenta": @ "¿Desea crear una cuenta?" delegate: self cancelButtonTitle: @ "Cancel" otherButtonTitles: @ "OK", nil]; [alertView show];  else else UIAlertView * alertView = [[UIAlertView alloc] initWithTitle: @ Mensaje de "Entrada de error": @ "El nombre de usuario y / o la contraseña no pueden estar vacíos". delegate: nil cancelButtonTitle: @ "OK" otherButtonTitles: nil]; [alertView show]; 

Hemos establecido el controlador de vista como el delegado de la vista de alerta, lo que significa que necesitamos implementar el UIAlertViewDelegate protocolo. Eche un vistazo a la implementación de alertView: clickedButtonAtIndex: mostrado a continuación.

 -alertView (void): (UIAlertView *) alertView clickedButtonAtIndex: (NSInteger) buttonIndex switch (buttonIndex) case 0: break; caso 1: [self createAccount]; descanso; por defecto: break; 

En crear una cuenta, aprovechamos el Llavero SS Clase para almacenar de forma segura el nombre de usuario y la contraseña elegidos por el usuario. Entonces llamamos performSegueWithIdentifier: remitente:.

 - (void) createAccount BOOL result = [SSKeychain setPassword: self.passwordTextField.text forService: @ cuenta de "MyPhotos": self.usernameTextField.text]; if (result) [self performSegueWithIdentifier: @ "photosViewController" sender: nil]; 

En prepareForSegue: remitente:, obtenemos una referencia a la MTPhotosViewController instancia, establecer su nombre de usuario propiedad con el valor de la usernameTextField, y restablecer el passwordTextField.

 - (void) prepareForSegue: (UIStoryboardSegue *) segue sender: (id) sender MTPhotosViewController * photosViewController = segue.destinationViewController; photosViewController.username = self.usernameTextField.text; self.passwordTextField.text = nil; 

Paso 7: Implementando MTPhotosCollectionViewCell

Abierto MTPhotosCollectionViewCell.h y declarar una salida llamada imageView de tipo UIImageView.

 #importar  @interface MTPhotoCollectionViewCell: UICollectionViewCell @property (débil, no atómico) IBOutlet UIImageView * imageView; @fin

Abre el guión gráfico principal y añade un UIImageView instancia de la célula prototipo de la MTPhotosViewController ejemplo. Seleccione la celda prototipo (no la vista de imagen) y establezca su clase en MTPhotosCollectionViewCell en el Inspector de identidad a la derecha. Con la celda prototipo aún seleccionada, abre el Inspector de atributos y establece el identificador a Célula fotoeléctrica.

Paso 8: Implementando MTPhotosViewController

Comience importando los archivos de encabezado necesarios en MTPhotosViewController.m Como se muestra abajo. También tenemos que declarar dos propiedades., fotos para almacenar el conjunto de fotos, se mostrará la vista de colección y ruta de archivo para mantener una referencia a la ruta del archivo. Usted puede haber notado que el MTPhotosViewController clase se ajusta a la UIActionSheetDelegate, UINavigationControllerDelegate, y UIImagePickerControllerDelegate protocolos.

 #import "MTPhotosViewController.h" #import  #importar "RNDecryptor.h" #importar "RNEncryptor.h" #importar "MTPhotoCollectionViewCell.h" @interface MTPhotosViewController ()  @property (strong, nonatomic) NSMutableArray * photos; @property (copy, nonatomic) NSString * filePath; @fin

También he implementado un método de conveniencia o de ayuda., setupUserDirectory, para crear y configurar los directorios necesarios en los que almacenaremos los datos del usuario. En prepareData, La aplicación descifra las imágenes que están almacenadas en el directorio seguro del usuario. Echa un vistazo a sus implementaciones a continuación..

 - (void) setupUserDirectory NSArray * paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES); NSString * documents = [rutas objectAtIndex: 0]; self.filePath = [documents stringByAppendingPathComponent: self.username]; NSFileManager * fileManager = [Administrador predeterminado de NSFileManager]; if ([fileManager fileExistsAtPath: self.filePath]) NSLog (@ "Directorio ya presente.");  else NSError * error = nil; [fileManager createDirectoryAtPath: self.filePath withIntermediateDirectories: YES atributos: nil error: & error]; if (error) NSLog (@ "No se puede crear el directorio para el usuario."); 
 - (void) prepareData self.photos = [[NSMutableArray alloc] init]; NSFileManager * fileManager = [Administrador predeterminado de NSFileManager]; NSError * error = nil; NSArray * contents = [fileManager contentsOfDirectoryAtPath: self.filePath error: & error]; if ([conteo de contenidos] &&! error) NSLog (@ "Contenido del directorio del usuario.% @", contenidos); para (NSString * fileName en el contenido) if ([fileName rangeOfString: @ ". securedData"]. length> 0) NSData * data = [NSData dataWithContentsOfFile: [self.filePath stringByAppendingPathComponent: fileName]]; NSData * decryptedData = [RNDecryptor decryptData: datos withSettings: kRNCryptorAES256Configuración de contraseña: @ "A_SECRET_PASSWORD" error: nil]; UIImage * image = [UIImage imageWithData: decryptedData]; [self.photos addObject: image];  else NSLog (@ "Este archivo no está protegido.");  else else if (! [conteo de contenidos]) if (error) NSLog (@ "No se puede leer el contenido del directorio del usuario");  else NSLog (@ "El directorio del usuario está vacío."); 

Invoque ambos métodos en la vista del controlador. viewDidLoad método como se muestra a continuación.

 - (void) viewDidLoad [super viewDidLoad]; [auto setupUserDirectory]; [self prepareData]; 

El elemento del botón de la barra en la barra de navegación del controlador de vista muestra una hoja de acción que permite al usuario elegir entre la cámara del dispositivo y la biblioteca de fotos.

 - Photos (IBAction): (id) sender UIActionSheet * actionSheet = [[UIActionSheet alloc] initWithTitle: @ Delegado "Select Source": self cancelButtonTitle: @ "Cancel" destructiveButtonTitle: n otherButtonTitles: @ "Camera", , nil]; [actionSheet showFromBarButtonItem: sender animated: YES]; 

Vamos a implementar actionSheet: clickedButtonAtIndex: del UIActionSheetDelegate protocolo.

 - (void) actionSheet: (UIActionSheet *) actionSheet clickedButtonAtIndex: (NSInteger) buttonIndex if (buttonIndex < 2)  UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init]; imagePickerController.mediaTypes = @[(__bridge NSString *)kUTTypeImage]; imagePickerController.allowsEditing = YES; imagePickerController.delegate = self; if (buttonIndex == 0)  #if TARGET_IPHONE_SIMULATOR #else imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera; #endif  else if ( buttonIndex == 1)  imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;  [self.navigationController presentViewController:imagePickerController animated:YES completion:nil];  

Para manejar la selección del usuario en el controlador de selección de imágenes, necesitamos implementar imagePickerController: didFinishPickingMediaWithInfo: del UIImagePickerControllerDelegate protocolo como se muestra a continuación. La imagen se encripta utilizando encryptData del Crncryptor biblioteca. La imagen también se agrega a la fotos matriz y la vista de colección se vuelve a cargar.

 - (void) imagePickerController: (UIImagePickerController *) picker didFinishPickingMediaWithInfo: (NSDictionary *) info UIImage * image = [info objectForKey: UIImagePickerControllerEditedImage]; if (! image) [info objectForKey: UIImagePickerControllerOriginalImage];  NSData * imageData = UIImagePNGRepresentation (imagen); NSString * imageName = [NSString stringWithFormat: @ "image-% d.securedData", self.photos.count + 1]; NSData * encryptedImage = [RNEncryptor encryptData: imageData withSettings: kRNCryptorAES256Configuración de la contraseña: @ "A_SECRET_PASSWORD" error: nil]; [encryptedImage writeToFile: [self.filePath stringByAppendingPathComponent: imageName] atómicamente: YES]; [self.photos addObject: image]; [self.collectionView reloadData]; [picker dismissViewControllerAnimated: YES complete: nil]; 

Antes de poder generar y ejecutar la aplicación, necesitamos implementar el UICollectionViewDataSource protocolo como se muestra a continuación.

 - (NSInteger) collectionView: (UICollectionView *) collectionView numberOfItemsInSection: (NSInteger) sección return self.photos? self.photos.count: 0; 
 - (UICollectionViewCell *) collectionView: (UICollectionView *) collectionView cellForItemAtIndexPath: (NSIndexPath *) indexPath MTPhotoCollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier: @ "PhotoCell" para el índice cell.imageView.image = [self.photos objectAtIndex: indexPath.row]; celda de retorno 

Paso 9: Manejo de los estados de aplicación

Si la aplicación pasa al fondo, el usuario debe cerrar sesión. Esto es importante desde una perspectiva de seguridad. Para lograr esto, el delegado de la aplicación debe tener una referencia al controlador de navegación para que pueda aparecer en el controlador de vista raíz de la pila de navegación. Comience por declarar una propiedad nombrada control de navegación en MTAppDelegate.h.

 #importar  @interface MTAppDelegate: UIResponder  @property (strong, nonatomic) ventana UIWindow *; @property (strong, nonatomic) UINavigationController * navigationController; @fin

En el controlador de vista viewDidLoad Método, configuramos la aplicación del delegado. control de navegación propiedad como se muestra a continuación. Tenga en cuenta que esta es solo una forma de manejar esto.

He puesto la propiedad anterior en ViewController es viewDidLoad método como se muestra a continuación.

 - (void) viewDidLoad [super viewDidLoad]; MTAppDelegate * applicationDeleagte = (MTAppDelegate *) [[UIApplication sharedApplication] delegate]; [applicationDeleagte setNavigationController: self.navigationController]; 

En la aplicación delegada, necesitamos actualizar applicationWillResignActive: Como se muestra abajo. Es tan simple como eso. El resultado es que el usuario cierra la sesión cada vez que la aplicación pierde el enfoque. Protegerá las imágenes del usuario almacenadas en la aplicación de miradas indiscretas. El inconveniente es que el usuario debe iniciar sesión cuando la aplicación vuelva a estar activa..

 - application (void) applicationWillResignActive: (UIApplication *) application [self.navigationController popToRootViewControllerAnimated: NO]; 

Paso 10: construir y ejecutar

Construye el proyecto y ejecuta la aplicación para ponerlo a prueba..


Conclusión

En este tutorial, aprendió a usar la API de Keychain Services para almacenar datos confidenciales y también aprendió a cifrar datos de imagen en iOS. Deje un comentario en los comentarios a continuación si tiene alguna pregunta o comentario.