안드로이드 시스템·Compose 용어 사전
보안Keystore · 안드로이드 키스토어

EncryptedSharedPreferences

Android Keystore 키로 암호화된 설정 저장소. 평문 저장의 위험을 없앤다.

Android Keystore의 키로 암호화된 설정 저장소.

왜 평문이 위험한가

  • /data/data/<package>/shared_prefs/auth.xml

  • 루팅된 기기에서는 그대로 읽힌다

  • adb backup 이 허용돼 있으면 통째로 빠져나간다

  • 다른 앱의 취약점을 통해 노출될 수 있다

  • "어차피 샌드박스니 안전하다" 는 루팅 앞에서 무너진다

val key = MasterKey.Builder(ctx)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    ctx, "auth", key,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
prefs.edit().putString("refresh_token", token).apply()

Keystore가 실제로 하는 일

키가 앱 메모리에 노출되지 않는다

  • 하드웨어 보안 모듈(TEE/StrongBox) 안에 있고, 앱은 '이 키로 암호화해 줘' 라고 요청만 한다
  • 앱 데이터를 통째로 복사해도 다른 기기에서 복호화할 수 없다

생체 인증과 묶을 수도 있다

  • setUserAuthenticationRequired(true)
  • 지문을 대야만 키가 열린다

무엇을 저장하나

  • 저장해도 되는 것 — 리프레시 토큰 · 세션 식별자 · 사용자 설정 저장하면 안 되는 것 비밀번호 원문 · 카드 번호 · 서버 비밀 키

"기기에 두지 않는 것" 이 가장 안전한 선택이라는 원칙은 그대로다

API 키를 코드에 넣으면

APK 를 풀면 문자열이 그대로 나온다

  • unzip app.apk && strings classes.dex | grep -i "api"

BuildConfig · local.properties · NDK · 난독화 — 전부 늦출 뿐 막지 못한다

진짜 해법

  • 비밀은 서버에 두고 앱은 자기 토큰으로 서버에 묻는다
  • 꼭 필요하면 서버에서 단기 토큰을 받아 쓴다

백업에서 제외하기

<application android:dataExtractionRules="@xml/data_rules">
<!-- data_rules.xml -->
<data-extraction-rules>
    <cloud-backup><exclude domain="sharedpref" path="auth.xml"/></cloud-backup>
</data-extraction-rules>

면접 함정

  • "암호화했으니 루팅 기기에서도 안전하다" → 앱이 실행 중이면 복호화된 값을 메모리에서 읽힐 수 있다. 위험을 줄일 뿐이다.
  • "난독화가 API 키를 보호한다" → 문자열 상수는 그대로 남는다.

무엇으로 대체되고 있나

androidx.security-crypto 의 EncryptedSharedPreferences 는 1.1.0-alpha 이후 정체돼 있고 deprecated 경로에 있다

대안

  • DataStore + 직접 암호화 (Keystore 키로 값만 암호화)
  • 아예 저장하지 않는다 (서버에서 짧은 수명 토큰을 매번 받는다)
  • 생체 인증으로 감싼 Keystore 키

면접에서는 "무엇을 쓰는가" 보다 "왜 평문이 위험하고 Keystore 가 무엇을 보장하는가" 가 중요하다

Keystore로 직접 암호화

val keyGen = KeyGenerator.getInstance("AES", "AndroidKeyStore")
keyGen.init(KeyGenParameterSpec.Builder("my_key",
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
    .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
    .setUserAuthenticationRequired(true)      // 지문을 대야 열린다
    .build())
keyGen.generateKey()

GCM 은 IV 를 매번 새로 만들어 함께 저장해야 한다 (비밀이 아니다) 같은 IV 를 재사용하면 GCM 의 안전성이 무너진다 — 흔한 구현 실수

루팅 탐지의 한계

SafetyNet 은 종료됐고 Play Integrity API 가 후속이다

  • 기기 무결성 · 앱 무결성 · 라이선스를 서버에서 검증한다

중요한 것은 '서버가 판단한다' 는 점이다

  • 앱 안에서 루팅을 탐지해 분기하면 그 분기를 패치로 지울 수 있다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 권한·보안 — 런타임 권한·저장소·암호화·네트워크