Sistema archivio chiavi Android

Il sistema Keystore Android consente di memorizzare le chiavi di crittografia in un contenitore per renderle più difficili da estrarre dal dispositivo. Una volta che le chiavi si trovano nell'archivio chiavi, puoi utilizzarle per operazioni crittografiche, mentre il materiale della chiave rimane non esportabile. Inoltre, il sistema di archivio chiavi consente di limitare quando e come è possibile utilizzare le chiavi, ad esempio richiedendo l'autenticazione dell'utente per l'uso delle chiavi o limitando l'utilizzo delle chiavi solo in determinate modalità crittografiche. Per saperne di più, consulta la sezione Funzionalità di sicurezza.

Il sistema dell'archivio chiavi è utilizzato dall'API KeyChain, introdotta in Android 4.0 (livello API 14), dalla funzionalità del provider di archiviazione chiavi Android, introdotta in Android 4.3 (livello API 18) e dalla libreria di sicurezza, disponibile come parte di Jetpack. Questo documento illustra quando e come utilizzare il sistema Keystore di Android.

Funzionalità di sicurezza

Il sistema Keystore di Android protegge il materiale della chiave dall'uso 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 di archivio chiavi riduce il rischio di utilizzo non autorizzato del materiale della chiave all'interno del dispositivo Android facendo in modo che le app specifichino gli usi autorizzati delle loro chiavi e applichino quindi queste limitazioni al di fuori dei processi delle app.

Prevenzione dell'estrazione

Il materiale delle chiavi dell'archivio chiavi Android è protetto dall'estrazione tramite due misure di sicurezza:

  • Il materiale della chiave non viene mai inserito nella procedura di richiesta. Quando un'app esegue operazioni crittografiche utilizzando una chiave dell'archivio chiavi Android, il testo non crittografato, il testo crittografato e i messaggi da firmare o verificare dietro le quinte vengono inviati a un processo di sistema che esegue le operazioni crittografiche. Se il processo dell'app viene compromesso, l'utente malintenzionato potrebbe riuscire a utilizzare le chiavi dell'app, ma non riuscire a estrarre il materiale delle chiavi (ad esempio per usarle al di fuori del dispositivo Android).
  • Il materiale della chiave può essere associato all'hardware protetto del dispositivo Android, ad esempio il Trusted Execution Environment (TEE) o Secure Element (SE). Quando questa funzionalità è abilitata per una chiave, il materiale della chiave non viene mai esposto al di fuori dell'hardware sicuro. Se il sistema operativo Android viene compromesso o se un utente malintenzionato riesce a leggere la memoria interna del dispositivo, l'utente malintenzionato potrebbe riuscire a utilizzare le chiavi dell'archivio chiavi Android di qualsiasi app sul dispositivo Android, ma non a estrarle dal dispositivo. Questa funzionalità è abilitata solo se l'hardware protetto del dispositivo supporta la combinazione specifica di algoritmo della chiave, modalità di blocco, schemi di spaziatura interna e digest con cui è autorizzata l'utilizzo della chiave.

    Per verificare se la funzionalità è abilitata 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 che corrispondono a KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT o KeyProperties.SecurityLevelEnum.STRONGBOX indicano che la chiave si trova all'interno di hardware protetto.
    • Se la tua app ha come target Android 9 (livello API 28) o versioni precedenti, controlla il valore restituito booleano pari a KeyInfo.isInsideSecurityHardware().

Modulo di sicurezza hardware

I dispositivi supportati con Android 9 (livello API 28) o versioni successive possono avere un StrongBox Keymaster, un'implementazione di Keymaster o Keymint HAL che risiede in un Secure Element simile a un modulo di sicurezza hardware. Mentre i moduli di sicurezza hardware possono riferirsi a molte implementazioni diverse dell'archiviazione delle chiavi in cui una compromissione del kernel Linux non può rivelarle, come TEE, StrongBox si riferisce esplicitamente a dispositivi come i Secure Elements (eSE) incorporati o le unità di elaborazione sicure on-SoC (iSE).

Il modulo contiene quanto segue:

  • La sua CPU
  • Archiviazione sicura
  • Un generatore di numeri casuali
  • 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 un input/output per uso generico (GPIO)

Per supportare le implementazioni StrongBox a bassa potenza, sono supportati 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)
  • DES tripla
  • APDU di lunghezza estesa
  • Attestazione chiave
  • Supporto dell'emendamento H per l'upgrade

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

Sebbene StrongBox sia un po' più lenta e con risorse limitate (il che significa che supporta un numero inferiore di operazioni simultanee) rispetto a TEE, StrongBox fornisce migliori garanzie di sicurezza contro gli attacchi fisici e laterali. Se vuoi dare la priorità alle garanzie di sicurezza più elevate rispetto all'efficienza delle risorse dell'app, ti consigliamo di utilizzare StrongBox sui dispositivi in cui è disponibile. Ovunque StrongBox non sia disponibile, la tua app può sempre ricorrere a TEE per archiviare i materiali chiave.

Autorizzazioni per l'utilizzo delle chiavi

Per evitare l'uso non autorizzato di chiavi sul dispositivo Android, Android Keystore consente alle app di specificare gli usi autorizzati delle loro chiavi durante la generazione o l'importazione delle chiavi. Una volta generata o importata una chiave, le relative autorizzazioni non possono essere modificate. Le autorizzazioni vengono quindi applicate dall'archivio chiavi di Android ogni volta che viene utilizzata la chiave. Si tratta di una funzionalità di sicurezza avanzata in genere utile solo se i requisiti prevedono che una compromissione del processo dell'applicazione dopo la generazione/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 autorizzati (crittografia, decriptazione, firma, verifica), schemi di spaziatura interna, modalità di blocco o digest.
  • Intervallo di validità temporale: l'uso della chiave è autorizzato soltanto durante un intervallo di tempo definito.
  • Autenticazione utente: la chiave può essere utilizzata solo se l'utente è stato autenticato abbastanza di recente. Vedi Richiedere l'autenticazione degli utenti per l'utilizzo della chiave.

Come misura di sicurezza aggiuntiva per le chiavi il cui materiale si trova all'interno di hardware sicuro (vedi KeyInfo.isInsideSecurityHardware() o, per le app destinate ad Android 10 (livello API 29) o versioni successive, KeyInfo.getSecurityLevel(), alcune autorizzazioni per l'utilizzo delle chiavi potrebbero essere applicate dall'hardware protetto, a seconda del dispositivo Android. In genere, l'hardware protetto applica le autorizzazioni crittografiche e di autenticazione degli utenti. Tuttavia, l'hardware protetto di solito non impone autorizzazioni con intervallo di validità temporale, perché di solito non ha un orologio in tempo reale indipendente e sicuro.

Puoi eseguire query sull'applicazione forzata dell'autorizzazione dell'autenticazione utente di una chiave dall'hardware protetto utilizzando KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

Scegliete tra un portachiavi e il provider di Android Keystore

Utilizza l'API KeyChain quando vuoi disporre di 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 delle credenziali installate può accedere un'app. Ciò consente a diverse app di utilizzare lo stesso insieme di credenziali con il consenso dell'utente.

Utilizza il provider dell'archivio chiavi Android per consentire a una singola app di archiviare le proprie credenziali, a cui solo quell'app può accedere. In questo modo, le app possono gestire le credenziali utilizzabili solo da loro, 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.

Utilizza il provider dell'archivio chiavi Android

Per utilizzare questa funzionalità, devi utilizzare le classi standard KeyStore, KeyPairGenerator o KeyGenerator insieme al provider 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).

Genera una nuova chiave privata o segreta

Per generare un nuovo KeyPair contenente un PrivateKey e un SecretKey, devi specificare gli attributi X.509 iniziali del certificato autofirmato.

La libreria di sicurezza fornisce un'implementazione predefinita per generare una chiave simmetrica valida, come mostrato nello snippet seguente:

Kotlin

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
val mainKey = MasterKey.Builder(applicationContext)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()

Java

// Although you can define your own key generation parameter specification, it's
// recommended that you use the value specified here.
Context context = getApplicationContext();
MasterKey mainKey = new MasterKey.Builder(context)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build();

In alternativa, puoi utilizzare KeyStore.setKeyEntry() per sostituire il certificato in un secondo momento con uno 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 chiavi criptate in hardware sicuro

Android 9 (livello API 28) e versioni successive consente di importare in modo sicuro chiavi criptate nell'archivio chiavi utilizzando un formato di chiave con codifica ASN.1. Il Keymaster quindi decripta le chiavi nell'archivio chiavi in modo che i contenuti delle chiavi non vengano mai visualizzati come testo non crittografato nella memoria host del dispositivo. Questo processo offre una sicurezza aggiuntiva per la decrittografia delle chiavi.

Per supportare l'importazione sicura delle chiavi criptate nell'archivio chiavi, completa i seguenti passaggi:

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

  2. Su un server o una macchina che ritieni attendibile, genera il messaggio ASN.1 per 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 nel sovraccarico di setEntry() che accetta un oggetto Keystore.Entry.

Utilizzare le voci dell'archivio chiavi

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

Elenco voci

Elenca le voci dell'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 i dati

Firma i dati recuperando KeyStore.Entry dall'archivio chiavi e utilizzando le API Signature, come 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);

Richiedi l'autenticazione utente per l'uso della chiave

Quando generi o importi una chiave in AndroidKeyStore, puoi specificare che la chiave sia autorizzata all'utilizzo solo se l'utente è stato autenticato. L'utente viene autenticato utilizzando un sottoinsieme di credenziali per la schermata di blocco sicura (sequenza/PIN/password, credenziali biometriche).

Si tratta di una funzionalità di sicurezza avanzata generalmente utile solo se i tuoi requisiti prevedono che una compromissione del processo dell'applicazione dopo la generazione/importazione della chiave (ma non prima o durante) non possa ignorare 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à:

Autorizza per un determinato periodo di tempo
Tutte le chiavi vengono autorizzate per l'utilizzo non appena l'utente esegue l'autenticazione utilizzando una delle credenziali specificate.
Autorizza per la durata di una specifica operazione crittografica

Ogni operazione che prevede una chiave specifica deve essere autorizzata individualmente dall'utente.

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

Per ogni chiave che crei, puoi scegliere di supportare una credenziale biometrica avanzata, una credenziale della 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 credenziali biometriche, la chiave 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, incluso come mostrare una finestra di dialogo di autenticazione biometrica.

Algoritmi supportati

Articoli del blog

Consulta il post del blog Unifying Key Store Access in ICS.