System magazynu kluczy Android

System Keystore Androida umożliwia przechowywanie kluczy kryptograficznych w kontenerze, co utrudnia ich wyodrębnienie z urządzenia. Gdy klucze znajdują się w magazynie kluczy, można ich używać do operacji kryptograficznych, a materiały klucza nie można wyeksportować. System magazynu kluczy umożliwia też ograniczenie sposobu i czasu korzystania z kluczy, na przykład wymaganie uwierzytelniania użytkownika przed użyciem klucza lub ograniczenie korzystania z kluczy tylko do określonych trybów szyfrowania. Więcej informacji znajdziesz w sekcji Funkcje zabezpieczeń.

System Keystore jest używany przez interfejs API KeyChain, wprowadzony w Androidzie 4.0 (poziom interfejsu API 14), oraz przez usługę dostawcy Keystore na Androidzie, wprowadzoną w Androidzie 4.3 (poziom interfejsu API 18). Z tego dokumentu dowiesz się, kiedy i jak używać systemu Keystore na Androida.

Funkcje zabezpieczeń

System magazynu kluczy Androida chroni materiał klucza przed nieautoryzowanym użyciem na 2 sposoby. Po pierwsze, zmniejsza ryzyko nieautoryzowanego użycia kluczy z zewnątrz urządzenia z Androidem, zapobiegając wyodrębnianiu kluczy z procesów aplikacji i z urządzenia z Androidem jako całości. Po drugie, system magazynu kluczy zmniejsza ryzyko nieautoryzowanego użycia kluczy na urządzeniu z Androidem, ponieważ aplikacje określają autoryzowane użycia kluczy, a następnie nakładają te ograniczenia poza procesami aplikacji.

Zapobieganie wyodrębnianiu

Materiał klucza w przypadku kluczy w keystore Androida jest chroniony przed wyodrębnieniem za pomocą 2 środków bezpieczeństwa:

  • Materiał klucza nigdy nie jest uwzględniany w procesie zgłaszania. Gdy aplikacja wykonuje operacje kryptograficzne za pomocą klucza Keystore na Androidzie, szyfrowany tekst, tekst jawny i wiadomości do podpisania lub weryfikacji są przekazywane do procesu systemowego, który wykonuje operacje kryptograficzne. Jeśli proces aplikacji zostanie przejęty, atakujący będzie mógł użyć kluczy aplikacji, ale nie będzie w stanie wyodrębnić ich materiału (np. do użycia poza urządzeniem z Androidem).
  • Materiał klucza może być powiązany z bezpiecznym sprzętem urządzenia z Androidem, takim jak zaufane środowisko wykonawcze (TEE) lub element zabezpieczeń (SE). Gdy ta funkcja jest włączona dla klucza, materiał klucza nigdy nie jest ujawniany poza bezpiecznym sprzętem. Jeśli system operacyjny Android jest zaatakowany lub osoba przeprowadzająca atak może odczytać pamięć wewnętrzną urządzenia, osoba przeprowadzająca atak może mieć dostęp do kluczy magazynu kluczy Androida dowolnej aplikacji na urządzeniu z Androidem, ale nie może ich wyodrębnić z urządzenia. Ta funkcja jest włączona tylko wtedy, gdy bezpieczny sprzęt urządzenia obsługuje określoną kombinację algorytmu klucza, trybów blokowania, schematów wypełniania i sum kontrolnych, z którymi można używać klucza.

    Aby sprawdzić, czy funkcja jest włączona dla klucza, uzyskaj dla niego właściwość KeyInfo. Kolejny krok zależy od docelowej wersji pakietu SDK aplikacji:

    • Jeśli Twoja aplikacja jest kierowana na Androida 10 (poziom interfejsu API 29) lub nowszego, sprawdź wartość zwrotną getSecurityLevel(). Zwracane wartości odpowiadające KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT lub KeyProperties.SecurityLevelEnum.STRONGBOX wskazują, że klucz znajduje się na bezpiecznym sprzęcie.
    • Jeśli Twoja aplikacja jest kierowana na Androida 9 (poziom interfejsu API 28) lub niższego, sprawdź zwracaną wartość logiczną funkcji KeyInfo.isInsideSecurityHardware().

