Proteger los datos: cifrado en reposo, clases de protección, llavero y biometría
En un dispositivo de Apple todo está cifrado siempre, lo cual convierte la pregunta interesante en otra completamente distinta: no si el dato está cifrado, sino en qué momentos existe la clave que lo descifra y qué hay que hacer para que aparezca. Esta lección recorre la jerarquía de claves del sistema, las clases de protección de archivos y del llavero, el uso correcto del enclave seguro y por qué la biometría solo protege de verdad cuando decide una clave y no una pantalla.
La afirmación de que una app cifra los datos en reposo, dicha sin más precisión, no aporta información en este ecosistema: el almacenamiento del dispositivo está cifrado íntegramente por hardware desde el primer arranque, sin que ninguna app tenga que hacer nada. Escribir el propio algoritmo encima de eso rara vez añade seguridad y casi siempre añade una clave que hay que guardar en algún sitio, que es exactamente el problema que se pretendía resolver. La pregunta técnicamente fértil no es si el dato está cifrado sino cuándo existe en memoria la clave capaz de descifrarlo, quién la libera y bajo qué condición. Toda la ingeniería de protección de datos en iOS consiste en elegir, para cada pieza de información, una respuesta a esa pregunta: al desbloquear, tras el primer desbloqueo, solo con este dispositivo, solo con este rostro, nunca fuera del enclave.
- Explicar la jerarquía de claves del sistema y por qué el código de acceso es la raíz de la protección real.
- Elegir la clase de protección correcta para cada archivo según cuándo necesite leerse.
- Guardar secretos en el llavero con la accesibilidad y el control de acceso adecuados.
- Usar biometría de forma que la decisión la tome el enclave y no la interfaz de tu app.
La jerarquía de claves y las clases de protección
El almacenamiento se cifra con una clave de archivo por cada archivo, y esa clave se envuelve a su vez con una clave de clase. Ahí está la sustancia: cada clase de protección determina bajo qué condición el sistema mantiene disponible su clave. Unas se derivan de una clave única grabada en el silicio, imposible de extraer; otras se derivan además del código de acceso del usuario, y por eso solo existen mientras el dispositivo está desbloqueado o desde que se desbloqueó por primera vez tras el arranque. La consecuencia práctica es contundente y explica por qué un dispositivo sin código de acceso ofrece una protección mucho menor pese a estar igualmente cifrado: sin código, la parte de la jerarquía que depende del usuario no aporta entropía y el material queda accesible en cuanto el dispositivo enciende.
Para los archivos, la elección se expresa con cuatro clases. Protección completa mantiene el contenido ilegible en cuanto la pantalla se bloquea y transcurre un breve margen, incluso con la app en ejecución en segundo plano. Completa salvo si está abierto permite que un archivo ya abierto siga escribiéndose tras el bloqueo, pensado para descargas y grabaciones largas. Completa hasta la primera autenticación, que es el valor predeterminado, deja el contenido accesible desde el primer desbloqueo tras el arranque hasta el siguiente apagado. Y ninguna deja el archivo protegido solo por el cifrado del volumen. La regla de decisión es sencilla de formular: la clase más restrictiva que permita al código leer el archivo cuando de verdad lo necesita, y la pregunta que hay que responder para aplicarla es si ese código puede ejecutarse con el dispositivo bloqueado.
// Un fichero que solo tiene sentido con el usuario delante.
try datos.write(to: url, options: [.atomic, .completeFileProtection])
// Un fichero que una tarea en segundo plano debe leer con la pantalla bloqueada.
try FileManager.default.setAttributes(
[.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication],
ofItemAtPath: url.path
)
Ese último caso concentra la mayoría de los errores reales. Una notificación remota que despierta la app, una tarea de actualización en segundo plano o una extensión que procesa contenido pueden ejecutarse con el dispositivo bloqueado, y si su base de datos está marcada con protección completa lo que obtienen no es un dato vacío sino un error de entrada y salida que suele manifestarse como una corrupción aparente. El diagnóstico correcto exige mirar el estado de la pantalla, no el código de persistencia. Por eso conviene separar el almacenamiento en dos ámbitos desde el principio: lo que la app necesita para funcionar sin usuario delante y lo que solo tiene sentido con él presente.
Las preferencias del usuario son un archivo de lista de propiedades dentro del contenedor, con la clase de protección predeterminada y sin ningún control de acceso adicional. Un testigo de sesión, una clave de API o un identificador de dispositivo escritos ahí quedan legibles desde el primer desbloqueo y viajan además en las copias de seguridad. No es un fallo del sistema: es que ese almacén nunca fue un almacén de secretos.
El llavero: accesibilidad y control de acceso
El llavero es el almacén diseñado para secretos pequeños, y su interfaz confunde porque mezcla dos ejes ortogonales que hay que razonar por separado. El primero es la accesibilidad, que responde a la misma pregunta que las clases de archivo: cuándo está disponible la clave. Ahí conviven las variantes al desbloquear, tras el primer desbloqueo y con código de acceso establecido, esta última la única que desaparece por completo si el usuario retira el código del dispositivo. El segundo eje es la persistencia entre dispositivos: cada accesibilidad tiene una variante restringida a este dispositivo que impide que el elemento salga en copias de seguridad o se sincronice, y existe además un atributo independiente para publicarlo en el llavero de iCloud. Un testigo de sesión que no debería sobrevivir a una restauración en otro teléfono necesita la variante restringida; una contraseña que el usuario espera reencontrar en su iPad necesita lo contrario.
Encima de esos dos ejes se apoya el tercero, que es el que de verdad eleva el nivel: el control de acceso. Un elemento puede exigir, para ser devuelto, que el usuario demuestre su presencia, o específicamente que se autentique con biometría, o que lo haga con biometría vinculada al conjunto de datos biométricos inscritos en ese momento, de modo que añadir un rostro o una huella nueva invalide el acceso. Esa última variante es la que convierte la biometría en una garantía real y no en una formalidad, porque impide el ataque clásico de quien conoce el código de acceso, inscribe su propia biometría y accede a los secretos de otra persona.
let control = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet],
nil
)!
var atributos: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.ejemplo.sesion",
kSecAttrAccount as String: "usuario",
kSecValueData as String: secreto,
kSecAttrAccessControl as String: control
]
let estado = SecItemAdd(atributos as CFDictionary, nil)
Conviene además fijar dos hechos operativos que ahorran incidencias. El primero es que el llavero sobrevive a la desinstalación de la app: un elemento escrito con un servicio determinado seguirá ahí cuando el usuario reinstale, lo que produce el desconcertante fenómeno de la app recién instalada que aparece con la sesión iniciada. Si eso no es lo que quieres, hay que limpiar explícitamente en el primer arranque detectado. El segundo es que el acceso al llavero desde extensiones o entre apps del mismo desarrollador requiere declarar un grupo de acceso compartido, y que ese grupo forma parte de los derechos firmados del binario, no de una configuración en tiempo de ejecución.
Al desbloquear
Disponible solo con el dispositivo desbloqueado. Es el valor por defecto razonable para todo lo que se use con el usuario delante.
Tras el primer desbloqueo
Disponible desde el primer desbloqueo tras encender. Necesario para trabajo en segundo plano y extensiones que corren con la pantalla apagada.
Solo este dispositivo
Variante que excluye el elemento de copias y sincronización. Obligatoria para claves ligadas al hardware o a una sesión que no debe migrar.
Biometría vigente
El elemento solo se devuelve tras coincidencia biométrica y se invalida si cambia el conjunto inscrito. Es la única forma de que añadir un rostro no abra la puerta.
Biometría que decide claves, no pantallas
Aquí está el error de diseño más extendido de toda la lección y merece nombrarlo sin rodeos. Muchas apps piden autenticación biométrica, reciben un valor de verdad y, si es afirmativo, muestran la pantalla protegida cuyo contenido ya estaba descifrado en memoria. Eso no es una medida de seguridad, es una cortina: cualquiera que pueda alterar el proceso —con un dispositivo comprometido, un depurador o una inyección de código— cambia el resultado de esa comparación y el dato aparece, porque el dato nunca estuvo protegido por la biometría, solo estaba oculto tras ella. La forma correcta invierte la relación: la biometría no autoriza una transición de interfaz, autoriza la liberación de una clave. Si el secreto está guardado con un control de acceso biométrico, un fallo de autenticación no devuelve nada que descifrar, y ninguna manipulación del proceso cambia esa aritmética porque la decisión la toma el enclave seguro, fuera del alcance del código de la app.
flowchart TD a[La app solicita el secreto] --> b[El llavero consulta el control de acceso] b --> c[El enclave seguro evalua la biometria] c -->|coincide| d[Libera la clave y devuelve el dato] c -->|no coincide| e[Devuelve error y ningun material] e --> f[Repliegue a codigo de acceso si asi se declaro] d --> g[La app usa el dato en memoria y lo descarta]
El mismo principio se extiende a las claves criptográficas. El enclave seguro puede generar un par de claves de curva elíptica cuya parte privada nunca abandona el hardware: la app obtiene una referencia opaca y puede pedir firmas o acuerdos de clave, pero no puede exportar el material ni copiarlo a otro dispositivo. Combinado con un control de acceso biométrico, se obtiene una propiedad muy fuerte que ningún esquema puramente software alcanza: la operación criptográfica es imposible sin ese dispositivo concreto y sin esa persona concreta. Sobre esa base se construyen la firma de operaciones sensibles, el envoltorio de claves simétricas de aplicación y los esquemas modernos de autenticación sin contraseña, y por eso conviene reservar los algoritmos de la biblioteca criptográfica de alto nivel para cifrar volúmenes de datos y dejar la custodia de la clave maestra al hardware.
Queda un frente que se olvida sistemáticamente: los datos que salen del almacén sin que nadie lo advierta. La instantánea que el sistema captura al mandar la app al segundo plano y que aparece en el conmutador de apps; el portapapeles, legible por cualquier app hasta que se declara efímero o de ámbito local; los registros de diagnóstico, donde una interpolación descuidada imprime el testigo completo en texto claro porque el sistema de registro solo redacta automáticamente cuando se le indica; las copias de seguridad; los archivos temporales en directorios que no comparten la protección del contenedor principal. Proteger el almacenamiento y descuidar estos canales es cerrar una puerta y dejar cuatro ventanas.
Antes de decidir la clase de protección de una información, pregunta si necesitas conservarla. Un testigo de corta vida que se renueva con cada sesión sustituye a un secreto permanente; un identificador derivado en el momento sustituye a uno almacenado. Cada dato que dejas de persistir elimina simultáneamente una decisión de protección, una fila del manifiesto de privacidad y un riesgo de fuga.
El desplazamiento conceptual que ordena todo este territorio es dejar de pensar en cifrar y empezar a pensar en disponibilidad condicionada de claves. El cifrado, aquí, es un hecho de infraestructura tan dado como la existencia del sistema de archivos; lo que un ingeniero decide de verdad es una función que asocia a cada pieza de información la condición bajo la cual su clave existe en memoria: el dispositivo desbloqueado, el primer desbloqueo tras el arranque, este hardware concreto, esta persona concreta, esta inscripción biométrica concreta. Formulado así, el modelo de amenaza deja de ser una lista de miedos y se convierte en un ejercicio de cuantificación: cada elección de clase define exactamente qué ventana temporal y qué capacidad necesita un atacante para obtener el dato, y cada uso de una clave que no sale del enclave elimina por construcción una familia entera de ataques, los que dependen de copiar material a otro sitio. La consecuencia arquitectónica es que la seguridad se vuelve una propiedad del lugar donde ocurre la decisión, no de la fuerza del algoritmo. Una comparación biométrica hecha en tu proceso es una sugerencia; la misma comparación hecha por el enclave para liberar una clave es una garantía, y la diferencia entre ambas no está en la criptografía, que es idéntica, sino en quién puede reescribir el resultado. Quien interioriza esto deja de preguntar qué algoritmo usar y empieza a preguntar quién custodia la clave, cuándo existe y qué la hace desaparecer, que son las tres únicas preguntas cuyas respuestas cambian el resultado de un ataque real.
El volumen ya está cifrado: lo que eliges es la clase de protección, es decir, cuándo existe la clave. Protección completa para lo que solo se usa con el usuario delante, hasta la primera autenticación para lo que necesitan el segundo plano y las extensiones. Los secretos van al llavero, con accesibilidad, variante restringida a este dispositivo si no debe migrar, y control de acceso biométrico vinculado a la inscripción vigente. La biometría debe liberar claves, nunca revelar pantallas, y las claves del enclave no se pueden exportar. Vigila instantáneas, portapapeles, registros y copias.
- Enumera todo lo que tu app persiste y asigna a cada elemento una clase de protección justificada por la pregunta de si algún código debe leerlo con la pantalla bloqueada.
- Busca en el código cualquier secreto guardado fuera del llavero y migra el más crítico, decidiendo explícitamente accesibilidad, variante de dispositivo y control de acceso.
- Prueba tu app con una tarea en segundo plano y el dispositivo bloqueado; provoca deliberadamente el fallo de lectura por protección completa y comprueba que tu código lo distingue de una corrupción.
- Convierte una comprobación biométrica que hoy solo controla la navegación en una que gobierne la recuperación de la clave, y verifica que sin autenticación no existe material descifrable en memoria.
- Revisa instantáneas del conmutador, portapapeles y registros en busca de fugas: interpola un secreto en un registro a propósito, observa qué se imprime y corrige la política de redacción.