Kryptografia

W tym dokumencie opisujemy prawidłowy sposób korzystania z funkcji kryptograficznych Androida i podajemy przykłady ich użycia. Jeśli Twoja aplikacja wymaga większego bezpieczeństwa kluczy, użyj systemu Android Keystore.

Określanie dostawcy tylko w systemie Android Keystore

Jeśli używasz systemu Android Keystore, musisz podać dostawcę.

W innych sytuacjach Android nie gwarantuje jednak konkretnego dostawcy dla danego algorytmu. Określenie dostawcy bez użycia systemu Android Keystore może powodować problemy z kompatybilnością w przyszłych wersjach.

Wybierz rekomendowany algorytm

Jeśli możesz wybrać algorytm (np. gdy nie musisz zachowywać zgodności z systemem innej firmy), zalecamy używanie 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)

wykonywać typowe operacje kryptograficzne,

W sekcjach poniżej znajdziesz fragmenty kodu, które pokazują, jak wykonywać w aplikacji typowe operacje kryptograficzne.

Szyfrowanie wiadomości

KotlinJava
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
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

KotlinJava
val message: ByteArray = ...
val md = MessageDigest.getInstance("SHA-256")
val digest: ByteArray = md.digest(message)
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 działania programu, odczytać z pliku dołączonego do aplikacji lub uzyskać z innego źródła w zależności od potrzeb.

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

Weryfikowanie podpisu cyfrowego

Musisz mieć PublicKey obiekt zawierający klucz publiczny sygnatariusza, 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.

KotlinJava
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)
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 kwestie zgodności. W tej sekcji omawiamy te, które najprawdopodobniej napotkasz.

Skrót wiadomości OAEP MGF1

Szyfry RSA OAEP są parametryzowane przez 2 różne skróty wiadomości: „główny” skrót i skrót MGF1. Istnieją identyfikatory Cipher zawierające nazwy skrótów, np. Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding"), które określają główny skrót i pozostawiają skrót MGF1 nieokreślony. W przypadku Keystore na Androida do wygenerowania skrótu MGF1 używany jest algorytm SHA-1, a w przypadku innych dostawców usług kryptograficznych na Androida oba skróty są takie same.

Aby mieć większą kontrolę nad skrótami używanymi przez aplikację, poproś o szyfr z OAEPPadding, jak w Cipher.getInstance("RSA/ECB/OAEPPadding"), i podaj OAEPParameterSpec do init(), aby wyraźnie wybrać oba skróty. Pokazuje to poniższy kod:

KotlinJava
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))
        }
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 aplikacji.

Algorytmy Bouncy Castle

Implementacje wielu algorytmów w Bouncy Castle są wycofane. Dotyczy to tylko przypadków, w których wyraźnie zażądasz dostawcy Bouncy Castle, jak pokazano w tym przykładzie:

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

Jak wspomnieliśmy w sekcji określanie dostawcy tylko za pomocą systemu Android Keystore, odradzamy wysyłanie żądań do konkretnego dostawcy. Jeśli postępujesz zgodnie z tymi wytycznymi, to wycofanie nie będzie Cię dotyczyć.

Szyfry oparte na hasłach bez wektora inicjującego

Szyfry oparte na hasłach (PBE), które wymagają wektora inicjującego (IV), mogą go uzyskać z klucza, jeśli jest on odpowiednio skonstruowany, lub z jawnie przekazanego wektora IV. Jeśli przekażesz klucz PBE, który nie zawiera wektora inicjującego, i nie przekażesz jawnego wektora inicjującego, szyfry PBE na Androidzie będą obecnie zakładać wektor inicjujący o wartości zero.

Podczas korzystania z szyfrów PBE zawsze przekazuj jawny wektor inicjujący, jak pokazano w tym fragmencie kodu:

KotlinJava
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))
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 Crypto Java Cryptography Architecture (JCA) został usunięty. Jeśli aplikacja zażąda instancji dostawcy Crypto, np. wywołując tę metodę, wystąpi błąd NoSuchProviderException.

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

Biblioteka Jetpack security-crypto

Wszystkie interfejsy API w bibliotece security-crypto Jetpack zostały wycofane w stabilnej wersji 1.1.0. Nie będzie kolejnych wersji tej biblioteki.

Adnotacje o wycofaniu są widoczne, jeśli w pliku build.gradle modułu aplikacji masz te zależności:

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

Obsługiwane algorytmy

Oto identyfikatory algorytmów JCA obsługiwane na Androidzie: