Inicio / Artículos

Autenticación biométrica avanzada en iOS y Android con claves vinculadas al hardware

El Face ID por sí solo no demuestra nada a tu servidor. Utiliza las claves de Secure Enclave y Android Keystore para firmar desafíos únicos que permitan una autenticación verdaderamente reforzada.

Editor: REACTAPP.TOP12 min de lectura2762 palabras

Fuentes: Biometrics, the Secure Enclave, and Auth Tokens: Three Ideas That Sound the Same But Aren’t

Las especificaciones del producto suelen indicar “Face ID desbloquea la aplicación”, y para los usuarios esa descripción es suficiente. Pero cuando se trata de desarrollar sistemas de pagos, registros médicos, consolas administrativas o flujos de inicio de sesión, esto oculta lo que realmente importa: un escaneo biométrico nunca llega al backend, nunca genera un token y nunca proporciona a tu código un modelo de rostro o huella dactilar. Lo que realmente obtiene una aplicación bien diseñada es el permiso para que el hardware protegido utilice una clave privada que tu código no puede leer, de modo que esa clave pueda firmar un desafío único que el servidor luego verifica. Esta guía separa las tres conceptos que a menudo se confunden (solicitudes biométricas, claves respaldadas por hardware y tokens de autenticación) e explica en detalle un flujo de registro seguido de firma en ambas plataformas.

Por qué un token por sí solo es una promesa débil en un dispositivo móvil

A pesar de todo eso, un endpoint sensible necesita confiar en una afirmación precisa: la llamada proviene de un teléfono que posee una clave registrada previamente por el servidor, y de una persona que aprobó su uso hace unos momentos.

Compare eso con “esta solicitud contiene un token”. Cualquier valor que se encuentre en la memoria o almacenamiento de la aplicación puede ser copiado y reutilizado desde otro lugar. Una clave privada que se genera dentro de hardware protegido y que nunca puede ser exportada es un objetivo mucho más difícil, ya que robarla implica atacar el hardware en lugar de leer un texto.

Antes de crear cualquier cosa, es útil definir qué demuestra cada componente:

  • Prompt biométrico: el teléfono aceptó Face ID, Touch ID o un biométrico robusto de Android.
  • Clave de hardware segura: una clave privada protegida generó la firma.
  • Tokén de autenticación: su servidor concedió una sesión o algún tipo de acceso.

Un diseño sólido utiliza los tres métodos, cada uno para su función específica.

HTTPS protege el canal, no la identidad

HTTPS encripta el tráfico entre la aplicación y su API, de modo que alguien que utilice una red Wi-Fi en un café no pueda leer ni modificar fácilmente las solicitudes.

phone ── encrypted HTTPS connection ──► server

Es obligatorio, pero también es insuficiente por sí solo. Si un atacante ya ha obtenido un tokén de acceso válido, TLS entregará fiel y seguramente ese tokén robado a su servidor. Nada en la conexión encriptada indica al backend si el tokén proviene del dispositivo legítimo o de una copia.

Por lo tanto, los principios básicos son: utilizar HTTPS en cada llamada, validar los certificados de la forma habitual, mantener actualizada la capa TLS y luego añadir un mecanismo de autenticación adicional que proporcione información verificable sobre el dispositivo y la acción del usuario.

El anclaje de certificados es una herramienta limitada

El anclaje restringe la aplicación a un certificado o identidad de clave pública conocida. En aplicaciones con control estricto, puede reducir ciertos escenarios de ataque tipo intermediario.

No permite que un token portador se comporte como una credencial respaldada por hardware. Además, conlleva un riesgo operativo real: si se envía un anclaje incorrecto o se pasa por alto la rotación de certificados, todas las copias instaladas de la aplicación dejarán de comunicarse con su API al instante. Úselo solo si su modelo de amenazas lo justifica y su proceso de lanzamiento puede gestionar la rotación de claves de manera fiable, y nunca lo considere un sustituto de la autenticación vinculada al dispositivo.

Una solicitud biométrica es solo un sí o no local

En iOS, la aplicación solicita a un LAContext que muestre la pantalla de autenticación del sistema; en Android, el equivalente es BiometricPrompt. El sistema operativo realiza el escaneo y comunica si fue exitoso o no. El fragmento a continuación está en Swift: evalúa la política que solo utiliza datos biométricos junto con una cadena de motivo y inicia la transferencia si la verificación tiene éxito.

let context = LAContext()
let policy = LAPolicy.deviceOwnerAuthenticationWithBiometrics
context.evaluatePolicy(policy, localizedReason: "Approve this transfer") { success, error in
    guard success else { return }
    startTransfer()
}

Tu código nunca accede a la geometría del rostro ni a las imágenes de huellas dactilares. Apple describe a LocalAuthentication como un componente que funciona con Secure Enclave y que devuelve al aplicativo únicamente un resultado de éxito o fracaso (Referencia del framework LocalAuthentication).

Eso es perfectamente suficiente para proteger contenido privado dentro de la aplicación. Sin embargo, como prueba ante un servidor, resulta insuficiente por tres razones:

  • El resultado solo indica “el dispositivo aceptó el cheque”; no contiene información sobre quién o qué cuenta.
  • El backend no tiene forma de verificar un valor booleano generado en el cliente.
  • Si el proceso de la aplicación es comprometido, un atacante puede omitir o falsificar la llamada de retorno y simplemente reutilizar un token que ya existe.

Lo importante de este patrón es que success es solo una marca en su propio espacio de memoria. Un diseño más robusto utiliza la verificación biométrica para desbloquear una única operación criptográfica cuyo resultado el servidor puede verificar de forma independiente.

Los almacenes de claves hardware mantienen la clave privada fuera de su proceso

Los dispositivos Apple incluyen el Secure Enclave, un subsistema aislado que puede generar y utilizar claves privadas sin exponer nunca el material de la clave en bruto al procesador principal de la aplicación. Según la documentación de Apple, la clave permanece dentro del enclave y la aplicación solo recibe resultados como firmas (Guía de claves del Secure Enclave de Apple).

El equivalente en la otra plataforma es el Android Keystore. Dependiendo del hardware, las claves pueden estar respaldadas por un Entorno de Ejecución Confiable (TEE) o por un elemento seguro StrongBox dedicado. Tenga cuidado con la denominación: “Titan M” es el chip de los teléfonos Google Pixel, no una API general de Android, y no debe dar por sentado que todos los dispositivos lo tengan. Desde el punto de vista de la aplicación, lo importante es cómo se respalda una clave y qué reglas de uso se le aplican.

Para una acción de alto valor, la clave debe cumplir cuatro reglas:

  1. Se genera en este dispositivo específico.
  2. Cada uso requiere una nueva aprobación biométrica sólida.
  3. La parte privada nunca puede ser exportada.
  4. Solo la clave pública se almacena en el servidor.

En Android, BiometricPrompt.CryptoObject solo puede permitir una operación criptográfica cuando la clase de autenticador es BIOMETRIC_STRONG, y la clave puede configurarse para exigir una nueva aprobación en cada uso (Referencia de BiometricPrompt, Descripción general del Keystore).

El protocolo: registrar una clave una vez y firmar nuevos desafíos posteriormente

Todo el esquema consta de dos fases: registro y uso.

Registro

  1. La aplicación genera un par de claves respaldado por hardware.
  2. Sube la clave pública al servidor a través de HTTPS.
  3. El servidor registra la clave pública junto con la cuenta y un identificador del dispositivo.
  4. La clave privada permanece en el teléfono, dentro de hardware seguro.

Enviar la clave pública no es perjudicial: permite verificar firmas pero no generarlas. Solo la clave privada puede firmar, y nunca abandona el hardware protegido.

Aprobar una solicitud sensible

  1. La aplicación solicita al servidor un desafío aleatorio único.
  2. El usuario presenta la solicitud biométrica.
  3. El hardware seguro firma el desafío.
  4. La aplicación devuelve el ID del desafío y la firma.
  5. El servidor verifica la firma con respecto a la clave pública almacenada.
  6. El servidor acepta o rechaza la acción, y puede emitir un token.

El desafío radica en el punto donde la mayoría de las implementaciones cometen errores. Debe ser aleatorio, caducar rápidamente y poder utilizarse exactamente una vez. También debe estar vinculado a la cuenta, a la clave del dispositivo específico y a la acción que se está aprobando (por ejemplo, “transferir 200 EUR a la cuenta X”). Si se omite alguno de estos elementos, una firma capturada puede ser reutilizada posteriormente para una solicitud diferente, lo cual constituye un clásico ataque de reutilización. Vincular la acción también impide que una firma recopilada para una operación inofensiva sea redirigida a una peligrosa.

iOS: una clave Secure Enclave que exige Face ID en cada uso

En iOS, se crea la clave directamente dentro del Secure Enclave y se adjunta un objeto de control de acceso que requiere datos biométricos cada vez que se utiliza la clave. En el código Swift a continuación, se observan tres opciones. kSecAttrAccessibleWhenUnlockedThisDeviceOnly impide que la clave sea migrada a copias de seguridad u otros dispositivos. Las banderas .privateKeyUsage y .biometryCurrentSet exigen la aprobación biométrica para firmar y vinculan la clave a los rostros o dedos registrados actualmente. Además, kSecAttrTokenIDSecureEnclave es lo que coloca la clave en el hardware; el Secure Enclave solo admite claves de curva elíptica de 256 bits (P-256), por lo que el tipo y tamaño de la clave se establecen de esa manera.

// 1. Create access control requiring Face ID / Touch ID for every use
var error: Unmanaged<CFError>?
guard let accessControl = SecAccessControlCreateWithFlags(
    kCFAllocatorDefault,
    kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
    [.privateKeyUsage, .biometryCurrentSet],
    &error
) else { return }
// 2. Generate the key pair in the Secure Enclave
let attributes: [String: Any] = [
    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
    kSecAttrKeySizeInBits as String: 256,
    kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
    kSecPrivateKeyAttrs as String: [
        kSecAttrIsPermanent as String: true,
        kSecAttrApplicationTag as String: "com.example.keys.transfer",
        kSecAttrAccessControl as String: accessControl
    ]
]
guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else { return }

Una consecuencia práctica de .biometryCurrentSet es que al registrar un nuevo huella dactilar o restablecer Face ID, la clave se invalida. Por lo general, eso es lo que se desea para los pagos, pero implica que la aplicación debe manejar adecuadamente la ausencia de clave y hacer que el usuario se registre nuevamente.

Suba la clave pública una vez que el usuario haya completado el inicio de sesión normal en la cuenta. Más tarde, cuando el backend envíe un desafío aleatorio para una transferencia, firme este con la clave privada. Usted mismo no mostrará ninguna solicitud: debido a las marcas de control de acceso, la llamada de firma activará automáticamente Face ID o Touch ID.

let algorithm: SecKeyAlgorithm = .ecdsaSignatureMessageX962SHA256
var signError: Unmanaged<CFError>?
// 3. This call automatically triggers the Face ID prompt because of the accessControl flags
guard let signature = SecKeyCreateSignature(
    privateKey,
    algorithm,
    challengeData as CFData,
    &signError
) else { return }
api.approve(challengeId, signature)

Si el usuario cancela o falla en el escaneo, SecKeyCreateSignature devuelve nil y no existe firma alguna que enviar, por lo que no hay bandera de éxito que un atacante pueda modificar.

Android: una clave del Keystore desbloqueada a través de BiometricPrompt

