Introducción al almacenamiento de datos seguros en iOS

La protección de datos de usuario sensibles es una responsabilidad fundamental de cualquier aplicación iOS. Ya sea que esté almacenando fichas de autenticación, claves de cifrado o credenciales privadas, la plataforma proporciona una solución dedicada respaldada por hardware: Keychain]. A diferencia de o archivos de lista de propiedades, la Keychain cifra datos en reposo y hace cumplir un control de acceso estricto.

Comprender la cadena de clave de iOS

La llavero es un contenedor de almacenamiento seguro gestionado por el sistema operativo. Almacena pequeños elementos sensibles, como contraseñas, claves criptográficas o certificados, en una base de datos cifrada. Los datos escritos en la llave de llave están protegidos incluso cuando el dispositivo está bloqueado.

  • Encriptación en reposo] utilizando AES-256 respaldados por hardware.
  • Control de acceso] a través de código de acceso, ID táctil o ID de cara.
  • Persistencia a través de las reinstalaciones de aplicaciones (si está configurada) y sincronización opcional de iCloud.
  • Isolación] entre aplicaciones: por defecto, una aplicación no puede leer los elementos de Keychain de otra aplicación a menos que compartan un grupo de acceso Keychain.

La cadena de llaves no está diseñada para grandes bloques; mantenga cada elemento bajo unos pocos kilobytes. Para datos más grandes, considere utilizar la API o el marco junto con el cifrado basado en archivos.

Keychain Services API vs. Bibliotecas de terceros

Apple proporciona el acceso nativo Keychain Services API (C-based, ), que es potente pero verbose. Usted puede utilizarlo directamente, o adoptar un envoltorio compatible con Swift. Las bibliotecas populares de terceros como KeychainAcceso] o [[FLTKLT]

Configuración de almacenamiento de llavero

Antes de almacenar cualquier cosa, usted debe decidir sobre Keychain item class]. Lo más común para las contraseñas genéricas es . Para las contraseñas o certificados de Internet, hay otras clases. Cada artículo es referenciado por un conjunto de atributos — un diccionario (CFDiccionario) que describe el artículo.

El flujo básico siempre sigue este patrón:

  1. Construya un diccionario de consulta con la clase de elementos y atributos.
  2. Llamar a la función ] apropiada (], , , ).
  3. Verifique el devuelto ( o un código de error).

Antes de escribir código, importa el módulo de seguridad:

import Security
import Foundation // for Data and String utilities

Datos de almacenamiento en la cadena de claves

Escribir una contraseña genérica

Para guardar una ficha (por ejemplo, un JWT) para el usuario actual:

func saveToken(_ token: String, forAccount account: String) -> Bool {
 guard let tokenData = token.data(using: .utf8) else { return false }

 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account,
 kSecValueData as String: tokenData,
 // Optional: restrict access to when device is unlocked
 kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
 ]

 // Delete any existing item first to avoid duplicates
 SecItemDelete(query as CFDictionary)

 let status = SecItemAdd(query as CFDictionary, nil)
 return status == errSecSuccess
}

Puntos clave:

  • actúa como una clave principal; elige una cuerda única (por ejemplo, el ID de usuario o una constante como ).
  • controla cuando se puede leer el artículo. Use para una mejor seguridad; previene la copia de seguridad de iCloud y restringe el acceso al dispositivo actual.
  • Llamamos antes de añadir para evitar acumular artículos duplicados. Alternativamente, usted puede utilizar .

Añadiendo control de acceso (Biometría o código de paso)

Para datos altamente sensibles, requiera ID táctil o ID facial antes de leer:

let accessControl = SecAccessControlCreateWithFlags(
 nil,
 kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
 .userPresence, // requires passcode, Face ID, or Touch ID
 nil
)

let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account,
 kSecValueData as String: tokenData,
 kSecAttrAccessControl as String: accessControl as Any
]
SecItemAdd(query as CFDictionary, nil)

Ahora cualquier llamada para este artículo activará un impulso biométrico o de código de paso. Use de LocalAuthentication para manejar la interacción del usuario con gracia.

Recuperar datos de la cadena de llaves

Para leer el token almacenado:

func retrieveToken(forAccount account: String) -> String? {
 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account,
 kSecReturnData as String: true,
 kSecMatchLimit as String: kSecMatchLimitOne
 ]

 var item: CFTypeRef?
 let status = SecItemCopyMatching(query as CFDictionary, &item)

 guard status == errSecSuccess,
 let data = item as? Data,
 let token = String(data: data, encoding: .utf8) else {
 return nil
 }
 return token
}

Establecer a ] para recuperar los datos. Utilice para recuperar un solo resultado. Si omite el límite, la API puede devolver un array.

