Crittografia

Questo documento descrive il modo corretto di utilizzare le funzionalità crittografiche di Android e include alcuni esempi del loro utilizzo. Se la tua app richiede una maggiore sicurezza delle chiavi, utilizza il sistema Android Keystore.

Specifica un fornitore solo con il sistema Android Keystore

Se utilizzi il sistema Android Keystore, devi specificare un provider.

In altre situazioni, invece, Android non garantisce un particolare fornitore per un determinato algoritmo. La specifica di un fornitore senza utilizzare il sistema Android Keystore può causare problemi di compatibilità nelle versioni future.

Scegliere un algoritmo consigliato

Quando hai la libertà di scegliere quale algoritmo utilizzare (ad esempio quando non hai bisogno della compatibilità con un sistema di terze parti), ti consigliamo di utilizzare i seguenti algoritmi:

Classe Consiglio
Cipher AES in modalità CBC o GCM con chiavi a 256 bit (ad esempio AES/GCM/NoPadding)
MessageDigest Famiglia SHA-2 (ad esempio SHA-256)
Mac HMAC della famiglia SHA-2 (ad esempio HMACSHA256)
Firma Famiglia SHA-2 con ECDSA (ad esempio SHA256withECDSA)

Eseguire operazioni crittografiche comuni

Le sezioni seguenti includono snippet che mostrano come completare operazioni di crittografia comuni nella tua app.

Criptare un messaggio

Kotlin

val plaintext: ByteArray = ...
val keygen = KeyGenerator.getInstance("AES")
keygen.init(256)
val key: SecretKey = keygen.generateKey()
val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING")
cipher.init(Cipher.ENCRYPT_MODE, key)
val ciphertext: ByteArray = cipher.doFinal(plaintext)
val iv: ByteArray = cipher.iv

Java

byte[] plaintext = ...;
KeyGenerator keygen = KeyGenerator.getInstance("AES");
keygen.init(256);
SecretKey key = keygen.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] ciphertext = cipher.doFinal(plaintext);
byte[] iv = cipher.getIV();

Generare un digest del messaggio

Kotlin

val message: ByteArray = ...
val md = MessageDigest.getInstance("SHA-256")
val digest: ByteArray = md.digest(message)

Java

byte[] message = ...;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(message);

Generare una firma digitale

Devi avere un oggetto PrivateKey contenente la chiave di firma, che puoi generare in fase di runtime, leggere da un file incluso nella tua app o ottenere da un'altra origine a seconda delle tue esigenze.

Kotlin

val message: ByteArray = ...
val key: PrivateKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initSign(key)
            update(message)
        }
val signature: ByteArray = s.sign()

Java

byte[] message = ...;
PrivateKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(key);
s.update(message);
byte[] signature = s.sign();

Verificare una firma digitale

Devi avere un oggetto PublicKey contenente la chiave pubblica del firmatario, che puoi leggere da un file incluso nella tua app, estrarre da un certificato o ottenere da un'altra origine a seconda delle tue esigenze.

Kotlin

val message: ByteArray = ...
val signature: ByteArray = ...
val key: PublicKey = ...
val s = Signature.getInstance("SHA256withECDSA")
        .apply {
            initVerify(key)
            update(message)
        }
val valid: Boolean = s.verify(signature)

Java

byte[] message = ...;
byte[] signature = ...;
PublicKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(key);
s.update(message);
boolean valid = s.verify(signature);

Complessità di implementazione

Alcuni dettagli dell'implementazione della crittografia Android sembrano insoliti, ma sono presenti a causa di problemi di compatibilità. Questa sezione descrive quelli che incontrerai più probabilmente.

Digest del messaggio MGF1 OAEP

Le cifrature RSA OAEP sono parametrizzate da due diversi digest dei messaggi: il digest "principale" e il digest MGF1. Esistono Cipher identificatori che includono nomi di digest, ad esempio Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"), che specifica il digest principale e lascia il digest MGF1 non specificato. Per Android Keystore, SHA-1 viene utilizzato per il digest MGF1, mentre per altri provider crittografici Android, i due digest sono uguali.

Per avere un maggiore controllo sui digest utilizzati dalla tua app, richiedi una cifra con OAEPPadding, come in Cipher.getInstance("RSA/ECB/OAEPPadding"), e fornisci un OAEPParameterSpec a init() per scegliere esplicitamente entrambi i digest. Ciò è mostrato nel codice seguente:

Kotlin

val key: Key = ...
val cipher = Cipher.getInstance("RSA/ECB/OAEPPadding")
        .apply {
            // To use SHA-256 the main digest and SHA-1 as the MGF1 digest
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT))
            // To use SHA-256 for both digests
            init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT))
        }

Java

Key key = ...;
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
// To use SHA-256 the main digest and SHA-1 as the MGF1 digest
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT));
// To use SHA-256 for both digests
cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));

Funzionalità deprecata

Le sezioni seguenti descrivono le funzionalità ritirate. Non utilizzarlo nella tua app.

Algoritmi Bouncy Castle

Le implementazioni di molti algoritmi di Bouncy Castle sono obsolete. Ciò influisce solo sui casi in cui richiedi esplicitamente il provider Bouncy Castle, come mostrato nell'esempio seguente:

Kotlin

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC")
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"))

Java

Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"));

Come indicato nella sezione relativa alla specifica di un fornitore solo con il sistema Android Keystore, è sconsigliato richiedere un fornitore specifico. Se segui questa linea guida, il ritiro non ti riguarda.

Cifratura basata su password senza vettore di inizializzazione

Le cifrature basate su password (PBE) che richiedono un vettore di inizializzazione (IV) possono ottenerlo dalla chiave, se è costruito in modo appropriato, o da un IV passato in modo esplicito. Se passi una chiave PBE che non contiene un IV e non passi un IV esplicito, le cifrature PBE su Android attualmente presuppongono un IV pari a zero.

Quando utilizzi cifrari PBE, passa sempre un IV esplicito, come mostrato nello snippet di codice riportato di seguito:

Kotlin

val key: SecretKey = ...
val cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC")
val iv = ByteArray(16)
SecureRandom().nextBytes(iv)
cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))

Java

SecretKey key = ...;
Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));

Fornitore di criptovalute

A partire da Android 9 (livello API 28), il provider Crypto Java Cryptography Architecture (JCA) è stato rimosso. Se la tua app richiede un'istanza del provider Crypto, ad esempio chiamando il seguente metodo, si verifica un NoSuchProviderException.

Kotlin

SecureRandom.getInstance("SHA1PRNG", "Crypto")

Java

SecureRandom.getInstance("SHA1PRNG", "Crypto");

Libreria Jetpack security-crypto

Tutte le API nella libreria Jetpack security-crypto sono state ritirate nella release stabile della versione 1.1.0. Non verranno rilasciate versioni successive di questa libreria.

Le annotazioni di ritiro sono visibili se nel file build.gradle del modulo dell'app sono presenti le seguenti dipendenze:

Groovy

dependencies {
    implementation "androidx.security:security-crypto:1.1.0"
    // or
    implementation "androidx.security:security-crypto-ktx:1.1.0"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.1.0")
    // or
    implementation("androidx.security:security-crypto-ktx:1.1.0")
}

Algoritmi supportati

Questi sono gli identificatori degli algoritmi JCA supportati su Android: