EncryptedSharedPreferences는 SharedPreferences API를 통해 저장된 데이터의 투명한 암호화를 제공하는 AndroidX Security 라이브러리의 컴포넌트입니다. 데이터가 일반 XML 파일에 저장되는 일반 SharedPreferences와 달리, EncryptedSharedPreferences는 디스크에 쓰기 전에 키와 값을 자동으로 암호화합니다. Android Developers에 따르면, 이 라이브러리는 값에 AES-256 GCM을, 키에 AES-256 SIV(RFC 5297)를 사용하여 데이터의 기밀성과 무결성을 보장합니다.
핵심 사항
EncryptedSharedPreferences는 androidx.security.crypto 패키지의 클래스로, AndroidX Security 1.0.0(2019)에서 도입되었습니다. SharedPreferences 인터페이스를 구현하지만, 모든 쓰기 작업(putString, putInt, putBoolean 등)은 데이터를 미리 암호화하고 읽기 작업은 반환 전에 데이터를 복호화합니다.
표준 SharedPreferences는 앱 디렉토리(/data/data/package/shared_prefs/)의 XML 파일에 데이터를 저장합니다. 파일이 암호화되지 않아 장치에 대한 루트 액세스 또는 백업 분석 시 모든 데이터가 일반 XML로 읽힙니다. 인증 토큰, API 키 및 사용자의 개인 데이터가 공격자에게 노출됩니다.
EncryptedSharedPreferences는 라이브러리 수준에서 이 문제를 해결합니다. 데이터는 디스크에 쓰기 전에 암호화되고 읽을 때 복호화됩니다. 개발자가 수동으로 암호화 함수를 호출할 필요가 없으며 API는 일반 SharedPreferences와 동일합니다.
AndroidX Security 라이브러리 v1.0.0은 2019년 12월에 출시되었습니다. EncryptedSharedPreferences는 Cipher + SharedPreferences를 통한 수동 암호화의 기존 방식을 대체했습니다. 현재 안정 버전은 1.1.0-alpha06(2024)이며 API 19+를 지원합니다. 이 라이브러리는 Jetpack의 일부이며 추가 권한이 필요하지 않습니다.
Google Security Blog(2024)에 따르면, EncryptedSharedPreferences는 클라우드 동기화가 필요하지 않은 민감한 앱 설정을 저장하는 권장 방법입니다. 더 복잡한 시나리오의 경우 SQLCipher 암호화를 사용하는 Room이 권장됩니다.
EncryptedSharedPreferences는 2단계 암호화 체계를 사용합니다. Master Key는 Android Keystore에 저장되고 파생 키가 데이터 암호화에 사용됩니다. 이를 통해 Keystore 보호와 대칭 암호화의 성능을 결합합니다.
값에는 AES-256 GCM(Galois/Counter Mode)이 사용됩니다. 이는 데이터 기밀성과 무결성을 보장하는 인증 암호화 모드(AEAD)입니다. 키(매개변수 이름)에는 AES-256 SIV(RFC 5297)가 적용되며, 이는 내용을 노출하지 않고 키를 검색하는 데 필요한 결정론적 암호화입니다.
EncryptedSharedPreferences의 각 파일에는 암호화된 키-값 쌍이 포함됩니다. 파일 구조는 먼저 메타데이터(버전, 키 식별자)가 포함된 헤더, 그 다음 암호화된 항목 목록으로 구성됩니다. 파일은 유효한 XML이 아니며 텍스트 편집기로 읽을 수 없습니다.
MasterKey 클래스는 Android Keystore에 저장된 256비트 마스터 키의 생성 및 관리를 담당합니다. MasterKey.Builder를 사용하여 저장소 유형(Keystore 또는 소프트웨어), 생체 인식 보호 및 키 수명을 구성할 수 있습니다. 기본적으로 마스터 키는 Android Keystore에서 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는 다섯 개의 매개변수를 받습니다: 컨텍스트, 파일 이름, 마스터 키, 키 암호화 체계 및 값 암호화 체계. 체계 선택은 성능과 보안 수준에 영향을 미칩니다.
AES256_SIV — 결정론적 암호화: 동일한 키는 항상 동일한 암호문을 생성합니다. 이는 키 검색(SharedPreferences.getX(key))에 필요합니다. 단점: 공격자는 반복되는 암호문을 비교하여 어떤 키가 사용되는지 확인할 수 있습니다. AES256_SIV2 — 추가 무작위화가 포함된 개선된 버전입니다.
값에는 AES256_GCM이 사용됩니다. GCM은 각 값에 12바이트 IV(초기화 벡터)와 16바이트 인증 태그를 추가합니다. 이는 기밀성(아무도 값을 읽을 수 없음)과 인증(아무도 감지되지 않고 값을 변조할 수 없음)을 제공합니다.
MasterKey.Builder의 setUserAuthenticationRequired(true) 메서드는 Keystore에서 마스터 키를 검색하기 전에 생체 인식 확인을 요구합니다. 이는 추가 계층을 추가합니다. 앱이 잠금 해제된 장치에서 실행 중인 경우에도 공격자는 Face ID 또는 Touch ID 없이 EncryptedSharedPreferences를 읽을 수 없습니다.
중요: setUserAuthenticationRequired를 사용하는 경우 사용자가 생체 인식을 변경하거나 제거하면 마스터 키를 사용할 수 없게 됩니다. KeyPermanentlyInvalidatedException을 처리하고 데이터 마이그레이션과 함께 새 마스터 키를 생성해야 합니다.
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) {
// 생체 인식이 변경되었습니다 — 키를 다시 생성해야 합니다
}
}
Kotlin을 사용하여 Android 앱에서 EncryptedSharedPreferences를 통합하는 완전한 예제를 살펴보겠습니다. androidx.security:security-crypto 라이브러리는 Gradle을 통해 추가됩니다.
build.gradle(app) 파일에 추가: implementation “androidx.security:security-crypto:1.1.0-alpha06”. Kotlin 프로젝트의 경우 kotlin-stdlib도 필요합니다. MasterKey 초기화는 일반적으로 Application.onCreate 또는 DI 컨테이너를 통해 한 번 수행됩니다.
EncryptedSharedPreferences 인스턴스를 생성한 후 API는 일반 SharedPreferences와 다르지 않습니다. edit()는 Editor를 반환하며 모든 메서드(putString, getString, putBoolean, getBoolean)는 동일하게 작동합니다. 유일한 차이점은 내부에 있습니다: 데이터는 쓸 때 암호화되고 읽을 때 복호화됩니다.
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()
}
}
암호화되지 않은 SharedPreferences에서 EncryptedSharedPreferences로 기존 데이터를 마이그레이션하려면: 이전 파일에서 모든 데이터를 읽고, 새 EncryptedSharedPreferences를 만들고, 모든 데이터를 쓰고, 이전 파일을 삭제합니다. Google은 내장 마이그레이터를 제공하지 않으므로 개발자가 수동으로 구현합니다.
SharedPreferences와 EncryptedSharedPreferences 중 선택은 저장되는 데이터 유형에 따라 다릅니다. UI 설정(테마, 언어, 정렬)의 경우 일반 SharedPreferences로 충분합니다. 기밀 정보(토큰, 비밀번호, 키)의 경우 EncryptedSharedPreferences가 필수입니다.
EncryptedSharedPreferences는 암호화 작업으로 인해 일반보다 느립니다. 단일 문자열 값 쓰기는 ~5-15ms(데이터 크기 및 AES 하드웨어 가속에 따라 다름)가 소요됩니다. 읽기는 2-5ms입니다. 대부분의 앱에서 이는 눈에 띄지 않지만 배치 작업(마이그레이션, 복원)에서는 commit() 대신 apply()를 사용하세요.
일반 SharedPreferences는 암호화 보호를 제공하지 않습니다. XML 파일은 루트 액세스가 있는 모든 프로세스 또는 adb backup을 통해 읽을 수 있습니다. EncryptedSharedPreferences는 애플리케이션 수준에서 데이터를 암호화하며 마스터 키는 선택적 하드웨어 보호(StrongBox)와 함께 Android Keystore에 저장됩니다.
| 특성 | SharedPreferences | EncryptedSharedPreferences |
|---|---|---|
| 저장 | 일반 XML | 암호화된 바이너리 파일 |
| 암호화 | 없음 | AES-256 GCM + SIV |
| 키 보호 | 없음 | Android Keystore + StrongBox |
| 성능 | 0.1-1ms | 2-15ms |
| 권장 | UI 설정 | 토큰, 키, PII |
EncryptedSharedPreferences를 사용하여 저장: OAuth 리프레시 토큰, 외부 서비스용 API 키, 사용자 이메일 또는 전화번호, 민감한 앱 설정(PIN, 인증 플래그). EncryptedSharedPreferences는 생체 인식 데이터나 대용량 문서 저장에 적합하지 않습니다. 대신 EncryptedFile 또는 SQLCipher를 사용하는 Room을 사용하세요.
일반적인 규칙: 데이터 유출이 사용자나 비즈니스에 해를 끼칠 경우 EncryptedSharedPreferences를 사용합니다. 데이터가 외관상의 것(테마, 언어, 정렬)에 불과한 경우 일반 SharedPreferences를 사용합니다. EncryptedSharedPreferences는 리팩토링 없이 처음부터 구현하는 것이 합리적입니다. 기존 프로젝트에서 교체하려면 마이그레이션과 이전의 암호화되지 않은 데이터 처리가 필요합니다.
EncryptedSharedPreferences는 앱 실행 중에는 데이터를 보호하지 않으며 디스크에서만 보호합니다. 공격자가 프로세스 메모리에 접근할 수 있는 경우 복호화된 데이터가 가로채질 수 있습니다. 추가 보호를 위해 ProGuard/DexGuard를 사용한 난독화를 사용하세요.
자주 묻는 질문
Jetpack DataStore는 Flow와 Kotlin 코루틴을 기반으로 하는 SharedPreferences의 더 현대적인 대안입니다. DataStore는 기본적으로 데이터를 암호화하지 않지만 EncryptedSharedPreferences와 결합하거나 암호화 프로토콜을 사용하여 Proto DataStore를 통한 수동 암호화와 함께 사용할 수 있습니다.
권장되지 않습니다. EncryptedSharedPreferences는 소량의 데이터(최대 100-200KB)를 위해 설계되었습니다. 더 큰 데이터의 경우 동일한 AndroidX Security 라이브러리의 SQLCipher를 사용하는 Room 또는 EncryptedFile을 통한 파일 암호화를 사용하세요.
아니요, 자동 스키마 마이그레이션은 없습니다. 데이터 구조를 변경할 때 개발자는 이전 KeyGen을 통해 이전 데이터를 수동으로 읽고 새 KeyGen을 통해 작성해야 합니다. 스키마 버전은 별도의 매개변수에 저장하는 것이 좋습니다.
AndroidX Security 1.0.0은 API 19+(Android KitKat)를 지원합니다. 버전 1.1.0-alpha06도 API 19+를 지원합니다. StrongBox에는 API 28+ 및 하드웨어 지원이 있는 장치(Google Pixel 3+, Samsung Galaxy S9+)가 필요합니다.
네, 리프레시 토큰은 주요 사용 사례 중 하나입니다. AES-256 GCM 암호화, Keystore의 마스터 키, 생체 인식 보호는 OAuth 토큰에 충분한 수준입니다. 수명이 짧은 액세스 토큰에도 적합하지만 일부 팀은 메모리에 저장하는 것을 선호합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.