Moduł zabezpieczeń sprzętowych

Obsługiwane urządzenia z Androidem 9 (poziom interfejsu API 28) lub nowszym mogą mieć StrongBox Keymaster, czyli implementację Keymastera lub Keymint HAL, która znajduje się w elementach zabezpieczeń w ramach modułu zabezpieczeń sprzętowych. Moduł zabezpieczeń sprzętowych może odnosić się do wielu różnych implementacji przechowywania kluczy, w których nie można ich ujawnić przez kompromis w jądrze Linuksa, np. TEE. StrongBox odnosi się do urządzeń takich jak wbudowane elementy zabezpieczeń (eSE) lub jednostki przetwarzania zabezpieczeń (iSE) na procesorze SoC.

Moduł ten zawiera:

  • własny procesor,
  • Bezpieczne miejsce na dane
  • Prawdziwy generator liczb losowych
  • dodatkowe mechanizmy zapobiegające modyfikowaniu pakietów i nieautoryzowanemu instalowaniu aplikacji;
  • Bezpieczny minutnik
  • Pin do uruchamiania ponownego (lub odpowiednik), np. pin do wejść/wyjść ogólnego przeznaczenia (GPIO).

Na potrzeby implementacji StrongBox o niskim zużyciu energii obsługiwany jest podzbiór algorytmów i rozmiarów kluczy:

  • RSA 2048
  • AES 128 i 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256 (obsługuje rozmiary kluczy od 8 do 64 bajtów)
  • 3DES
  • Jednostki ADU o dłuższej długości
  • Atestacja klucza
  • Pomoc dotycząca poprawki H w przypadku przejścia na wyższą wersję

Podczas generowania lub importowania kluczy za pomocą klasy KeyStore możesz wskazać preferencje dotyczące przechowywania klucza w usłudze StrongBox Keymaster, przekazując wartość true do metody setIsStrongBoxBacked().

Chociaż StrongBox jest nieco wolniejszy i ma ograniczone zasoby (co oznacza, że obsługuje mniej równoczesnych operacji) w porównaniu z TEE, zapewnia lepsze zabezpieczenia przed atakami fizycznymi i z kanału bocznego. Jeśli zamiast wydajności zasobów aplikacji wolisz priorytetowo traktować wyższe gwarancje bezpieczeństwa, zalecamy korzystanie z StrongBox na urządzeniach, na których jest dostępna. Jeśli StrongBox jest niedostępny, aplikacja może zawsze skorzystać z TEE do przechowywania kluczowych materiałów.

Autoryzacje użycia klucza

Aby uniknąć nieautoryzowanego użycia kluczy na urządzeniu z Androidem, aplikacje mogą określać autoryzowane sposoby użycia kluczy podczas ich generowania lub importowania. Po wygenerowaniu lub zaimportowaniu klucza nie można zmienić jego autoryzacji. Następnie za każdym razem, gdy używany jest klucz, żądania autoryzacji są wymuszane przez magazyn kluczy Androida. Jest to zaawansowana funkcja zabezpieczeń, która jest zwykle przydatna tylko wtedy, gdy wymagane jest, aby naruszenie zabezpieczeń procesu aplikacji po wygenerowaniu/importowaniu klucza (ale nie przed lub w jego trakcie) nie mogło prowadzić do nieautoryzowanego użycia klucza.

Obsługiwane uprawnienia do korzystania z klucza można podzielić na te kategorie:

  • Szyfrowanie: klucz może być używany tylko z autoryzowanymi algorytmami kluczy, operacjami lub celami (szyfrowanie, odszyfrowywanie, podpisywanie, weryfikacja), schematami wypełniania, trybami blokowania lub ciągami kontrolnymi.
  • Czasowy interwał ważności: klucz może być używany tylko w określonym przedziale czasu.
  • Uwierzytelnianie użytkownika: klucza można używać tylko wtedy, gdy użytkownik został uwierzytelniony w wystarczająco długim czasie. Zobacz artykuł Wymaganie uwierzytelniania użytkowników przy użyciu klucza.

Jako dodatkowy środek bezpieczeństwa w przypadku kluczy, których materiały klucza znajdują się na bezpiecznym sprzęcie (patrz KeyInfo.isInsideSecurityHardware() lub, w przypadku aplikacji kierowanych na Androida 10 (poziom interfejsu API 29) lub nowszego, KeyInfo.getSecurityLevel()), niektóre autoryzacje użycia klucza mogą być wymuszane przez bezpieczny sprzęt, w zależności od urządzenia z Androidem. Bezpieczny sprzęt zwykle wymusza uwierzytelnianie i autoryzację użytkownika za pomocą kryptografii. Jednak bezpieczny sprzęt zazwyczaj nie egzekwuje autoryzacji interwałów ważności w czasie, ponieważ zwykle nie ma niezależnego, bezpiecznego zegara w czasie rzeczywistym.

Za pomocą KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware() możesz sprawdzić, czy autoryzacja uwierzytelniania użytkownika klucza jest wymuszana przez bezpieczny sprzęt.

Wybierz, czy chcesz używać katalogu kluczy czy dostawcy magazynu kluczy Androida

Jeśli chcesz uzyskać dane logowania dla całego systemu, użyj interfejsu API KeyChain. Gdy aplikacja prosi o używanie danych logowania za pomocą interfejsu API KeyChain, użytkownicy mogą wybrać w interfejsie użytkownika, do których zainstalowanych danych logowania aplikacja ma mieć dostęp. Dzięki temu kilka aplikacji może używać tego samego zestawu danych logowania za zgodą użytkownika.

Użyj dostawcy Keystore na Androida, aby umożliwić poszczególnej aplikacji przechowywanie własnych danych logowania, do których dostęp ma tylko ta aplikacja. Dzięki temu aplikacje mogą zarządzać danymi logowania, których używają tylko one, a jednocześnie zapewniać te same zabezpieczenia, co interfejs API KeyChain w przypadku danych logowania dla całego systemu. Ta metoda nie wymaga od użytkownika wyboru danych logowania.

Korzystanie z dostawcy Keystore na Androida

Aby korzystać z tej funkcji, musisz używać standardowych klas KeyStore, KeyPairGenerator lub KeyGenerator oraz dostawcy AndroidKeyStore wprowadzonego w Androidzie 4.3 (poziom API 18).

AndroidKeyStore jest zarejestrowany jako typ KeyStore do użycia z metodą KeyStore.getInstance(type) oraz jako dostawca do użycia z metodami KeyPairGenerator.getInstance(algorithm, provider)KeyGenerator.getInstance(algorithm, provider).

Operacje kryptograficzne mogą być czasochłonne, dlatego aplikacje powinny unikać używania interfejsu AndroidKeyStore w głównym wątku, aby zapewnić płynność działania interfejsu. (Aplikacja StrictMode może pomóc znaleźć miejsca, w których tak nie jest).

Wygeneruj nowy klucz prywatny lub tajny

Aby wygenerować nowy certyfikat KeyPair zawierający certyfikat PrivateKey, musisz podać początkowe atrybuty X.509 certyfikatu. Możesz użyć opcji KeyStore.setKeyEntry(), aby później zastąpić certyfikat certyfikatem podpisanym przez urząd certyfikacji (CA).

Aby wygenerować parę kluczy, użyj KeyPairGenerator z 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();

Importowanie zaszyfrowanych kluczy do bezpiecznego sprzętu

Android 9 (poziom interfejsu API 28) i nowsze umożliwia bezpieczne importowanie zaszyfrowanych kluczy do magazynu kluczy za pomocą formatu klucza ASN.1. Następnie Keymaster odszyfrowuje klucze w magazynie kluczy, dzięki czemu ich zawartość nigdy nie pojawia się w postaci zwykłego tekstu w pamięci hosta urządzenia. Ten proces zapewnia dodatkowe zabezpieczenia podczas odszyfrowywania kluczy.

