Keychain es un almacenamiento seguro en iOS diseñado para guardar contraseñas, claves criptográficas, certificados y notas confidenciales de forma segura. Según Apple Security Documentation (2025), Keychain utiliza cifrado por hardware a través de Secure Enclave en todos los dispositivos con chip A7 y posteriores. Comprender la arquitectura de iOS Keychain es esencial para que cada desarrollador almacene correctamente los tokens y secretos de la aplicación.
Puntos clave
iOS Keychain es un mecanismo seguro para almacenar datos confidenciales, integrado en el sistema operativo de Apple. A diferencia de UserDefaults o los archivos normales, Keychain cifra todos los elementos a nivel de hardware y proporciona un control de acceso detallado basado en políticas de seguridad.
Keychain se introdujo en iOS 2.0 y desde entonces ha sufrido cambios significativos: en iOS 7 se agregó soporte para claves de hardware a través de Secure Enclave, en iOS 9 se introdujo el uso compartido de Keychain entre aplicaciones mediante Access Groups, en iOS 13 se agregó soporte para vinculación biométrica a través de LAContext. Según Apple WWDC Session (2024), más del 90% de las aplicaciones iOS en el top 100 de App Store usan Keychain para almacenar tokens de autenticación.
Arquitectónicamente, Keychain es una base de datos SQLite cifrada ubicada fuera del sandbox de la aplicación. Cada elemento (SecItem) se cifra con una clave separada que, a su vez, está protegida por la clave de hardware Secure Enclave. El servicio del sistema Securityd gestiona el acceso a Keychain según los entitlements de la aplicación y la clase de protección solicitada.
Una ventaja importante de Keychain sobre otros métodos de almacenamiento: los datos se cifran y descifran automáticamente por el sistema operativo. El desarrollador no necesita implementar criptografía manualmente, solo llamar a SecItemAdd con los parámetros correctos. iOS garantiza que otros programas no puedan leer los datos de Keychain (cuando Access Groups están configurados correctamente).
La arquitectura de Keychain incluye varios niveles: físico (Secure Enclave), sistema (Security.framework), aplicación (API SecItem*) y lógico (Access Groups, Protection Classes). Comprender cada nivel ayuda a diseñar correctamente el almacenamiento de secretos.
La API principal para trabajar con Keychain son las funciones de Security framework: SecItemAdd para agregar, SecItemCopyMatching para leer, SecItemUpdate para actualizar y SecItemDelete para eliminar. Cada función acepta un diccionario query que describe los atributos del elemento a buscar o guardar.
Atributos clave de query: kSecClass - tipo de elemento (kSecClassGenericPassword, kSecClassKey, kSecClassCertificate), kSecAttrAccount - identificador único dentro de la clase, kSecValueData - datos guardados (Data), kSecAttrAccessible - clase de protección. SecItemCopyMatching con el indicador kSecReturnData devuelve los datos del elemento, con kSecMatchLimit - la cantidad de resultados.
Importante: todas las funciones devuelven un OSStatus. Una operación exitosa devuelve errSecSuccess (0). Errores: errSecItemNotFound (-25300) - elemento no encontrado, errSecDuplicateItem (-25299) - el elemento ya existe, errSecAuthFailed (-25293) - autenticación biométrica fallida. El desarrollador debe manejar cada estado correctamente.
Protection Class es el atributo kSecAttrAccessible que determina cuándo los datos en Keychain están disponibles para lectura. iOS admite seis clases de protección con diferentes niveles de disponibilidad y seguridad.
La clase recomendada para la mayoría de escenarios es kSecAttrAccessibleWhenUnlockedThisDeviceOnly: los datos solo están disponibles cuando el dispositivo está desbloqueado y no se copian a iCloud Backup. Para datos que deben estar disponibles después de reiniciar (pero solo después del primer desbloqueo), use kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Para datos críticos que requieren autenticación biométrica en cada acceso, combine kSecAttrAccessibleWhenUnlockedThisDeviceOnly con una ACL que requiera biometría.
Las clases sin el sufijo ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) permiten la copia a iCloud Backup. Esto es conveniente para el usuario pero reduce la seguridad: los datos pueden restaurarse desde la copia de seguridad. Para tokens de autenticación, use siempre ThisDeviceOnly.
Access Control List (ACL) es un mecanismo que restringe las operaciones en un elemento de Keychain según la autenticación del usuario. La ACL se configura mediante SecAccessControlCreateWithFlags y se pasa al atributo kSecAttrAccessControl al guardar un elemento.
Indicadores compatibles: kSecAccessControlUserPresence - cualquier autenticación (Face ID, Touch ID o código de acceso), kSecAccessControlBiometryCurrentSet - solo biometría (huellas digitales o rostro actualmente registrados), kSecAccessControlDevicePasscode - solo código de acceso. La ACL se aplica a cada operación: leer, actualizar y eliminar un elemento también requieren autenticación.
En iOS 15+ apareció el indicador kSecAccessControlWatch - para Apple Watch, que permite la autenticación a través de un reloj emparejado. La ACL se puede combinar: por ejemplo, kSecAccessControlUserPresence o kSecAccessControlBiometryAny con un código de acceso opcional (orientación .or).
iOS Keychain admite cuatro clases principales de elementos (kSecClass), cada una diseñada para su propio tipo de datos. Elegir la clase correcta simplifica la organización y la búsqueda de elementos.
kSecClassGenericPassword - contraseña genérica: la clase más utilizada. Almacena datos binarios arbitrarios (Data) con una clave única (kSecAttrAccount). Adecuado para tokens, claves de API, códigos PIN. No requiere entitlements adicionales para su uso.
kSecClassInternetPassword - contraseña de internet: almacena datos asociados con un recurso de red. Atributos adicionales: kSecAttrServer (dominio del servidor), kSecAttrProtocol (https, ftp), kSecAttrPort, kSecAttrAuthenticationType. iOS puede completar automáticamente estas contraseñas mediante AutoFill.
kSecClassKey - clave criptográfica: para almacenar claves de cifrado (AES, RSA, EC). La clave se guarda como SecKeyRef, no como Data. kSecClassCertificate - certificado X.509 para almacenar y verificar certificados digitales. Ambas clases requieren comprensión de operaciones criptográficas y configuración correcta de atributos.
En la práctica, el 95% de los casos de uso de Keychain en aplicaciones móviles se cubren con kSecClassGenericPassword para almacenar tokens de autenticación y kSecClassKey para almacenar claves de cifrado privadas. kSecClassCertificate se usa raramente, generalmente en aplicaciones empresariales con PKI propio.
Veamos ejemplos prácticos de trabajo con Keychain en Swift usando Security framework. Cada ejemplo incluye manejo de errores y configuración correcta de Protection Class.
Un ejemplo básico guarda un token de autenticación en Keychain con protección WhenUnlockedThisDeviceOnly. La clave (kSecAttrAccount) es el identificador del servicio, los datos (kSecValueData) son el token en formato Data.
import Security
enum KeychainError: Error {
case unexpectedStatus(OSStatus)
}
func saveToken(token: String, service: String) throws {
let data = Data(token.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "auth_token",
kSecValueData as String: data,
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
}
func readToken(service: String) throws -> String {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: "auth_token",
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne
]
var result: AnyObject?
let status = SecItemCopyMatching(
query as CFDictionary, &result
)
guard status == errSecSuccess,
let data = result as? Data else {
throw KeychainError.unexpectedStatus(status)
}
return String(decoding: data, as: UTF8.self)
}
El ejemplo demuestra el uso de SecAccessControlCreateWithFlags para vincular una clave a la biometría. Cada acceso al elemento requerirá Face ID o Touch ID.
import LocalAuthentication
func saveWithBiometry(data: Data, key: String) throws {
let accessControl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
.biometryCurrentSet,
nil
)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: key,
kSecValueData as String: data,
kSecAttrAccessControl as String: accessControl as Any
]
SecItemDelete(query as CFDictionary)
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else {
throw KeychainError.unexpectedStatus(status)
}
}
El ejemplo muestra la configuración de un Access Group para el acceso compartido a Keychain entre aplicaciones del mismo desarrollador. Requiere el entitlement keychain-access-groups.
// Capabilities: Keychain Sharing habilitado
// App IDs: group.com.example.shared
func saveSharedToken(token: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "shared_token",
kSecValueData as String: token,
kSecAttrAccessGroup as String:
"group.com.example.shared",
kSecAttrAccessible as String:
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
SecItemAdd(query as CFDictionary, nil)
}
El uso correcto de iOS Keychain requiere seguir varias reglas clave que previenen vulnerabilidades comunes y pérdida de datos.
Use ThisDeviceOnly para todos los secretos de autenticación: kSecAttrAccessibleWhenUnlockedThisDeviceOnly garantiza que los tokens no terminen en iCloud Backup. Si un atacante obtiene acceso a la copia de seguridad, los datos de Keychain con este indicador no estarán disponibles. La excepción son los datos que deben estar disponibles en todos los dispositivos del usuario (por ejemplo, claves de cifrado para servicios propietarios), para los cuales use kSecAttrAccessibleWhenUnlocked con kSecAttrSynchronizable.
No almacene contraseñas en texto plano, guarde hashes o tokens de sesión. La Apple Security Guide (2025) recomienda nunca guardar la contraseña del usuario en Keychain en texto claro. En su lugar, guarde el refresh token recibido del servidor después de una autenticación exitosa mediante OAuth 2.0. La contraseña solo se usa para obtener el token y se elimina inmediatamente de la memoria.
Maneje los errores de Keychain correctamente: cada operación de Keychain devuelve un OSStatus que debe verificarse. Preste especial atención a errSecItemNotFound (token caducado o eliminado) y errSecAuthFailed (biometría fallida). En el primer caso, la aplicación debe solicitar una nueva autenticación; en el segundo, mostrar al usuario un método alternativo (código de acceso). Nunca ignore el estado errSecItemNotFound, provocará un fallo al intentar leer nil.
Pruebe Keychain en un dispositivo real: el simulador no tiene Secure Enclave ni admite ACL biométricos. Verifique siempre los escenarios: primer inicio, restauración desde copia de seguridad, cambio de contraseña del dispositivo, eliminación y reinstalación de la aplicación. En un dispositivo real, Keychain persiste cuando se elimina la aplicación, pero solo si no se usó el indicador kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, que se borra al eliminar el código de acceso.
Minimice la cantidad de operaciones con Keychain: cada operación de lectura o escritura es una llamada al servicio del sistema Securityd, que puede bloquear el hilo. Almacene en caché los tokens leídos en memoria durante la sesión y acceda a Keychain nuevamente solo al reiniciar la aplicación o en caso de error de autenticación (401 del servidor). iOS bloquea automáticamente Keychain cuando el dispositivo se bloquea, por lo tanto, planifique la lectura a través de LAContext con una solicitud biométrica.
Preguntas frecuentes
Sí, use la clase de protección kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly o kSecAttrAccessibleAfterFirstUnlock. Los datos estarán disponibles después del primer desbloqueo del dispositivo tras un reinicio. Para acceso automático al iniciar la aplicación (sin esperar el desbloqueo), use kSecAttrAccessibleAlways, pero esto reduce la seguridad.
Llame a SecItemDelete con un query que contenga kSecClass para cada tipo de datos. Para una limpieza completa de todos los elementos de la aplicación, ejecute: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary). Repita para kSecClassKey, kSecClassCertificate y kSecClassInternetPassword.
kSecAttrAccessible determina cuándo los datos están disponibles (al desbloquear, después del primer desbloqueo, etc.). kSecAttrAccessControl determina quién puede acceder (biometría, código de acceso, cualquier autenticación). Se combinan: primero Protection Class, luego ACL. Por ejemplo, los datos solo están disponibles al desbloquear Y solo después de Face ID.
Razones: el elemento nunca se guardó, el elemento se eliminó al quitar el código de acceso (si se usó kSecAttrAccessibleWhenPasscodeSet), la aplicación se reinstaló (Keychain persiste pero no se restaura desde la copia de seguridad en un dispositivo nuevo), el Access Group o el identificador del equipo de desarrollador cambiaron. Verifique kSecAttrService y kSecAttrAccount.
Use LAContext de LocalAuthentication: llame a context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Si devuelve true, el dispositivo admite Touch ID o Face ID. Para la ACL de Keychain, use el indicador biometryCurrentSet (solo datos biométricos actuales) o biometryAny (cualquier registro anterior).
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también