Respuesta breve. Una clave respaldada por hardware se genera dentro de un chip independiente o de un procesador aislado y nunca sale de él en texto en claro; tu código guarda un identificador (handle) y pide al hardware que firme. Eso garantiza bien una cosa: la clave privada no se puede copiar fuera del dispositivo, así que robar una imagen de disco, una copia de seguridad o un volcado de memoria no sirve de nada. No impide que un malware del mismo dispositivo pida al hardware que firme, salvo que exijas la presencia del usuario o biometría en cada uso. Los algoritmos admitidos son pocos (P-256 en todas partes, RSA en algunas plataformas, ML-DSA y ML-KEM poscuánticos en el Secure Enclave de Apple desde las versiones 26). La atestación permite que un servidor verifique que una clave reside de verdad en ese hardware, mediante una cadena de certificados hasta el fabricante: las raíces de Google para Android, las de Yubico para las YubiKey, las del fabricante del TPM para los TPM. Perder el dispositivo significa perder la clave, así que diseña desde el principio el registro de varias claves y su rotación.
Las plataformas
| Dónde reside la clave | Algoritmos habituales | Atestación | Comprobación del usuario | |
|---|---|---|---|---|
| Secure Enclave de Apple | Un coprocesador dedicado en los chips Apple silicon | P-256; ML-KEM-768/1024 y ML-DSA-65/87 (CryptoKit, OS 26+) | App Attest en iOS; ninguna para claves arbitrarias | Touch ID, Face ID o código de acceso mediante control de acceso |
| Android Keystore, TEE | El entorno de ejecución de confianza del SoC principal | RSA, EC, AES, HMAC | Atestación de claves hasta las raíces de Google | Credencial de la pantalla de bloqueo o biometría, en cada uso o durante un intervalo de tiempo |
| Android StrongBox | Un elemento seguro embebido o un enclave seguro integrado (Android 9+) | RSA 2048, P-256, AES, HMAC-SHA256, 3DES | Igual, con el nivel de seguridad StrongBox | Igual |
| TPM 2.0 | Un chip discreto, un TPM de firmware o un TPM virtual | RSA 2048+, P-256, P-384 (según el fabricante) | AK certificada por la EK; certificado de la EK emitido por el fabricante | Valores de autorización y políticas |
| YubiKey PIV | Un applet de tarjeta inteligente en el token | RSA 1024/2048, P-256, P-384; RSA 3072/4096, Ed25519 y X25519 desde el firmware 5.7 | Certificado de atestación de la ranura f9, encadenado a Yubico | Política de PIN y política de toque por clave |
| HSM en la nube | HSM operados por el proveedor | Consulta la firma con KMS en la nube | Declaraciones de atestación de Google Cloud HSM | IAM, no personas |
Las llaves de acceso son el uso más común de todo esto: un autenticador de plataforma es una de las cuatro primeras filas con una interfaz WebAuthn (llaves de acceso para desarrolladores).
Secure Enclave de Apple, probado en este Mac
El Secure Enclave es el procesador de claves aislado de Apple. Con CryptoKit, crear una clave ocupa una línea. Lo ejecutamos en un Apple M4 Max con macOS 27.0.1 y Swift 6.4, como una simple herramienta de línea de comandos, sin derechos (entitlements) especiales:
// Secure Enclave from a command-line tool: create a P-256 signing key, sign, export the public key,
// and look at what the "private key" object actually contains.
import CryptoKit
import Foundation
print("SecureEnclave.isAvailable: \(SecureEnclave.isAvailable)")
let key = try SecureEnclave.P256.Signing.PrivateKey()
let message = Data("release 1.4.2".utf8)
let signature = try key.signature(for: message)
print("public key (x9.63): \(key.publicKey.x963Representation.count) bytes")
print("signature (DER): \(signature.derRepresentation.count) bytes, raw r||s: \(signature.rawRepresentation.count) bytes")
print("verifies with the public key: \(key.publicKey.isValidSignature(signature, for: message))")
// dataRepresentation is an opaque blob: the key encrypted by the Secure Enclave for this device.
let blob = key.dataRepresentation
print("dataRepresentation: \(blob.count) bytes (opaque, usable only by this device's Secure Enclave)")
let restored = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: blob)
print("restored from the blob signs too: \(restored.publicKey.isValidSignature(try restored.signature(for: message), for: message))")
// A software key from the same API family, for comparison: its raw bytes are the private scalar.
let software = P256.Signing.PrivateKey()
print("software key rawRepresentation: \(software.rawRepresentation.count) bytes (the private scalar itself)")
SecureEnclave.isAvailable: true
public key (x9.63): 65 bytes
signature (DER): 71 bytes, raw r||s: 64 bytes
verifies with the public key: true
dataRepresentation: 284 bytes (opaque, usable only by this device's Secure Enclave)
restored from the blob signs too: true
software key rawRepresentation: 32 bytes (the private scalar itself)
El dataRepresentation de 284 bytes es lo que una aplicación guarda (en el llavero o en un archivo) para volver a usar la clave más adelante; está envuelto por el Secure Enclave y solo el enclave de ese dispositivo puede usarlo. No hay ninguna forma de pedir el escalar privado: el código que lo intenta no compila.
export.swift:5:11: error: value of type 'SecureEnclave.P256.Signing.PrivateKey' has no member 'rawRepresentation'
La guía de Apple Proteger claves con el Secure Enclave, escrita para la API SecKey del framework Security, sigue diciendo que «funciona solo con claves de curva elíptica NIST P-256». CryptoKit fue más allá: SecureEnclave.MLDSA65, MLDSA87, MLKEM768 y MLKEM1024 están disponibles desde iOS y macOS 26. En este Mac:
let mldsa = try SecureEnclave.MLDSA65.PrivateKey()
let signature = try mldsa.signature(for: message)
print("ML-DSA-65 in the Secure Enclave: public key \(mldsa.publicKey.rawRepresentation.count) bytes, signature \(signature.count) bytes")
print("verifies: \(mldsa.publicKey.isValidSignature(signature, for: message))")
let mlkem = try SecureEnclave.MLKEM768.PrivateKey()
let encapsulated = try mlkem.publicKey.encapsulate()
let shared = try mlkem.decapsulate(encapsulated.encapsulated)
print("ML-KEM-768 in the Secure Enclave: ciphertext \(encapsulated.encapsulated.count) bytes, shared secrets match: \(shared == encapsulated.sharedSecret)")
ML-DSA-65 in the Secure Enclave: public key 1952 bytes, signature 3309 bytes
verifies: true
ML-KEM-768 in the Secure Enclave: ciphertext 1088 bytes, shared secrets match: true
Los tamaños son los valores de FIPS 204 y FIPS 203. Para claves arbitrarias, Apple no ofrece ninguna atestación que un servidor pueda comprobar; en iOS, App Attest (DeviceCheck) certifica la aplicación y una clave del Secure Enclave que genera con ese fin.
Android Keystore: TEE y StrongBox
En Android, las claves generadas en el Android Keystore son «no exportables»: «el material de clave nunca entra en el proceso de la aplicación» y, cuando el hardware seguro del dispositivo admite el algoritmo y los parámetros de la clave, «su material de clave nunca se expone fuera del hardware seguro». Hay dos niveles de hardware:
- TEE (
KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT): un entorno aislado en el procesador principal. - StrongBox (Android 9 y posteriores,
setIsStrongBoxBacked(true)): «SE embebidos o Secure Enclaves integrados (iSE), que ofrecen un aislamiento y una resistencia a la manipulación más fuertes que el TEE», con su propia CPU, su propio almacenamiento, su generador de números aleatorios y un temporizador seguro. Solo admite «RSA 2048, AES 128 y 256, ECDSA, ECDH P-256, HMAC-SHA256» y Triple DES, y Google advierte de que «es más lento y tiene más limitaciones de recursos» y de que «para la mayoría de las aplicaciones, StrongBox no es necesario». Una solicitud no admitida lanzaStrongBoxUnavailableException.
Comprueba dónde reside finalmente una clave con KeyInfo.getSecurityLevel() (API 31+; isInsideSecureHardware() en versiones anteriores) y vincula su uso al usuario con setUserAuthenticationParameters(), ya sea por operación o durante un intervalo de tiempo tras el desbloqueo. Una advertencia de la misma página: el hardware «no suele aplicar autorizaciones con intervalos de validez temporal, porque normalmente no tiene un reloj de tiempo real independiente y seguro».
La atestación de claves se lo demuestra a tu servidor. El dispositivo devuelve una cadena de certificados para la clave; el primer certificado que lleva la extensión de atestación (OID 1.3.6.1.4.1.11129.2.1.17) describe la clave, sus propiedades y su nivel de seguridad. Las reglas de la atestación de claves que vale la pena trasladar a tu verificador:
- Verifica en un servidor, nunca en el propio dispositivo.
- La raíz debe ser una de las raíces de atestación de Google, publicadas en
android.googleapis.com/attestation/root. Una raíz ECDSA P-384, «Key Attestation CA1», «empezará a firmar cadenas de certificados de atestación el 1 de febrero de 2026», junto a la raíz RSA de siempre; acepta ambas. - Comprueba cada certificado con la lista de revocación de Google en
android.googleapis.com/attestation/status. - «Solo se puede confiar en la primera aparición de la extensión en la cadena»; las posteriores pueden proceder de un atacante que alarga la cadena.
- Exige que
attestationSecurityLevelseaTrustedEnvironmentoStrongBox; una cadena hasta cualquier otra raíz, o una atestación de software, no dice nada sobre el hardware.
Google recomienda su biblioteca de verificación en Kotlin en lugar de verificadores propios. La firma C2PA de la cámara del Pixel es un ejemplo construido sobre esto: las claves de firma de claims se «generan y almacenan con Android StrongBox en el chip de seguridad Titan M2», y los certificados solo se emiten después de la atestación de claves (credenciales de contenido C2PA).
TPM 2.0: claves, políticas y atestación en un TPM de software
Un TPM es el equivalente en PC y servidores. Para mostrar su funcionamiento sin hardware, el laboratorio ejecutó swtpm 0.10.2 (un TPM de software que, por construcción, no protege nada) con tpm2-tools 5.8 en debian:forky-20261005-slim. Se crea una clave de firma bajo una clave primaria, con atributos que la vinculan a este TPM:
tpm2_createprimary -Q -C o -g sha256 -G ecc256 -c primary.ctx
tpm2_create -Q -C primary.ctx -G ecc256:ecdsa-sha256 -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign" -u sig.pub -r sig.priv
tpm2_load -Q -C primary.ctx -u sig.pub -r sig.priv -c sig.ctx
tpm2_readpublic -c sig.ctx -f pem -o sig.pem
tpm2_sign -Q -c sig.ctx -g sha256 -f plain -o msg.sig msg.txt
openssl dgst -sha256 -verify sig.pem -signature msg.sig msg.txt
attributes:
value: fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign
sig.priv: 128 bytes (encrypted by the TPM, usable only under this parent)
Verified OK
fixedtpm y fixedparent significan que la clave nunca se puede duplicar a otro TPM ni a otra clave padre; sensitivedataorigin, que la generó el TPM. sig.priv es la clave cifrada con la clave padre y se puede guardar en disco sin riesgo. La firma es ECDSA corriente en DER, y OpenSSL la verifica con la clave pública exportada.
La atestación usa una clave de atestación (AK) aparte, con el atributo restricted. tpm2_certify hace que la AK firme una declaración sobre otra clave; esa declaración incluye el nonce del verificador:
tpm2_create -Q -C primary.ctx -G ecc256:ecdsa-sha256:null -a "fixedtpm|fixedparent|sensitivedataorigin|userwithauth|restricted|sign" -u ak.pub -r ak.priv
tpm2_load -Q -C primary.ctx -u ak.pub -r ak.priv -c ak.ctx
tpm2_readpublic -Q -c ak.ctx -f pem -o ak.pem
printf 'server-nonce-7f3a' > nonce.bin
tpm2_certify -Q -c sig.ctx -C ak.ctx -g sha256 -q nonce.bin -o attest.bin -s attest.sig -f plain
openssl dgst -sha256 -verify ak.pem -signature attest.sig attest.bin
tpm2_print -t TPMS_ATTEST attest.bin
Verified OK
magic: ff544347
type: 8017
extraData: 7365727665722d6e6f6e63652d37663361
name: 000b2d7a257558828e1caf174b67daabff261f8c02077768266be3cdc58015de4bee
qualifiedName: 000b6d68f45fa311b20dc86b37e1659c1f06055e5641aa6a03e28cbb2caad6646fcd
name of the signing key: 000b2d7a257558828e1caf174b67daabff261f8c02077768266be3cdc58015de4bee
La estructura empieza con el valor mágico 0xff544347 («TPM_GENERATED»), el tipo 0x8017 es una declaración de certificación, extraData es nuestro nonce en hexadecimal y name es el hash del área pública y los atributos de la clave certificada: coincide con la clave de firma. ¿Por qué confiar en ella? Porque una clave restringida se niega a firmar datos que empiezan con el valor mágico TPM_GENERATED, salvo que el propio TPM haya producido esos datos:
ordinary data (msg.txt): signed by the attestation key
ERROR: Esys_Sign(0x3E0) - tpm:parameter(3):invalid ticket
En un despliegue real, la cadena continúa: la AK está ligada a la clave de aprobación (EK) del TPM, cuyo certificado emitió el fabricante del TPM, y el verificador comprueba esa cadena; el formato de declaración de atestación tpm de WebAuthn funciona así. Un TPM de software no tiene ese certificado, y precisamente por eso no se debe confiar en su atestación.
YubiKey PIV y atestación
El applet PIV de una YubiKey guarda las claves en ranuras (9a autenticación, 9c firma, 9d gestión de claves, 9e autenticación de tarjeta) con una política de PIN y una política de toque fijadas en el momento de generarlas. La atestación PIV demuestra «que una determinada clave asimétrica se generó en el dispositivo y no se importó»: la YubiKey firma un certificado para la clave con su clave de atestación de la ranura f9, que viene acompañada de un certificado de una CA PIV de Yubico. Los comandos, de yubico-piv-tool 2.7.3 (no tenemos ningún token en el laboratorio, así que solo se comprobó su --help):
yubico-piv-tool -a generate -s 9c -A ECCP256 --pin-policy=always --touch-policy=always -o 9c.pub
yubico-piv-tool -a attest -s 9c > 9c-attest.pem
yubico-piv-tool -a read-certificate -s f9 > f9-intermediate.pem
El certificado de atestación lleva extensiones de Yubico: versión de firmware (1.3.6.1.4.1.41482.3.3), número de serie (…3.7), política de PIN y de toque (…3.8), factor de forma (…3.9) y estado FIPS o CSPN (…3.10, …3.11). Un verificador comprueba la cadena desde el certificado de la ranura, pasando por el certificado f9, hasta la raíz de Yubico, y después lee las políticas: «toque siempre» significa que una persona estuvo presente físicamente en cada firma. Yubico está rotando su raíz: «las YubiKey con firmware 5.7.4 o posterior estarán firmadas por nuestra nueva CA raíz», así que los verificadores necesitan ambas raíces. El CA/Browser Forum acepta exactamente este tipo de prueba para las claves de firma de código (Authenticode), y OpenSSH usa los mismos tokens, pero a través de FIDO2 (ssh-keygen).
Lo que las claves en hardware no te dan
- Protección frente al código que se ejecuta como el usuario. Un malware en el dispositivo puede pedir al hardware que firme; lo que no puede es llevarse la clave. Exige presencia del usuario (toque), verificación del usuario (PIN, biometría) o una confirmación para las claves que importan.
- Disponibilidad. Un dispositivo perdido, borrado o averiado se lleva la clave consigo, y borrar un TPM (cosa que hacen algunas actualizaciones de firmware y reinstalaciones) invalida las claves que dependen de él. Registra al menos dos autenticadores, mantén una vía de recuperación y planifica la rotación.
- Agilidad de algoritmos. Obtienes lo que implementa el chip. Muchas plataformas siguen ofreciendo solo P-256 y RSA; si un protocolo necesita Ed25519 o secp256k1, compruébalo antes de diseñar en torno al hardware.
- Confianza en lo que firma el hardware. Una clave segura firma lo que le entregue el sistema operativo; la pantalla que muestra «aprobar pago» pertenece a ese mismo sistema operativo, que puede estar comprometido. Por eso existen las billeteras de hardware con pantalla propia.
- Prueba sin atestación. Si tu servidor no verifica una cadena de atestación, «la clave está en hardware» es solo lo que dice el cliente.
Lista de comprobación para usar claves en hardware
- Genera las claves en el dispositivo; nunca importes una clave que quieras presentar como respaldada por hardware.
- Elige el nivel que necesitas: Secure Enclave o TEE para la mayoría de las aplicaciones, StrongBox o un elemento seguro para claves de alto valor, un HSM para las claves de servidor.
- Vincula el uso al usuario (biometría o PIN en cada uso) cuando una firma autorice algo.
- Verifica la atestación en el servidor: raíces (actuales y rotadas), revocación, nonce, nivel de seguridad y propiedades de la clave.
- Guarda solo claves públicas e identificadores opacos; cuenta con que las claves desaparezcan y permite volver a registrarlas.
Fuentes
- Apple: Proteger claves con el Secure Enclave, CryptoKit
SecureEnclave.MLDSA65ySecureEnclave.MLKEM768 - Android: sistema Android Keystore, atestación de claves, esquema de atestación
- Especificación TCG TPM 2.0 Library (TPMS_ATTEST, claves restringidas); tpm2-tools 5.8, swtpm 0.10.2
- Yubico: atestación PIV, YubiKey y PIV (algoritmos según el firmware), yubico-piv-tool 2.7.3
- Google Security Blog sobre la firma C2PA del Pixel