EncryptedSharedPreferences — to komponent biblioteki AndroidX Security, zapewniający przezroczyste szyfrowanie danych zapisywanych przez API SharedPreferences. W przeciwieństwie do zwykłych SharedPreferences, gdzie dane są przechowywane w otwartym pliku XML, EncryptedSharedPreferences automatycznie szyfruje klucze i wartości przed zapisem na dysk. Według Android Developers, biblioteka używa AES-256 GCM dla wartości i AES-256 SIV (RFC 5297) dla kluczy, zapewniając poufność i integralność danych.
Najważniejsze
EncryptedSharedPreferences — to klasa z pakietu androidx.security.crypto, wprowadzona w AndroidX Security 1.0.0 (2019). Implementuje interfejs SharedPreferences, ale wszystkie operacje zapisu (putString, putInt, putBoolean itd.) wstępnie szyfrują dane, a operacje odczytu odszyfrowują je przed zwróceniem.
Standardowe SharedPreferences zapisują dane w pliku XML w katalogu aplikacji (/data/data/package/shared_prefs/). Plik nie jest zaszyfrowany — przy root dostępie do urządzenia lub analizie kopii zapasowej wszystkie dane są czytelne jako zwykły XML. Tokeny uwierzytelniania, klucze API, dane osobowe użytkownika stają się dostępne dla atakującego.
EncryptedSharedPreferences rozwiązuje ten problem na poziomie biblioteki: dane są szyfrowane przed zapisem na dysk i odszyfrowywane przy odczycie. Deweloper nie musi ręcznie wywoływać funkcji kryptograficznych — API pozostaje identyczne jak w zwykłych SharedPreferences.
Biblioteka AndroidX Security v1.0.0 została wydana w grudniu 2019. EncryptedSharedPreferences zastąpiła przestarzałe podejście z ręcznym szyfrowaniem przez Cipher + SharedPreferences. Obecna stabilna wersja to 1.1.0-alpha06 (2024), obsługująca API 19+. Biblioteka wchodzi w skład Jetpack i nie wymaga dodatkowych uprawnień.
Według Google Security Blog (2024), EncryptedSharedPreferences to zalecany sposób przechowywania poufnych ustawień aplikacji, które nie wymagają synchronizacji przez chmurę. Dla bardziej złożonych scenariuszy zaleca się Room z szyfrowaniem przez SQLCipher.
EncryptedSharedPreferences używa dwupoziomowego schematu szyfrowania: klucz główny (Master Key) jest przechowywany w Android Keystore, a do szyfrowania danych używane są klucze pochodne. To połączenie ochrony Keystore i wydajności szyfrowania symetrycznego.
Dla wartości używany jest AES-256 GCM (Galois/Counter Mode) — tryb uwierzytelnionego szyfrowania (AEAD), zapewniający poufność i integralność danych. Dla kluczy (nazw parametrów) stosuje się AES-256 SIV (RFC 5297) — deterministyczne szyfrowanie, niezbędne do wyszukiwania po kluczu bez ujawniania jego zawartości.
Każdy plik EncryptedSharedPreferences zawiera zaszyfrowane pary klucz-wartość. Struktura pliku: najpierw nagłówek z metadanymi (wersja, identyfikator klucza), następnie lista zaszyfrowanych wpisów. Plik nie jest prawidłowym XML i nie jest czytelny w edytorach tekstowych.
Klasa MasterKey odpowiada za tworzenie i zarządzanie 256-bitowym kluczem głównym, który jest przechowywany w Android Keystore. MasterKey.Builder pozwala skonfigurować: typ przechowywania (Keystore lub programowy), ochronę biometrią, czas życia klucza. Domyślnie klucz główny jest generowany w Android Keystore z algorytmem AES/GCM/NoPadding.
import androidx.security.crypto.MasterKey
import androidx.security.crypto.EncryptedSharedPreferences
fun getEncryptedPrefs() {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}
EncryptedSharedPreferences.create przyjmuje pięć parametrów: kontekst, nazwę pliku, klucz główny, schemat szyfrowania kluczy i schemat szyfrowania wartości. Wybór schematów wpływa na wydajność i poziom ochrony.
AES256_SIV — szyfrowanie deterministyczne: identyczne klucze zawsze dają identyczny zaszyfrowany tekst. Jest to niezbędne do wyszukiwania po kluczu (SharedPreferences.getX(key)). Wadą jest to, że atakujący może określić, które klucze są używane, po powtarzających się szyfrogramach. AES256_SIV2 — ulepszona wersja z dodatkową randomizacją.
Dla wartości używany jest AES256_GCM. GCM dodaje 12-bajtowy IV (wektor inicjalizacyjny) i 16-bajtowy znacznik uwierzytelniający do każdej wartości. Zapewnia to poufność (nikt nie odczyta wartości) i uwierzytelnienie (nikt nie podmieni wartości bez wykrycia).
Metoda setUserAuthenticationRequired(true) w MasterKey.Builder wymaga potwierdzenia biometrycznego przed uzyskaniem klucza głównego z Keystore. Dodaje to dodatkowy poziom: nawet jeśli aplikacja jest uruchomiona na odblokowanym urządzeniu, atakujący nie odczyta EncryptedSharedPreferences bez Face ID lub Touch ID.
Ważne: przy setUserAuthenticationRequired klucz główny staje się niedostępny, jeśli użytkownik zmienił lub usunął biometrię. Należy obsługiwać KeyPermanentlyInvalidatedException i utworzyć nowy klucz główny z migracją danych.
fun createBiometricKey(): MasterKey {
return MasterKey.Builder(context)
.setKeyScheme(MasterKey.AES256_GCM_SPEC)
.setUserAuthenticationRequired(true)
.setRequestStrongBoxBacked(true)
.build()
}
fun writeSecureToken(token: String) {
try {
prefs.edit().putString("auth_token", token).apply()
} catch (e: KeyPermanentlyInvalidatedException) {
// Biometria się zmieniła — trzeba ponownie utworzyć klucz
}
}
Rozważmy pełny przykład integracji EncryptedSharedPreferences w aplikacji Android w Kotlin. Biblioteka androidx.security:security-crypto jest dodawana przez Gradle.
W pliku build.gradle (app) dodaj: implementation "androidx.security:security-crypto:1.1.0-alpha06". Dla projektów Kotlin wymagany jest również kotlin-stdlib. Inicjalizacja MasterKey odbywa się jednorazowo, zwykle w Application.onCreate lub przez kontener DI.
Po utworzeniu instancji EncryptedSharedPreferences API nie różni się od zwykłych SharedPreferences. edit() zwraca Editor, wszystkie metody (putString, getString, putBoolean, getBoolean) działają analogicznie. Różnica jest tylko wewnątrz: dane są szyfrowane przy zapisie i odszyfrowywane przy odczycie.
class AuthRepository(context: Context) {
private val prefs = createEncryptedPrefs(context)
fun saveCredentials(login: String, password: String) {
prefs.edit()
.putString("login", login)
.putString("password", password)
.apply()
}
fun getToken(): String? {
return prefs.getString("auth_token", null)
}
fun clearAll() {
prefs.edit().clear().apply()
}
}
Do migracji istniejących danych z niezabezpieczonych SharedPreferences do EncryptedSharedPreferences należy: odczytać wszystkie dane ze starego pliku, utworzyć nowy EncryptedSharedPreferences, zapisać wszystkie dane, usunąć stary plik. Google nie udostępnia wbudowanego narzędzia migracji — deweloper implementuje je samodzielnie.
Wybór między SharedPreferences a EncryptedSharedPreferences zależy od rodzaju przechowywanych danych. Dla ustawień interfejsu (motyw, język, sortowanie) zwykłe SharedPreferences są wystarczające. Dla poufnych informacji (tokeny, hasła, klucze) EncryptedSharedPreferences jest obowiązkowy.
EncryptedSharedPreferences jest wolniejsze od zwykłych z powodu operacji kryptograficznych. Zapis pojedynczej wartości tekstowej trwa ~5-15 ms (zależy od rozmiaru danych i sprzętowego przyspieszenia AES). Odczyt — 2-5 ms. Dla większości aplikacji jest to niezauważalne, ale przy operacjach wsadowych (migracja, przywracanie) warto używać apply() zamiast commit().
Zwykłe SharedPreferences nie zapewniają żadnej ochrony kryptograficznej: plik XML może być odczytany przez każdy proces z root dostępu lub przez adb backup. EncryptedSharedPreferences szyfruje dane na poziomie aplikacji, a klucz główny jest przechowywany w Android Keystore z możliwością ochrony sprzętowej (StrongBox).
| Cecha | SharedPreferences | EncryptedSharedPreferences |
|---|---|---|
| Przechowywanie | Otwarty XML | Zaszyfrowany plik binarny |
| Szyfrowanie | Brak | AES-256 GCM + SIV |
| Ochrona kluczy | Brak | Android Keystore + StrongBox |
| Wydajność | 0.1-1 ms | 2-15 ms |
| Zalecenie | Ustawienia UI | Tokeny, klucze, PII |
Używaj EncryptedSharedPreferences do przechowywania: refresh token OAuth, kluczy API dla zewnętrznych serwisów, adresu email lub numeru telefonu użytkownika, poufnych ustawień aplikacji (PIN, flagi uwierzytelniania). Do przechowywania danych biometrycznych lub dużych dokumentów EncryptedSharedPreferences nie nadaje się — użyj EncryptedFile lub Room z SQLCipher.
Ogólna zasada: jeśli dane po wycieku zaszkodzą użytkownikowi lub firmie — używaj EncryptedSharedPreferences. Jeśli dane są tylko kosmetyczne (motyw, język, sortowanie) — zwykłe SharedPreferences. EncryptedSharedPreferences ma sens wdrażać od razu, bez refaktoryzacji: zastąpienie w istniejącym projekcie wymaga migracji i obsługi starych niezaszyfrowanych danych.
Pamiętaj, że EncryptedSharedPreferences nie chroni danych podczas pracy aplikacji — tylko na dysku. Jeśli atakujący ma dostęp do pamięci procesu, odszyfrowane dane mogą zostać przechwycone. Używaj dodatkowej ochrony: ProGuard/DexGuard do zaciemniania kodu.
Często zadawane pytania
Jetpack DataStore — to nowocześniejsza alternatywa dla SharedPreferences, oparta na Flow i Kotlin coroutines. DataStore domyślnie nie szyfruje danych, ale może być połączony z EncryptedSharedPreferences lub używany z ręcznym szyfrowaniem przez Proto DataStore z protokołami kryptograficznymi.
Nie zaleca się. EncryptedSharedPreferences jest przeznaczony dla małych woluminów (do 100-200 KB). Do większych danych używaj Room z SQLCipher lub szyfrowania plików przez EncryptedFile z tej samej biblioteki AndroidX Security.
Nie, automatyczna migracja schematu nie istnieje. Przy zmianie struktury danych deweloper musi ręcznie odczytać stare dane przez stary KeyGen i zapisać je przez nowy. Zaleca się przechowywanie wersji schematu w osobnym parametrze.
AndroidX Security 1.0.0 obsługuje API 19+ (Android KitKat). Wersja 1.1.0-alpha06 również obsługuje API 19+. Do korzystania z StrongBox wymagane jest API 28+ i urządzenie z obsługą sprzętową (Google Pixel 3+, Samsung Galaxy S9+).
Tak, refresh token — to jeden z głównych scenariuszy użycia. Szyfrowanie AES-256 GCM, klucz główny w Keystore, ochrona biometryczna — wystarczający poziom dla tokenów OAuth. Dla access token z krótkim czasem życia również jest odpowiedni, choć niektóre zespoły preferują przechowywanie go w pamięci.
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ż