Android Keystore — to systemowy mechanizm Androida do bezpiecznego przechowywania kluczy kryptograficznych w izolacji sprzętowej. System wykorzystuje Trusted Execution Environment (TEE) na urządzeniach z ARM TrustZone lub dedykowany Secure Element do ochrony kluczy na poziomie układu. Według Android Open Source Project, Keystore obsługuje algorytmy RSA, EC, AES i HMAC z generowaniem kluczy bezpośrednio w chronionym środowisku.
Najważniejsze
Android Keystore — to dostawca kryptograficzny (provider) zaimplementowany w Androidzie od API 1 (Android 1.0), ale pełne wsparcie sprzętowe pojawiło się od Androida 4.3 (API 18). Keystore rozwiązuje problem bezpiecznego przechowywania kluczy prywatnych w taki sposób, że nawet przy kompromitacji systemu operacyjnego atakujący nie może wyodrębnić kluczy w jawnej postaci.
Architektura Android Keystore składa się z trzech warstw: aplikacyjnego API (java.security.KeyStore), usługi systemowej (keystore daemon) i warstwy sprzętowej (Keymaster HAL). Aplikacja korzysta przez standardowe API Java Cryptography Architecture (JCA), a usługa systemowa kieruje żądania do Keymaster działającego w TEE.
Wszystkie operacje kryptograficzne z kluczami (podpis, deszyfrowanie) są wykonywane wewnątrz TEE lub Secure Element. Klucze nigdy nie opuszczają chronionego środowiska — aplikacja otrzymuje tylko uchwyt (alias) do odwoływania się do klucza. To fundamentalna różnica w porównaniu z programowymi KeyStore, gdzie klucze są potencjalnie dostępne w pamięci procesu.
Standardowy JKS (Java KeyStore) lub BKS (Bouncy Castle) przechowują klucze w plikach chronionych hasłem. Android Keystore przechowuje klucze w izolacji sprzętowej, gdzie są chronione nawet przed rootem. JKS jest podatny na bezpośredni dostęp do systemu plików, Android Keystore — nie.
Inna różnica: w Android Keystore klucze mają ścisłe parametry użycia (purpose — tylko sign/verify/encrypt/decrypt), określone przy generacji. Nie można ich później zmienić, co zapobiega niewłaściwemu użyciu klucza.
Podczas tworzenia nowego klucza aplikacja wywołuje KeyPairGenerator lub KeyGenerator z KeyGenParameterSpec, który zawiera wszystkie parametry przyszłego klucza. System przekazuje żądanie do Keymaster HAL, który generuje klucz wewnątrz TEE i zwraca uchwyt.
Metoda KeyGenParameterSpec.Builder przyjmuje obowiązkowe parametry: nazwę klucza w Keystore, przeznaczenie (PURPOSE_SIGN, PURPOSE_ENCRYPT), algorytm (RSA, EC, AES). Dodatkowo: digest (SHA-256), padding (PKCS7), userAuthenticationRequired (biometria), keyValidityStart/End (ograniczenia czasowe).
Po ustawieniu parametrów KeyPairGenerator.generateKeyPair() zwraca KeyPair, gdzie PrivateKey to obiekt delegujący operacje do Keymaster. Klucz publiczny może być wyodrębniony, prywatny — nie. Istnieje tylko wewnątrz TEE.
import java.security.KeyPairGenerator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
fun generateKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGN_PADDING_RSA_PKCS1)
.setUserAuthenticationRequired(true)
.build()
val kpGen = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
kpGen.initialize(spec)
kpGen.generateKeyPair()
}
Signature dla ECDSA lub RSA-PSS jest tworzone przez standardowe API: Signature.getInstance(algorithm).initSign(privateKey). Operacja podpisu jest wykonywana w TEE: aplikacja przekazuje dane, Keymaster podpisuje je sprzętowo i zwraca podpis. Klucz i dane nie mieszają się we wspólnej pamięci.
Dla ochrony biometrycznej konieczne jest przed podpisem uwierzytelnienie użytkownika przez BiometricPrompt. Bez udanego uwierzytelnienia Keymaster nie wykonuje operacji, zwracając CryptoAuthenticationException.
import java.security.KeyStore
import java.security.Signature
import androidx.biometric.BiometricPrompt
fun signWithBiometric(alias: String) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val entry = ks.getEntry(alias, null) as KeyStore.PrivateKeyEntry
val signature = Signature.getInstance("SHA256withRSA")
signature.initSign(entry.privateKey)
// BiometricPrompt z CryptoObject(signature) żąda FaceID/PIN
}
Android obsługuje dwa tryby przechowywania kluczy: programowy (na urządzeniach bez TEE) i sprzętowy (na urządzeniach z TEE lub Secure Element). Tryb zależy od możliwości SoC i wersji Androida.
Na urządzeniach bez Trusted Execution Environment (przed Android 4.3 lub budżetowe SoC) klucze są przechowywane w zaszyfrowanej postaci przy użyciu klucza głównego pochodzącego z hasła ekranu blokady. Ten tryb jest mniej bezpieczny — klucze są dostępne w pamięci procesu podczas wykonywania operacji kryptograficznych.
Poziom ochrony opiera się na szyfrowaniu pliku KeyStore za pomocą AES-256-GCM. Klucz szyfrowania jest generowany na podstawie hasła użytkownika lub PIN-u przez Scrypt (PBKDF2 z dużą liczbą iteracji).
Na nowoczesnych urządzeniach używany jest Keymaster 4.x w TEE (ARM TrustZone). Klucze są generowane, przechowywane i używane wyłącznie wewnątrz TrustZone. Nawet jądro Linuxa nie ma dostępu do kluczy prywatnych — tylko Keymaster HAL może wykonywać operacje.
Secure Element (np. eSE w Samsung Knox lub StrongBox w Google Pixel 3+) — to oddzielny układ z własnym procesorem i pamięcią. Jest certyfikowany Common Criteria EAL 4+ i zapewnia maksymalny poziom ochrony, w tym ochronę przed fizycznym otwarciem.
| Typ | Miejsce przechowywania | Poziom ochrony | Dostępne od API |
|---|---|---|---|
| Software | Plik /data/misc/keystore | Średni (AES-256) | API 1+ |
| Keymaster 3 | TEE (TrustZone) | Wysoki | API 23+ |
| Keymaster 4 | TEE + Secure I/O | Bardzo wysoki | API 28+ |
| StrongBox | Sprzętowy Secure Element | Maksymalny | API 28+, opcjonalnie |
Android Keystore jest zintegrowany z Java Cryptography Architecture (JCA). Do dostępu do dostawcy używa się standardowego KeyStore.getInstance("AndroidKeyStore"). API dostępne od API 18.
Metoda KeyStore.load(null) ładuje kontener KeyStore bieżącej aplikacji. Hasło nie jest wymagane — Android używa kontekstu aplikacji i jej UID do rozgraniczenia dostępu. Każda aplikacja widzi tylko swoje wpisy, chyba że używany jest wspólny UID.
Metody setEntry i getEntry działają z KeyStore.PrivateKeyEntry, SecretKeyEntry lub TrustedCertificateEntry. Parametr ProtectionParameter — zawsze null dla Android Keystore (ochrona zaimplementowana na poziomie systemu).
import java.security.KeyStore
import java.security.cert.Certificate
import android.security.keystore.KeyProtection
fun storeSecretKey(alias: String, key: SecretKey) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val prot = KeyProtection.Builder(
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.build()
ks.setEntry(alias, KeyStore.SecretKeyEntry(key), prot)
}
Za pomocą KeyCharacteristics można określić, w jakim środowisku przechowywany jest klucz: programowym KeyStore, TEE lub StrongBox. Metoda getKeyCharacteristics() zwraca zestaw flag: FLAG_HARDWARE (keymaster), FLAG_SECURE_ELEMENT (StrongBox), FLAG_TRUSTED_USER_PRESENCE_REQUIRED (biometria).
Android Keystore obsługuje szeroki zestaw algorytmów kryptograficznych, podzielony na trzy kategorie: asymetryczne, symetryczne i MAC. Wsparcie dla konkretnych algorytmów zależy od wersji Keymaster HAL.
RSA (1024–4096 bitów) — do podpisu (PKCS1, PSS) i szyfrowania (OAEP, PKCS1). EC (P-224, P-256, P-384, P-521) — do podpisu ECDSA i uzgadniania ECDH. AES (128, 256 bitów) — do szyfrowania symetrycznego w trybach CBC, CTR, GCM. HMAC (SHA1, SHA256, SHA512) — do uwierzytelniania wiadomości.
Dla każdego klucza określa się setPurposes, ograniczający możliwe operacje. Klucz RSA z PURPOSE_SIGN nie może być użyty do szyfrowania, nawet jeśli atakujący ma dostęp do API. To wymuszanie użycia klucza na poziomie sprzętu.
Keymaster zawiera licznik nieudanych prób uwierzytelnienia biometrycznego. Po określonej liczbie nieudanych prób (konfigurowane przez setInvalidatedByBiometricEnrollment) klucz staje się niedostępny i wymaga usunięcia/regeneracji. Po usunięciu wszystkich szablonów biometrycznych wszystkie klucze z userAuthenticationRequired=true są automatycznie unieważniane.
Obsługiwana jest również Key Attestation (Android 8.1+): na żądanie aplikacji Keymaster podpisuje certyfikat z informacjami o charakterystykach klucza (sprzętowy/programowy, algorytm, purges). Serwer może zweryfikować ten certyfikat w celu potwierdzenia, że klucz został utworzony w zaufanym środowisku.
Często zadawane pytania
Java KeyStore przechowuje klucze w pliku chronionym hasłem (JKS, BKS). Android Keystore używa izolacji sprzętowej TEE lub Secure Element. Java KeyStore jest podatny przy dostępie root, Android Keystore — nie, ponieważ klucze prywatne nigdy nie opuszczają chronionego środowiska.
Tak, przez KeyStore.setEntry z KeyProtection. Jednak zaimportowany klucz nie będzie miał ochrony sprzętowej — będzie przechowywany w programowym Keystore, zaszyfrowanym kluczem głównym. Dla maksymalnego bezpieczeństwa zawsze generuj klucze wewnątrz Keystore.
Użyj KeyChain.isBoundKeyAlgorithm lub sprawdź KeyCharacteristics po wygenerowaniu klucza. Obecność FLAG_HARDWARE w charakterystykach oznacza, że klucz został utworzony w TEE. Można też sprawdzić android.security.keystore.isHardwareBacked().
Po usunięciu aplikacji Android usuwa wszystkie jej klucze z Keystore. Dane są nieodwracalnie tracone. Przy ponownej instalacji aplikacja musi wygenerować nowe klucze. Kopia zapasowa kluczy przez TEE jest niemożliwa ze względów architektonicznych.
Na zablokowanym urządzeniu Keymaster nie wykonuje żadnych operacji. Klucze z userAuthenticationRequired=true wymagają potwierdzenia biometrycznego za każdym razem. Nawet z dostępem root atakujący nie może wywołać Keymaster bezpośrednio — tylko przez Android Keystore service.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również