El Kotlin que se muestra a continuación crea una clave EC de solo firma en el proveedor AndroidKeyStore. setUserAuthenticationRequired(true) hace obligatoria la autenticación, y setUserAuthenticationParameters(0, AUTH_BIOMETRIC_STRONG) establece el período de validez en cero segundos, lo que significa que cada uso requiere una nueva verificación biométrica segura en lugar de un período de gracia después del último desbloqueo.

val spec = KeyGenParameterSpec.Builder(
    "transfer-key",
    KeyProperties.PURPOSE_SIGN
).setDigests(KeyProperties.DIGEST_SHA256)
    .setUserAuthenticationRequired(true)
    .setUserAuthenticationParameters(
        0,
        KeyProperties.AUTH_BIOMETRIC_STRONG
    ).build()
val generator = KeyPairGenerator.getInstance(
    KeyProperties.KEY_ALGORITHM_EC,
    "AndroidKeyStore"
)
generator.initialize(spec)
val publicKey = generator.generateKeyPair().public.encoded

Envíe la publicKey codificada al servidor posteriormente, después de que el usuario inicie sesión normalmente, y guárdela junto con los registros del usuario y del dispositivo.

Cuando el servidor emita posteriormente un desafío para una transferencia, inicialice un objeto Signature, envuélvalo en un CryptoObject y páselo al sistema de solicitud. Solo dentro de onAuthenticationSucceeded introduzca los bytes del desafío y genere la firma.

val signature = Signature.getInstance("SHA256withECDSA")
signature.initSign(privateKey)
val crypto = BiometricPrompt.CryptoObject(signature)
prompt.authenticate(promptInfo, crypto)
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
    val signed = result.cryptoObject!!.signature!!
        .apply { update(challenge) }.sign()
    api.approve(challengeId, signed)
}

El diálogo no constituye el límite de seguridad; lo es el CryptoObject. El almacén de claves se negará a realizar la operación de firma a menos que la verificación biométrica asociada tenga éxito, y el material de la clave permanece dentro del almacén protegido. Tu aplicación solo conserva la firma resultante.

En el servidor, verifica esa firma con la clave pública registrada. Si coincide con un desafío no vencido y no utilizado, aprueba la transferencia o emite un token de corta duración limitado a esa acción.

Dónde deben quedar los tokens de autenticación

Verificar una firma respaldada por hardware no elimina los tokens. Después de la comprobación, es completamente razonable emitir un token de acceso regular, ya que los tokens siguen siendo la forma práctica de autorizar la mayor parte del tráfico de API. Si está diseñando la parte relacionada con los tokens del sistema, las consideraciones presentes en nuestra guía de estrategias para tokens de actualización también son aplicables aquí.

Mantenga esos tokens en almacenamiento protegido por la plataforma: el Keychain en iOS y el almacenamiento respaldado por Keystore en Android. Los tokens de actualización de larga duración nunca deben guardarse en preferencias simples, archivos comunes o almacenamiento accesible desde JavaScript, lo cual es especialmente importante en aplicaciones React Native donde AsyncStorage es la opción predeterminada, fácil de usar pero sin cifrar.

La pregunta de diseño entonces es con qué frecuencia se debe solicitar una nueva firma por hardware:

  • En cada llamada a la API: casi siempre es demasiado lento e irritante.
  • Al iniciar cada aplicación: es una opción razonable para un bloqueo de aplicaciones privado.
  • En pagos, ediciones de perfil, exportaciones de datos o al aprobar un nuevo dispositivo: con frecuencia es la opción óptima.
  • Al usar cada token de actualización: vale la pena para cuentas de alto riesgo, siempre y cuando la experiencia del usuario pueda manejar las solicitudes.

Considere la firma como la prueba adicional y el token como el permiso de corta duración que se deriva de ella.

Límites de las claves respaldadas por hardware