Aby umożliwić bezpieczne importowanie zaszyfrowanych kluczy do magazynu kluczy, wykonaj te czynności:

  1. Wygeneruj parę kluczy, która używa celu PURPOSE_WRAP_KEY. Zalecamy dodanie atestu również do tej pary kluczy.

  2. Na serwerze lub komputerze, któremu ufasz, wygeneruj wiadomość ASN.1 dla SecureKeyWrapper.

    Opakowanie zawiera ten schemat:

       KeyDescription ::= SEQUENCE {
           keyFormat INTEGER,
           authorizationList AuthorizationList
       }
    
       SecureKeyWrapper ::= SEQUENCE {
           wrapperFormatVersion INTEGER,
           encryptedTransportKey OCTET_STRING,
           initializationVector OCTET_STRING,
           keyDescription KeyDescription,
           secureKey OCTET_STRING,
           tag OCTET_STRING
       }
    
  3. Utwórz obiekt WrappedKeyEntry, przekazując komunikat ASN.1 jako tablicę bajtów.

  4. Przekaż obiekt WrappedKeyEntry do przeciążenia funkcji setEntry(), która przyjmuje obiekt Keystore.Entry.

Praca z rekordami katalogu kluczy

Dostęp do dostawcy AndroidKeyStore można uzyskać za pomocą wszystkich standardowych interfejsów API KeyStore.

Wyświetlanie listy wpisów

Wyświetl wpisy w magazynie kluczy, wywołując metodę 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();

Podpisywanie i weryfikowanie danych

Podpisz dane, pobierając KeyStore.Entry z magazynu kluczy i korzystając z interfejsów API Signature, takich jak 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();

Podobnie zweryfikuj dane za pomocą metody 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);

Wymaganie uwierzytelniania użytkowników w przypadku użycia klucza

Podczas generowania lub importowania klucza do usługi AndroidKeyStore możesz określić, że klucz może być używany tylko wtedy, gdy użytkownik został uwierzytelniony. Użytkownik jest uwierzytelniony za pomocą podzbioru danych logowania do ekranu blokady (wzór, kod PIN, hasło, dane biometryczne).

Jest to zaawansowana funkcja zabezpieczeń, która jest przydatna tylko wtedy, gdy wymagasz, aby po wygenerowaniu lub zaimportowaniu klucza (ale nie przed lub w trakcie generowania lub importowania) aplikacja nie mogła ominąć wymagań dotyczących uwierzytelniania użytkownika.

Jeśli klucz może być używany tylko wtedy, gdy użytkownik został uwierzytelniony, możesz wywołać funkcję setUserAuthenticationParameters(), aby skonfigurować klucz do działania w jednym z tych trybów:

Autoryzowanie na określony czas
Wszystkie klucze są autoryzowane do użycia, gdy tylko użytkownik uwierzytelni się za pomocą jednego z określonych zestawów danych logowania.
Autoryzuj na czas konkretnej operacji kryptograficznej

Każda operacja z użyciem określonego klucza musi być autoryzowana przez użytkownika.

Aplikacja rozpoczyna ten proces, wywołując funkcję authenticate() w instancji BiometricPrompt.

W przypadku każdego utworzonego klucza możesz wybrać obsługę silnych danych logowania biometrycznych, danych logowania na ekranie blokady lub obu tych typów danych. Aby sprawdzić, czy użytkownik skonfigurował poświadczenia, na których opiera się klucz aplikacji, wywołaj funkcję canAuthenticate().

Jeśli klucz obsługuje tylko dane logowania biometryczne, jest on domyślnie unieważniany po każdym dodaniu nowych rejestracji biometrycznych. Możesz skonfigurować klucz tak, aby pozostawał ważny po dodaniu nowych rejestracji biometrycznych. Aby to zrobić, prześlij wartość false do funkcji setInvalidatedByBiometricEnrollment().

Dowiedz się więcej o dodawaniu do aplikacji funkcji uwierzytelniania biometrycznego, w tym o wyświetlaniu okna uwierzytelniania biometrycznego.

Obsługiwane algorytmy

Artykuły w blogu

Przeczytaj wpis na blogu Ujednolicenie dostępu do KeyStore w ICS.