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 della chiave da esterno al dispositivo Android impedendo l'estrazione del materiale della chiave dai processi delle applicazioni e dal dispositivo Android come un insieme. 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 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 di ritorno di
Secure Element StrongBox KeyMint
I dispositivi con Android 9 (livello API 28) o versioni successive possono includere un StrongBox KeyMint, un'implementazione dell'HAL KeyMint supportata da StrongBox. Sebbene i moduli di sicurezza hardware (HSM) si riferiscano in generale a soluzioni di archiviazione delle chiavi sicure resistenti alle compromissioni del kernel Linux, StrongBox indica in modo specifico le implementazioni in SE embedded o Secure Enclave integrati (iSE), che offrono un isolamento e una resistenza alle manomissioni più elevati rispetto al TEE.
Un'implementazione di StrongBox KeyMint deve contenere quanto segue:
- Una propria CPU
- Spazio di archiviazione sicuro
- 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 di uso generale (GPIO)
È supportato un sottoinsieme di algoritmi e dimensioni delle chiavi per adattarsi alle implementazioni di StrongBox a basso consumo:
- 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 lunghezza estesa
StrongBox supporta anche l'attestazione delle chiavi.
Utilizzare StrongBox KeyMint
Utilizza FEATURE_STRONGBOX_KEYSTORE
per verificare se StrongBox è disponibile su un dispositivo. Se StrongBox è disponibile, puoi indicare una preferenza per l'archiviazione della chiave in StrongBox KeyMint passando true
ai seguenti metodi:
- Generazione di chiavi:
KeyGenParameterSpec.Builder.setIsStrongBoxBacked()
- Importazione delle chiavi:
KeyProtection.Builder.setIsStrongBoxBacked()
Se StrongBox KeyMint non supporta l'algoritmo o la dimensione della chiave specificata,
il framework lancerà un StrongBoxUnavailableException
. In questo caso, genera o importa la chiave senza chiamare setIsStrongBoxBacked(true)
.
Autorizzazioni di utilizzo delle chiavi
Per evitare l'uso non autorizzato delle chiavi sul dispositivo Android, Android Keystore consente alle app di specificare gli usi 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 applicate da Android Keystore ogni volta che viene utilizzata la chiave. Questa è una funzionalità di sicurezza avanzata che in genere è utile solo se i tuoi requisiti prevedono che la compromissione della procedura di applicazione 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 Richiedere 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 indipendente e sicuro.
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 in termini 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
AndroidKeyStore
provider 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ì).
Genera 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
:
/* * 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()
/* * 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 una maggiore 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
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 nel keystore chiamando il metodo aliases()
:
/* * 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()
/* * 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()
:
/* * 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() }
/* * 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[])
:
/* * 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) }
/* * 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 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, 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
Cipher
KeyGenerator
KeyFactory
KeyStore
(supporta gli stessi tipi di chiavi diKeyGenerator
eKeyPairGenerator
)KeyPairGenerator
Mac
Signature
SecretKeyFactory
Passaggio successivo
- Consulta l'articolo Unifying Key Store Access in ICS (Unificazione dell'accesso al Key Store in ICS) sul Blog per sviluppatori Android.