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 aKeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT
oKeyProperties.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()
.
- Se la tua app ha come target Android 10 (livello API 29) o versioni successive, controlla il valore restituito
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:
Genera una coppia di chiavi che utilizza lo scopo
PURPOSE_WRAP_KEY
. Ti consigliamo di aggiungere l'attestazione anche a questa coppia di chiavi.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 }
Crea un oggetto
WrappedKeyEntry
passando il messaggio ASN.1 come array di byte.Passa questo oggetto
WrappedKeyEntry
all'overload disetEntry()
che accetta un oggettoKeystore.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 diBiometricPrompt
.
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
Cipher
KeyGenerator
KeyFactory
KeyStore
(supporta gli stessi tipi di chiavi diKeyGenerator
eKeyPairGenerator
)KeyPairGenerator
Mac
Signature
SecretKeyFactory
Articoli del blog
Leggi il post del blog Unificazione dell'accesso al Key Store in ICS.