Importante:] Al utilizar el control de acceso (biometría), la llamada podría devolver si el usuario cancela. Maneje este caso por separado y nunca vuelva a caer en el almacenamiento de texto plano.

Actualización y eliminación de elementos de la cadena de llaves

Actualización de un artículo existente

En lugar de borrar y re-adding, use :

func updateToken(_ newToken: String, forAccount account: String) -> Bool {
 guard let newData = newToken.data(using: .utf8) else { return false }

 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account
 ]

 let attributesToUpdate: [String: Any] = [
 kSecValueData as String: newData
 ]

 let status = SecItemUpdate(query as CFDictionary, attributesToUpdate as CFDictionary)
 return status == errSecSuccess
}

Esto es más eficiente que un borrado+add, y evita las condiciones de carrera potenciales.

Eliminar un artículo

func deleteItem(forAccount account: String) -> Bool {
 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: account
 ]
 let status = SecItemDelete(query as CFDictionary)
 return status == errSecSuccess
}

Tenga cuidado de no eliminar elementos que pertenecen a otras aplicaciones que comparten el mismo grupo de acceso, siempre alcance su consulta con si utiliza Keychains compartidos.

Acceso atributos de control y accesibilidad

La constante define cuando se puede leer el elemento Keychain. Elige la opción más restrictiva que aún satisface las necesidades de su aplicación:

AttributeMeaning
kSecAttrAccessibleWhenUnlockedAvailable only while device is unlocked (default).
kSecAttrAccessibleAfterFirstUnlockAvailable after device boots and is unlocked once. Allows background access.
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyRequires a passcode to be set. Strictest option—prevents access even after unlock if passcode is removed.
kSecAttrAccessibleWhenUnlockedThisDeviceOnlySame as WhenUnlocked but does not back up to iCloud, and cannot be restored to another device.

Para la mayoría de las aplicaciones, da el equilibrio adecuado entre seguridad y usabilidad. Si necesita leer los elementos en el fondo (por ejemplo, un token de refresco de fondo), debe utilizar (y aceptar que los datos están ligeramente menos protegidos).

Manejo de errores y saltos comunes

Las funciones devuelven un . Siempre comprueba y maneja los fallos adecuadamente.

  • (–25300) – Ningún artículo coincide con la consulta.
  • (–25299) – Ya existe un elemento con la misma clave primaria (si no se elimina primero).
  • (–128) – El usuario canceló la velocidad biométrica.
  • (–25293) – La autenticación falló o no se disponía de biometría.

Nunca ignores un estado de no éxito. Degradado graciosamente: muestre un mensaje de error o reingrese, pero nunca almacene datos sensibles fuera de la cadena de llaves como un inconveniente. Puedes usar para comprobar la disponibilidad biométrica antes de intentar el acceso.

Prácticas óptimas y consideraciones de producción

  • Utilizar nombres de cuenta únicos y descriptivos por usuario o por tipo de artículo para evitar colisiones.
  • Siempre especificar un atributo de accesibilidad; de lo contrario, se aplica el predeterminado del sistema (], que puede no ser ideal.
  • Datos de Keychain de cable cuando el usuario se registra]—elabore sobre todas las cuentas conocidas y elimine los elementos.
  • Use Grupos de Acceso a Keychain sólo cuando compartan sus propias aplicaciones. Evite grupos amplios.
  • Nunca almacene datos no sensibles (como preferencias de usuario) en la cadena de llaves — use o una base de datos en su lugar.
  • Consider using with para escenarios avanzados (macOS Catalyst).
  • Prueba en un dispositivo real; el Simulador utiliza un teclado de software que se comporta de forma diferente del almacenamiento respaldado por hardware.

Usando Keychain con SwiftUI y Async/Await

Para aplicaciones modernas, envuelve las operaciones de Keychain en un actor o una clase asinc-safe para evitar bloquear el hilo principal. Ejemplo utilizando :

actor KeychainManager {
 func saveToken(_ token: String, for account: String) async -> Bool {
 // same implementation as above, but now it's safe to call from any context
 return saveToken(token, forAccount: account)
 }
}

Si utiliza biometría, la llamada puede bloquear el hilo mientras espera la interacción del usuario. Envuélvelo en una cola de fondo, o mejor, utilice método antes de la llamada Keychain.

Conclusión

The iOS Keychain is the correct place to store small, sensitive pieces of data. By using the native Keychain Services API, you gain direct control over encryption, accessibility, and authentication policies. Always pair your Keychain usage with solid error handling and remember to clear data when appropriate. For further reading, refer to the Apple Keychain Service Documentation and the Keychain Concepts overview. Adopting these practices will help you ship iOS apps that respect user privacy and withstand security scrutiny.