Sistema archivio chiavi Android

Il sistema Android Keystore ti consente di archiviare le chiavi crittografiche in un contenitore per renderle più difficili da estrarre dal dispositivo. Una volta che le chiavi sono nel keystore, puoi utilizzarle per le operazioni di crittografia, con il materiale della chiave che rimane non esportabile. Inoltre, il sistema del keystore ti consente di limitare quando e come possono essere utilizzate le chiavi, ad esempio richiedendo l'autenticazione dell'utente per l'utilizzo delle chiavi o limitando l'utilizzo delle chiavi solo in determinate modalità di crittografia. Per ulteriori informazioni, consulta la sezione Funzionalità di sicurezza.

Il sistema Keystore viene utilizzato dall'API KeyChain, introdotta in Android 4.0 (livello API 14), nonché dalla funzionalità del fornitore Android Keystore, introdotta in Android 4.3 (livello API 18). Questo documento illustra quando e come utilizzare il sistema Android Keystore.

Funzionalità di sicurezza

Il sistema Android Keystore protegge il materiale delle chiavi dall'utilizzo non autorizzato in due modi. Innanzitutto, riduce il rischio di utilizzo non autorizzato del materiale delle chiavi da esterno al dispositivo Android impedendo l'estrazione del materiale delle chiavi dai processi delle applicazioni e dal dispositivo Android come un'unità. In secondo luogo, il sistema Keystore riduce il rischio di utilizzo non autorizzato del materiale della chiave all'interno del dispositivo Android facendo in modo che le app specifichino gli utilizzi autorizzati delle loro chiavi e poi applicando queste limitazioni al di fuori dei processi delle app.

Prevenzione dell'estrazione

Il materiale delle chiavi delle chiavi del Keystore di Android è protetto dall'estrazione mediante due misure di sicurezza:

  • Il materiale della chiave non viene mai inserito nel processo di applicazione. Quando un'app esegue operazioni di crittografia utilizzando una chiave del Keystore di Android, dietro le quinte il testo non criptato, il testo criptato e i messaggi da firmare o verificare vengono inviati a un processo di sistema che esegue le operazioni di crittografia. Se il processo dell'app è compromesso, l'aggressore potrebbe essere in grado di utilizzare le chiavi dell'app, ma non può estrarre il relativo materiale della chiave (ad esempio, per utilizzarlo al di fuori del dispositivo Android).
  • Il materiale della chiave può essere associato all'hardware sicuro del dispositivo Android, ad esempio il Trusted Execution Environment (TEE) o il Secure Element (SE). Quando questa funzionalità è attivata per una chiave, il relativo materiale non viene mai esposto all'esterno dell'hardware sicuro. Se il sistema operativo Android è compromesso o un malintenzionato può leggere lo spazio di archiviazione interno del dispositivo, potrebbe essere in grado di utilizzare le chiavi del Keystore Android di qualsiasi app sul dispositivo Android, ma non può estrarle dal dispositivo. Questa funzionalità è attivata solo se l'hardware sicuro del dispositivo supporta la particolare combinazione di algoritmo di chiave, modalità di blocco, schemi di padding e digest con cui la chiave è autorizzata a essere utilizzata.

    Per verificare se la funzionalità è attivata per una chiave, ottieni un KeyInfo per la chiave. Il passaggio successivo dipende dalla versione dell'SDK target della tua app:

    • Se la tua app ha come target Android 10 (livello API 29) o versioni successive, controlla il valore di ritorno di getSecurityLevel(). I valori restituiti corrispondenti a KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT o KeyProperties.SecurityLevelEnum.STRONGBOX indicano che la chiave si trova in hardware sicuro.
    • Se la tua app ha come target Android 9 (livello API 28) o versioni precedenti, controlla il valore booleano di ritorno di KeyInfo.isInsideSecurityHardware().

Modulo di sicurezza hardware

I dispositivi supportati con Android 9 (livello API 28) o versioni successive possono avere un Keymaster StrongBox, un'implementazione dell'HAL Keymaster o Keymint che risiede in un elemento sicuro simile a un modulo di sicurezza hardware. Sebbene i moduli di sicurezza hardware possano fare riferimento a molte implementazioni diverse di archiviazione delle chiavi in cui un compromesso del kernel di Linux non può rivelarle, come TEE, StrongBox fa esplicitamente riferimento a dispositivi come elementi di sicurezza integrati (eSE) o unità di elaborazione sicura on-SoC (iSE).

Il modulo contiene quanto segue:

  • Una propria CPU
  • Spazio di archiviazione protetto
  • Un generatore di numeri casuali veri
  • Meccanismi aggiuntivi per resistere alla manomissione dei pacchetti e al sideload non autorizzato delle app
  • Un timer sicuro
  • Un pin di notifica del riavvio (o equivalente), ad esempio input/output generico (GPIO)

Per supportare le implementazioni di StrongBox a basso consumo, è supportato un sottoinsieme di algoritmi e dimensioni delle chiavi:

  • RSA 2048
  • AES 128 e 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256 (supporta dimensioni delle chiavi comprese tra 8 e 64 byte)
  • Triple DES
  • APDU di lunghezza estesa
  • Attestazione chiave
  • Supporto dell'aggiornamento per l'emendamento H

Quando generi o importi le chiavi utilizzando la classe KeyStore, puoi indicare una preferenza per l'archiviazione della chiave in StrongBox Keymaster passando true al metodo setIsStrongBoxBacked().

Anche se StrongBox è un po' più lento e limitato in termini di risorse (ovvero supporta meno operazioni contemporaneamente) rispetto al TEE, offre migliori garanzie di sicurezza contro gli attacchi fisici e lato canale. Se vuoi dare la priorità a maggiori garanzie di sicurezza rispetto all'efficienza delle risorse dell'app, ti consigliamo di utilizzare StrongBox sui dispositivi su cui è disponibile. Se StrongBox non è disponibile, la tua app può sempre ricorrere al TEE per archiviare i materiali delle chiavi.

Autorizzazioni di utilizzo delle chiavi

Per evitare l'uso non autorizzato delle chiavi sul dispositivo Android, Android Keystore consente alle app di specificare gli utilizzi autorizzati delle proprie chiavi quando le generano o le importano. Una volta generata o importata una chiave, le relative autorizzazioni non possono essere modificate. Le autorizzazioni vengono quindi applicate dal Keystore Android ogni volta che viene utilizzata la chiave. Si tratta di una funzionalità di sicurezza avanzata che è generalmente utile solo se i tuoi requisiti prevedono che una compromissione della procedura di richiesta dopo la generazione/l'importazione della chiave (ma non prima o durante) non possa portare a utilizzi non autorizzati della chiave.

Le autorizzazioni di utilizzo delle chiavi supportate rientrano nelle seguenti categorie:

  • Crittografia:la chiave può essere utilizzata solo con algoritmi, operazioni o finalità delle chiavi autorizzate (crittografia, decrittografia, firma, verifica), schemi di padding, modalità di blocco o digest.
  • Intervallo di validità temporale:l'utilizzo della chiave è autorizzato solo durante un intervallo di tempo definito.
  • Autenticazione utente: la chiave può essere utilizzata solo se l'utente è stato autenticato abbastanza di recente. Consulta Richiedi l'autenticazione dell'utente per l'utilizzo della chiave.

Come misura di sicurezza aggiuntiva per le chiavi il cui materiale è all'interno di un hardware sicuro (vedi KeyInfo.isInsideSecurityHardware() o, per le app che hanno come target Android 10 (livello API 29) o versioni successive, KeyInfo.getSecurityLevel()), alcune autorizzazioni di utilizzo delle chiavi potrebbero essere applicate dall'hardware sicuro, a seconda del dispositivo Android. L'hardware sicuro in genere applica le autorizzazioni di crittografia e autenticazione utente. Tuttavia, l'hardware sicuro di solito non applica le autorizzazioni per gli intervalli di validità temporali, perché in genere non dispone di un orologio in tempo reale sicuro e indipendente.

Puoi eseguire una query per verificare se l'autorizzazione di autenticazione utente di una chiave viene applicata dall'hardware sicuro utilizzando KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

Scegli tra un portachiavi e il fornitore Android Keystore

Utilizza l'API KeyChain quando vuoi le credenziali a livello di sistema. Quando un'app richiede l'utilizzo di qualsiasi credenziale tramite l'API KeyChain, gli utenti possono scegliere, tramite un'interfaccia utente fornita dal sistema, a quali credenziali installate l'app può accedere. In questo modo, più app possono utilizzare lo stesso insieme di credenziali con il consenso dell'utente.

Utilizza il provider Android Keystore per consentire a una singola app di memorizzare le proprie credenziali, a cui solo l'app può accedere. In questo modo, le app possono gestire le credenziali che solo loro possono utilizzare, offrendo al contempo gli stessi vantaggi di sicurezza dell'API KeyChain per le credenziali a livello di sistema. Questo metodo non richiede all'utente di selezionare le credenziali.

Utilizzare il provider Android Keystore

Per utilizzare questa funzionalità, utilizza le classi KeyStore e KeyPairGenerator o KeyGenerator standard insieme al fornitore AndroidKeyStore introdotto in Android 4.3 (livello API 18).

AndroidKeyStore è registrato come tipo KeyStore per l'utilizzo con il metodo KeyStore.getInstance(type) e come provider per l'utilizzo con i metodi KeyPairGenerator.getInstance(algorithm, provider) e KeyGenerator.getInstance(algorithm, provider).

Poiché le operazioni di crittografia possono richiedere molto tempo, le app devono evitare di utilizzare AndroidKeyStore nel thread principale per garantire che l'UI dell'app rimanga reattiva. (StrictMode può aiutarti a trovare luoghi in cui non è così).

Generare una nuova chiave privata o segreta

Per generare un nuovo KeyPair contenente un PrivateKey, devi specificare gli attributi X.509 iniziali del certificato. Puoi utilizzare KeyStore.setKeyEntry() per sostituire il certificato in un secondo momento con un certificato firmato da un'autorità di certificazione (CA).

Per generare la coppia di chiavi, utilizza un KeyPairGenerator con KeyGenParameterSpec:

Kotlin

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    build()
}

kpg.initialize(parameterSpec)

val kp = kpg.generateKeyPair()

Java

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
        .setDigests(KeyProperties.DIGEST_SHA256,
            KeyProperties.DIGEST_SHA512)
        .build());

KeyPair kp = kpg.generateKeyPair();

Importa le chiavi criptate in hardware protetto

Android 9 (livello API 28) e versioni successive ti consentono di importare le chiavi criptate in modo sicuro nel keystore utilizzando un formato di chiave con codifica ASN.1. Keymaster decripta quindi le chiavi nel keystore, in modo che i contenuti delle chiavi non vengano mai visualizzati in testo normale nella memoria host del dispositivo. Questa procedura offre ulteriore sicurezza per la decrittografia delle chiavi.

Per supportare l'importazione sicura delle chiavi criptate nel keystore, completa i seguenti passaggi:

  1. Genera una coppia di chiavi che utilizza lo scopo PURPOSE_WRAP_KEY. Ti consigliamo di aggiungere l'attestazione anche a questa coppia di chiavi.

  2. Su un server o una macchina attendibili, genera il messaggio ASN.1 per il messaggio SecureKeyWrapper.

    Il wrapper contiene il seguente schema:

       KeyDescription ::= SEQUENCE {
           keyFormat INTEGER,
           authorizationList AuthorizationList
       }
    
       SecureKeyWrapper ::= SEQUENCE {
           wrapperFormatVersion INTEGER,
           encryptedTransportKey OCTET_STRING,
           initializationVector OCTET_STRING,
           keyDescription KeyDescription,
           secureKey OCTET_STRING,
           tag OCTET_STRING
       }
    
  3. Crea un oggetto WrappedKeyEntry, passando il messaggio ASN.1 come array di byte.

  4. Passa questo oggetto WrappedKeyEntry all'overload di setEntry() che accetta un oggetto Keystore.Entry.

Lavorare con le voci del keystore

Puoi accedere al provider AndroidKeyStore tramite tutte le API KeyStore standard.

Elenco voci

Elenca le voci nel keystore chiamando il metodo aliases():

Kotlin

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
   load(null)
}
val aliases: Enumeration<String> = ks.aliases()

Java

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();

Firma e verifica dei dati

Firma i dati recuperando il file KeyStore.Entry dal keystore e utilizzando le API Signature, ad esempio sign():

Kotlin

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry: KeyStore.Entry = ks.getEntry(alias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return null
}
val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run {
    initSign(entry.privateKey)
    update(data)
    sign()
}

Java

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return null;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(((PrivateKeyEntry) entry).getPrivateKey());
s.update(data);
byte[] signature = s.sign();

Analogamente, verifica i dati con il metodo verify(byte[]):

Kotlin

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
val ks = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
if (entry == null) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return false
}
val valid: Boolean = Signature.getInstance("SHA256withECDSA").run {
    initVerify(entry.certificate)
    update(data)
    verify(signature)
}

Java

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return false;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(((PrivateKeyEntry) entry).getCertificate());
s.update(data);
boolean valid = s.verify(signature);

Richiedere l'autenticazione utente per l'utilizzo della chiave

Quando generi o importi una chiave in AndroidKeyStore, puoi specificare che la chiave può essere utilizzata solo se l'utente è stato autenticato. L'utente viene autenticato utilizzando un sottoinsieme delle sue credenziali di blocco schermo sicure (sequenza/PIN/password, credenziali biometriche).

Questa è una funzionalità di sicurezza avanzata che in genere è utile solo se i tuoi requisiti prevedono che un compromesso della procedura di applicazione dopo la generazione/l'importazione della chiave (ma non prima o durante) non possa aggirare il requisito di autenticazione dell'utente per l'utilizzo della chiave.

Quando una chiave è autorizzata a essere utilizzata solo se l'utente è stato autenticato, puoi chiamare setUserAuthenticationParameters() per configurarla in modo che funzioni in una delle seguenti modalità:

Autorizzazione per un periodo di tempo
Tutte le chiavi sono autorizzate all'uso non appena l'utente si autentica utilizzando una delle credenziali specificate.
Autorizzazione per la durata di un'operazione di crittografia specifica

Ogni operazione che coinvolge una chiave specifica deve essere autorizzata singolarmente dall'utente.

L'app avvia questo processo chiamando authenticate() su un'istanza di BiometricPrompt.

Per ogni chiave creata, puoi scegliere di supportare una credenziale biometrica sicura, una credenziale per la schermata di blocco o entrambi i tipi di credenziali. Per determinare se l'utente ha configurato le credenziali su cui si basa la chiave della tua app, chiama canAuthenticate().

Se una chiave supporta solo le credenziali biometriche, viene invalidata per impostazione predefinita ogni volta che vengono aggiunte nuove registrazioni biometriche. Puoi configurare la chiave in modo che rimanga valida quando vengono aggiunte nuove registrazioni biometriche. Per farlo, passa false a setInvalidatedByBiometricEnrollment().

Scopri di più su come aggiungere funzionalità di autenticazione biometrica alla tua app, inclusa la procedura per mostrare una finestra di dialogo di autenticazione biometrica.

Algoritmi supportati

Articoli del blog

Leggi il post del blog Unificazione dell'accesso al Key Store in ICS.