Kryptografia

Dokument ten opisuje prawidłowe korzystanie z funkcji kryptograficznych Androida i zawiera przykłady ich użycia. Jeśli Twoja aplikacja wymaga większego zabezpieczenia klucza, użyj systemu Keystore na Androidzie.

Określ dostawcę tylko w systemie Keystore Androida

Jeśli używasz systemu Keystore Androida, musisz określić dostawcę.

W innych sytuacjach Android nie gwarantuje określonego dostawcy dla danego algorytmu. Określanie dostawcy bez używania systemu Android Keystore może spowodować problemy ze zgodnością w przyszłych wersjach.

Wybieranie zalecanego algorytmu

Jeśli masz możliwość wyboru algorytmu (np. gdy nie wymagasz zgodności z systemem zewnętrznym), zalecamy użycie tych algorytmów:

Kategoria Rekomendacja
Cipher AES w trybie CBC lub GCM z 256-bitowymi kluczami (np. AES/GCM/NoPadding)
MessageDigest Rodzina SHA-2 (np. SHA-256)
Mac HMAC z rodziny SHA-2 (np. HMACSHA256)
Podpis Rodzina SHA-2 z ECDSA (np. SHA256withECDSA)

wykonywanie typowych operacji kryptograficznych;

W następnych sekcjach znajdziesz fragmenty kodu, które pokazują, jak wykonywać w aplikacji typowe operacje kryptograficzne.

Szyfrowanie wiadomości

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();

Generowanie skrótu wiadomości

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);

Generowanie podpisu cyfrowego

Musisz mieć obiekt PrivateKey zawierający klucz podpisywania, który możesz wygenerować w czasie wykonywania, odczytać z pliku dołączonego do aplikacji lub uzyskać z innego źródła w zależności od potrzeb.

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();

Weryfikowanie podpisu cyfrowego

Musisz mieć obiekt PublicKey zawierający klucz publiczny podpisującego, który możesz odczytać z pliku dołączonego do aplikacji, wyodrębnić z certyfikatu lub uzyskać z innego źródła w zależności od potrzeb.

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);

Złożoność implementacji

Niektóre szczegóły implementacji kryptografii na Androidzie wydają się nietypowe, ale są obecne ze względu na problemy ze zgodnością. W tej sekcji omawiamy te, które najprawdopodobniej napotkasz.

OAEP MGF1 message digest

Szyfry RSA OAEP są parametryzowane za pomocą 2 różnych ciągów haszowanych wiadomości: „głównego” ciągu haszowanych wiadomości i ciągu MGF1. Istnieją Cipheridentyfikatory, które zawierają nazwy digest, takie jak Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"), które określają główny digest i nie określają digest MGF1. W przypadku Keystore na Androida do skrótu MGF1 używany jest algorytm SHA-1, podczas gdy w przypadku innych dostawców usług kryptograficznych na Androida oba skróty są takie same.

Aby mieć większą kontrolę nad skrótami, których używa Twoja aplikacja, poproś o szyfrowanie z dodatkiem OAEPPadding, jak w Cipher.getInstance("RSA/ECB/OAEPPadding"), i przekaż parametr OAEPParameterSpec do init(), aby wyraźnie wybrać oba skróty. Pokazuje to poniższy kod:

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));

Wycofana funkcja

W sekcjach poniżej opisujemy wycofane funkcje. Nie używaj go w swojej aplikacji.

Algorytmy Bouncy Castle

Zastosowanie w algorytmach typu „Bouncy Castle” jest wycofane. Dotyczy to tylko przypadków, gdy wyraźnie poprosisz o użycie dostawcy Bouncy Castle, jak w tym przykładzie:

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"));

Jak wspomniano w sekcji określania dostawcy tylko za pomocą systemu Keystore Androida, nie zalecamy korzystania z tego rozwiązania. Jeśli przestrzegasz tych wytycznych, wycofanie się ich nie dotyczy.

Szyfry szyfrowania opartego na hasłach bez wektora inicjującego

Szyfry szyfrowania opartego na hasłach (PBE), które wymagają wektora inicjującego (IV), mogą uzyskać go z klucza, jeśli jest on odpowiednio skonstruowany, lub z jawnie przekazanego wektora inicjującego. Jeśli przekażesz klucz PBE, który nie zawiera IV, i nie przekażesz wyraźnego IV, szyfr PBE na Androidzie przyjmie obecnie wartość zero.

Podczas korzystania z szyfrów PBE zawsze przekazuj jawny IV, jak pokazano w tym fragmencie kodu:

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));

Dostawca kryptowalut

Od Androida 9 (poziom interfejsu API 28) dostawca usługi Crypto Java Cryptography Architecture (JCA) został usunięty. Jeśli Twoja aplikacja zażąda wystąpienia dostawcy kryptowalut, na przykład przez wywołanie tej metody, nastąpi błądNoSuchProviderException.

Kotlin

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

Java

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

Biblioteka kryptograficzna Jetpack Security

Biblioteka kryptograficzna Jetpack Security została wycofana. Dotyczy to tylko sytuacji, gdy w pliku build.gradle w module aplikacji masz te zależności:

Groovy

dependencies {
    implementation "androidx.security:security-crypto:1.0.0"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.0.0")
}

Obsługiwane algorytmy

Oto identyfikatory algorytmu JCA obsługiwane na Androidzie: