Sistema archivio chiavi Android

Il sistema Android Keystore consente di archiviare le chiavi di crittografia in un container 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 limitandone l'utilizzo 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. In primo luogo, riduce il rischio di utilizzo non autorizzato del materiale della chiave dall'esterno del dispositivo Android impedendo l'estrazione del materiale della chiave dai processi dell'applicazione e dal dispositivo Android nel suo complesso. In secondo luogo, il sistema Keystore riduce il rischio di utilizzo non autorizzato del materiale delle chiavi 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'utente malintenzionato potrebbe riuscire a utilizzare le chiavi dell'app, ma non riuscire a estrarre il materiale delle chiavi (ad esempio per utilizzarle al di fuori del dispositivo Android).
  • Il materiale delle chiavi può essere associato all'hardware sicuro del dispositivo Android, ad esempio il Trusted Execution Environment (TEE) o 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 viene compromesso o un utente malintenzionato riesce a leggere la memoria interna del dispositivo, l'utente malintenzionato potrebbe essere in grado di utilizzare le chiavi dell'archivio chiavi Android di qualsiasi app sul dispositivo Android, ma non potrà 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 restituito 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 StrongBox Keymaster, un'implementazione di Keymaster o Keymint HAL che si trova in un elemento di sicurezza 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 alle manomissioni dei pacchetti e al sideload non autorizzato delle app
  • Un timer sicuro
  • Un pin di notifica di riavvio (o equivalente), ad esempio input/output per uso 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 inclusi)
  • Triple DES
  • APDU di durata 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().

Sebbene StrongBox sia un po' più lento e vincolato dalle risorse (il che significa che supporta meno operazioni simultanee) rispetto a TEE, StrongBox offre maggiori garanzie di sicurezza contro gli attacchi fisici e side-channel. 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 chiave.

Autorizzazioni all'uso 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. Questa è 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 scopi della chiave autorizzati (crittografia, decriptazione, firma, verifica), schemi di spaziatura interna, 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 normalmente applica le autorizzazioni crittografiche e di 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 query sull'applicazione forzata dell'autorizzazione di autenticazione utente di una chiave da parte dell'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 dell'archivio chiavi Android per consentire a un singolo app di archiviare le proprie credenziali, a cui può accedere solo l'app in questione. In questo modo le app possono gestire le credenziali che solo loro possono utilizzare, garantendo al contempo gli stessi vantaggi in termini di sicurezza offerti dall'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 nell'archivio chiavi 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. Questo processo fornisce un'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 nell'archivio chiavi 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();

In modo simile, 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 l'utilizzo della chiave sia autorizzato solo se l'utente è stato autenticato. L'utente viene autenticato utilizzando un sottoinsieme di credenziali sicure della schermata di blocco (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 può essere utilizzata soltanto 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, trasmetti false in 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.