Aller au contenu
ecdsa.com
← Tous les articles

Clés matérielles : Secure Enclave, StrongBox, TPM, PIV

Nous avons créé des clés dans la Secure Enclave de ce Mac, y compris des clés post-quantiques, ainsi que dans un TPM logiciel avec attestation, et nous avons lu les règles d’attestation d’Android et de Yubico. La promesse commune est limitée, mais précieuse : la clé ne peut pas être copiée.

En bref. Une clé protégée par le matériel est générée dans une puce distincte ou un processeur isolé, et n’en sort jamais en clair ; votre code détient une référence (handle) et demande au matériel de signer. Cela garantit bien une chose : la clé privée ne peut pas être copiée hors de l’appareil, si bien qu’une image disque, une sauvegarde ou un vidage mémoire volés sont inutilisables. Cela n’empêche pas un logiciel malveillant présent sur le même appareil de demander au matériel de signer, sauf si vous exigez la présence de l’utilisateur ou la biométrie à chaque utilisation. Les algorithmes pris en charge sont peu nombreux (P-256 partout, RSA sur certaines plateformes, ML-DSA et ML-KEM post-quantiques dans la Secure Enclave d’Apple depuis les versions 26). L’attestation permet à un serveur de vérifier qu’une clé réside réellement dans un tel matériel, grâce à une chaîne de certificats qui remonte au fabricant : les racines de Google pour Android, celles de Yubico pour les YubiKey, celles du fabricant du TPM pour les TPM. Perdre l’appareil, c’est perdre la clé : prévoyez donc dès le départ l’enregistrement de plusieurs clés et leur rotation.

Les plateformes

Emplacement de la cléAlgorithmes courantsAttestationContrôles côté utilisateur
Secure Enclave d’AppleUn coprocesseur dédié des puces AppleP-256 ; ML-KEM-768/1024 et ML-DSA-65/87 (CryptoKit, OS 26+)App Attest sur iOS ; aucune pour une clé quelconqueTouch ID, Face ID, code de l’appareil via le contrôle d’accès
Android Keystore, TEEL’environnement d’exécution de confiance du SoC principalRSA, EC, AES, HMACAttestation de clé chaînée aux racines de GoogleIdentifiant de l’écran de verrouillage ou biométrie, à chaque utilisation ou pour une durée donnée
Android StrongBoxUn élément sécurisé embarqué ou une enclave sécurisée intégrée (Android 9+)RSA 2048, P-256, AES, HMAC-SHA256, 3DESIdem, avec le niveau de sécurité StrongBoxIdem
TPM 2.0Une puce dédiée, un TPM intégré au micrologiciel ou un TPM virtuelRSA 2048+, P-256, P-384 (selon le fabricant)AK certifiée par l’EK, certificat d’EK émis par le fabricantValeurs d’autorisation et politiques
YubiKey PIVUne applet de carte à puce sur le jetonRSA 1024/2048, P-256, P-384 ; RSA 3072/4096, Ed25519 et X25519 à partir du micrologiciel 5.7Certificat d’attestation de l’emplacement f9, chaîné à YubicoPolitique de PIN et politique de toucher propres à chaque clé
HSM cloudHSM exploités par le fournisseurVoir la signature avec un KMS cloudDéclarations d’attestation de Google Cloud HSMIAM, pas des personnes

Les clés d’accès en sont l’usage le plus courant : un authentificateur de plateforme, c’est l’une des quatre premières lignes du tableau, dotée d’une interface WebAuthn (les clés d’accès pour les développeurs).

Secure Enclave d’Apple, testée sur ce Mac

La Secure Enclave est le processeur de clés isolé d’Apple. Avec CryptoKit, créer une clé tient en une ligne. Nous avons exécuté ce code sur un Apple M4 Max sous macOS 27.0.1 avec Swift 6.4, sous la forme d’un simple outil en ligne de commande, sans droits (entitlements) particuliers :

main.swift : une clé P-256 de la Secure Enclave à côté d’une clé logicielle
// 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)")
Sortie (macOS 27.0.1, Apple M4 Max ; la clé publique PEM est omise)
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)

Les 284 octets de dataRepresentation sont ce qu’une application stocke (dans le trousseau ou dans un fichier) pour réutiliser la clé plus tard ; ils sont chiffrés par la Secure Enclave, et seule l’enclave de cet appareil peut s’en servir. Il n’existe aucun moyen de demander le scalaire privé : un code qui essaie ne compile même pas.

swiftc sur un fichier qui lit key.rawRepresentation d’une clé de la Secure Enclave
export.swift:5:11: error: value of type 'SecureEnclave.P256.Signing.PrivateKey' has no member 'rawRepresentation'

Le guide d’Apple Protéger les clés avec la Secure Enclave, rédigé pour l’API SecKey du framework Security, indique toujours qu’elle « ne fonctionne qu’avec des clés à courbe elliptique NIST P-256 ». CryptoKit va plus loin : SecureEnclave.MLDSA65, MLDSA87, MLKEM768 et MLKEM1024 sont disponibles à partir d’iOS et de macOS 26. Sur ce Mac :

pq.swift : clés post-quantiques dans la Secure Enclave
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)")
Sortie
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

Ces tailles sont les valeurs de FIPS 204 et de FIPS 203. Pour une clé quelconque, Apple ne propose aucune attestation qu’un serveur pourrait vérifier ; sur iOS, App Attest (DeviceCheck) atteste l’application ainsi qu’une clé de la Secure Enclave qu’App Attest crée à cette fin.

Android Keystore : TEE et StrongBox

Sur Android, les clés générées dans l’Android Keystore restent « non exportables » : « les éléments de clé n’entrent jamais dans le processus de l’application », et lorsque le matériel sécurisé de l’appareil prend en charge l’algorithme et les paramètres de la clé, « ses éléments de clé ne sont jamais exposés en dehors du matériel sécurisé ». Il existe deux niveaux de matériel :

  • TEE (KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT) : un environnement isolé sur le processeur principal.
  • StrongBox (Android 9 et versions ultérieures, setIsStrongBoxBacked(true)) : « des SE embarqués ou des Secure Enclaves intégrées (iSE), qui offrent une isolation et une résistance aux manipulations supérieures à celles du TEE », avec son propre processeur, son propre stockage, son propre générateur de nombres aléatoires et sa propre minuterie sécurisée. StrongBox ne prend en charge que « RSA 2048, AES 128 et 256, ECDSA, ECDH P-256, HMAC-SHA256 » ainsi que Triple DES, et Google prévient qu’il « est plus lent, plus limité en ressources » et que « pour la plupart des applications, StrongBox n’est pas nécessaire ». Une demande non prise en charge lève StrongBoxUnavailableException.

Vérifiez où une clé a effectivement été placée avec KeyInfo.getSecurityLevel() (API 31+ ; isInsideSecureHardware() sur les versions antérieures), et liez son utilisation à l’utilisateur avec setUserAuthenticationParameters(), soit à chaque opération, soit pendant une fenêtre de temps après le déverrouillage. Une réserve, tirée de la même page : le matériel « n’applique généralement pas les autorisations liées à un intervalle de validité temporelle, car il ne dispose normalement pas d’une horloge temps réel indépendante et sécurisée ».

L’attestation de clé le prouve à votre serveur. L’appareil renvoie une chaîne de certificats pour la clé ; le premier certificat qui porte l’extension d’attestation (OID 1.3.6.1.4.1.11129.2.1.17) décrit la clé, ses propriétés et son niveau de sécurité. Les règles de l’attestation de clé qui méritent d’être reprises dans votre vérificateur :

  • Vérifiez sur un serveur, jamais sur l’appareil lui-même.
  • La racine doit être l’une des racines d’attestation de Google, publiées à l’adresse android.googleapis.com/attestation/root. Une racine ECDSA P-384, « Key Attestation CA1 », « commencera à signer les chaînes de certificats d’attestation le 1er février 2026 », aux côtés de la racine RSA en place depuis longtemps ; acceptez les deux.
  • Vérifiez chaque certificat par rapport à la liste de révocation de Google, à l’adresse android.googleapis.com/attestation/status.
  • « Seule la première occurrence de l’extension dans la chaîne est digne de confiance » ; les suivantes peuvent provenir d’un attaquant qui prolonge la chaîne.
  • Exigez un attestationSecurityLevel égal à TrustedEnvironment ou StrongBox ; une chaîne qui mène à une autre racine, ou une attestation logicielle, ne dit rien du matériel.

Google recommande sa bibliothèque de vérification en Kotlin plutôt que des vérificateurs maison. La signature C2PA de l’appareil photo des Pixel en est un exemple concret : les clés de signature de claim sont « générées et stockées à l’aide d’Android StrongBox dans la puce de sécurité Titan M2 », et les certificats ne sont émis qu’après l’attestation de clé (Content Credentials C2PA).

TPM 2.0 : clés, politiques et attestation, dans un TPM logiciel

Le TPM en est l’équivalent pour les PC et les serveurs. Pour en montrer le fonctionnement sans matériel, le laboratoire a fait tourner swtpm 0.10.2 (un TPM logiciel, qui par construction ne protège rien) avec tpm2-tools 5.8 dans debian:forky-20261005-slim. Une clé de signature est créée sous une clé primaire, avec des attributs qui la lient à ce TPM :

lab.sh (extrait) : une clé de signature P-256 non exportable, une signature, la vérification avec OpenSSL
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
Sortie (filtrée)
attributes:
  value: fixedtpm|fixedparent|sensitivedataorigin|userwithauth|sign
sig.priv: 128 bytes (encrypted by the TPM, usable only under this parent)
Verified OK

fixedtpm et fixedparent signifient que la clé ne pourra jamais être dupliquée vers un autre TPM ou une autre clé parente ; sensitivedataorigin, que c’est le TPM qui l’a générée. sig.priv est la clé chiffrée sous sa clé parente, que l’on peut stocker sans risque sur disque. La signature est une signature ECDSA ordinaire en DER, vérifiée par OpenSSL avec la clé publique exportée.

L’attestation utilise une clé d’attestation distincte (AK, attestation key) dotée de l’attribut restricted. tpm2_certify fait signer par l’AK une déclaration qui porte sur une autre clé et inclut le nonce du vérificateur :

lab.sh (extrait) : certifier la clé de signature avec une clé d’attestation restreinte
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
Sortie (filtrée)
Verified OK
magic: ff544347
type: 8017
extraData: 7365727665722d6e6f6e63652d37663361
    name: 000b2d7a257558828e1caf174b67daabff261f8c02077768266be3cdc58015de4bee
    qualifiedName: 000b6d68f45fa311b20dc86b37e1659c1f06055e5641aa6a03e28cbb2caad6646fcd
name of the signing key: 000b2d7a257558828e1caf174b67daabff261f8c02077768266be3cdc58015de4bee

La structure commence par la valeur magique 0xff544347 (« TPM_GENERATED »), le type 0x8017 désigne une déclaration de certification, extraData est notre nonce en hexadécimal, et name est le condensat de la zone publique et des attributs de la clé certifiée : il correspond à la clé de signature. Pourquoi s’y fier ? Parce qu’une clé restreinte refuse de signer des données qui commencent par la valeur magique TPM_GENERATED, sauf si le TPM a lui-même produit ces données :

La même clé d’attestation : des données ordinaires, puis une attestation falsifiée (un octet modifié)
ordinary data (msg.txt): signed by the attestation key
ERROR: Esys_Sign(0x3E0) - tpm:parameter(3):invalid ticket

Dans un déploiement réel, la chaîne continue : l’AK est rattachée à la clé d’approbation (endorsement key, EK) du TPM, dont le certificat a été émis par le fabricant du TPM, et le vérificateur contrôle cette chaîne ; le format de déclaration d’attestation tpm de WebAuthn fonctionne ainsi. Un TPM logiciel n’a pas de tel certificat, et c’est précisément pour cette raison que son attestation ne doit pas être jugée digne de confiance.

YubiKey PIV et attestation