Este enfoque es sólido, pero no es una solución universal:

  • No sirve de nada contra un usuario legítimo que desbloquea su propio teléfono y aprueba una acción dañina, por ejemplo bajo presión de ingeniería social.
  • No hace que un dispositivo rooteado o con jailbreak sea confiable; solo dificulta la extracción y clonación de la clave.
  • No reemplaza la detección de fraudes en el servidor, la limitación de frecuencia ni la supervisión de anomalías.
  • No elimina la necesidad de recuperación de cuentas, ya que las personas pierden sus teléfonos y vuelven a registrar sus datos biométricos.
  • Demuestra el control de una clave registrada tras la aprobación local, no la identidad real de la persona.
  • Planifique desde el principio la ruta de recuperación, haciéndola intencionadamente más lenta y sujeta a una verificación más estricta que la ruta habitual. Cuando desaparece una clave del dispositivo, se restablece la pantalla de bloqueo o cambia el registro biométrico, su aplicación debe estar lista para registrar una clave nueva y su servidor debe revocar la antigua. La documentación de Android indica que las claves vinculadas a la autenticación pueden invalidarse permanentemente cuando cambia la pantalla de bloqueo segura o cuando se modifica el registro biométrico, dependiendo de cómo estuviera configurada la clave (referencia de KeyProtection.Builder).

    Cómo se relacionan las claves de acceso con este diseño

    Las claves de acceso se basan en los mismos principios: un par de claves pública/privada y un desafío del servidor firmado con la clave privada. Para el inicio de sesión en sitios web y cuentas, suelen ser la mejor opción disponible, ya que el sistema operativo y la plataforma se encargan de la mayor parte de las tareas complejas. Las claves de acceso son un protocolo estandarizado y multiplataforma que se basa en este tipo de flujo de firma respaldado por hardware. Una particularidad: muchas claves de acceso se sincronizan entre los dispositivos del usuario a través del gestor de credenciales de la plataforma, por lo que no siempre están vinculadas a un único teléfono, como ocurre con una clave de Keystore o Secure Enclave que no se puede exportar.

    Esa distinción sugiere una división simple: utilizar una clave de aplicación respaldada por hardware cuando una aplicación nativa necesita una prueba vinculada al dispositivo para realizar una acción específica, y emplear claves de acceso para el inicio de sesión sin contraseñas en entornos modernos, siempre que su producto las soporte. En cualquier caso, la función del servidor es la misma: verificar una firma con una clave pública en la que ya confía.

    Elegir la herramienta adecuada para cada necesidad

    • Proteger los datos en tránsito: HTTPS.
    • Mantener al usuario conectado: un token de acceso de corta duración junto con un token de actualización protegido.
    • Proteger el contenido privado al abrir la aplicación: Face ID, Touch ID o BiometricPrompt.
    • Aprobar un pago u otra acción de alto riesgo: una firma por hardware protegida con biometría.
    • Inicio de sesión sin contraseñas: claves de acceso.
    • Reducir el riesgo de intercepción TLS: fijación de certificados.
  • Mantener el contenido de la carga privado incluso frente a su propio servidor: cifrado de extremo a extremo del contenido de la carga.
  • Puntos clave

    Los escaneos de Face ID y huellas dactilares no autentican una solicitud del backend. Ellos autorizan el uso de una clave; el hardware seguro utiliza esa clave para firmar un nuevo desafío del servidor; y el servidor verifica la firma antes de decidir qué puede hacer el usuario.

    • Biométrico: aprobación local, invisible para el servidor.
    • Clave de hardware: prueba de posesión que no puede ser copiada del dispositivo.
    • Tokén: concesión a corto plazo de permisos por parte del servidor.

    Mantenga estas funciones separadas, asocie cada desafío a una cuenta, clave y acción, y planifique la invalidación y recuperación de las claves. El resultado es un diseño de autenticación móvil que es más fácil de comprender y considerablemente más difícil de falsificar.

    Lecturas relacionadas