L’applet PIV d’une YubiKey stocke les clés dans des emplacements, ou slots (9a authentification, 9c signature, 9d gestion des clés, 9e authentification de la carte), avec une politique de PIN et une politique de toucher fixées au moment de la génération. L’attestation PIV montre « qu’une clé asymétrique donnée a été générée sur l’appareil et non importée » : la YubiKey signe un certificat pour la clé avec sa clé d’attestation de l’emplacement f9, laquelle est accompagnée d’un certificat émis par une AC PIV de Yubico. Voici les commandes, tirées de yubico-piv-tool 2.7.3 (nous n’avons pas de jeton au laboratoire, seule son aide --help a donc été vérifiée) :

yubico-piv-tool (non exécuté sur un jeton)
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

Le certificat d’attestation porte des extensions propres à Yubico : version du micrologiciel (1.3.6.1.4.1.41482.3.3), numéro de série (…3.7), politique de PIN et de toucher (…3.8), facteur de forme (…3.9) et statut FIPS ou CSPN (…3.10, …3.11). Un vérificateur contrôle la chaîne depuis le certificat de l’emplacement jusqu’à la racine de Yubico, en passant par le certificat f9, puis lit les politiques : une politique de toucher « always » signifie qu’une personne était physiquement présente pour chaque signature. Yubico procède à la rotation de sa racine : « les YubiKey dotées du micrologiciel en version 5.7.4 ou ultérieure seront signées par notre nouvelle AC racine », si bien que les vérificateurs ont besoin des deux racines. Le CA/Browser Forum accepte précisément ce type de preuve pour les clés de signature de code (Authenticode), tandis qu’OpenSSH utilise les mêmes jetons via FIDO2 plutôt que PIV (ssh-keygen).

Ce que les clés matérielles ne vous apportent pas

  • Une protection contre le code exécuté avec les droits de l’utilisateur. Un logiciel malveillant présent sur l’appareil peut demander au matériel de signer ; il ne peut simplement pas emporter la clé. Exigez la présence de l’utilisateur (toucher), la vérification de l’utilisateur (PIN, biométrie) ou une confirmation pour les clés importantes.
  • La disponibilité. Un appareil perdu, effacé ou cassé emporte la clé avec lui, et l’effacement d’un TPM (ce que font certaines mises à jour du micrologiciel et certaines réinstallations) invalide les clés qui en dépendent. Enregistrez au moins deux authentificateurs, conservez une procédure de récupération et planifiez la rotation.
  • L’agilité algorithmique. Vous obtenez ce que la puce implémente. Beaucoup de plateformes ne proposent encore que P-256 et RSA ; si un protocole exige Ed25519 ou secp256k1, vérifiez ce point avant de concevoir votre système autour du matériel.
  • La confiance dans ce que le matériel signe. Une clé sécurisée signe tout ce que le système d’exploitation lui transmet ; l’écran qui affiche « approuver le paiement » appartient au même système, peut-être compromis. C’est pour cette raison qu’il existe des portefeuilles matériels dotés de leur propre écran.
  • Une preuve sans attestation. Si votre serveur ne vérifie pas de chaîne d’attestation, « la clé est dans le matériel » n’est qu’une affirmation du client.

Liste de contrôle pour utiliser des clés matérielles

  1. Générez les clés sur l’appareil ; n’importez jamais une clé que vous voulez présenter comme protégée par le matériel.
  2. Choisissez le niveau dont vous avez besoin : Secure Enclave ou TEE pour la plupart des applications, StrongBox ou un élément sécurisé pour les clés de grande valeur, un HSM pour les clés côté serveur.
  3. Liez l’utilisation à l’utilisateur (biométrie ou PIN à chaque utilisation) lorsqu’une signature autorise quelque chose.
  4. Vérifiez l’attestation sur le serveur : racines (actuelles et renouvelées), révocation, nonce, niveau de sécurité, propriétés de la clé.
  5. Ne stockez que les clés publiques et des références opaques ; attendez-vous à ce que des clés disparaissent et prenez en charge le réenregistrement.

Sources