📱 Android
[Android] SharedPreferences 대신 DataStore #2
콩드로이드
2025. 12. 25. 20:59
DataStore는 암호화를 자동으로 제공 X
DataStore를 사용할 때 가장 주의해야 할 부분인 거 같습니다
- 토큰
- 사용자 식별 정보
- 개인정보
만약 위와 같은 값들을 그대로 저장하면 루팅된 기기나 백업 파일을 통해 노출될 가능성이 있습니다,,
그래서 암호화 작업이 필요해요
EncryptedSharedPreferences Deprecated
원래는 EncryptedSharedPreference가 공식 대안이었지만, 현재는 Deprecated 되었습니다
대신 Jetpack Security (Crypto API)와 DataStore의 조합이 권장되었습니다!
Crypto API를 사용해서 DataStore를 암호화해서 사용하는 방법에 대해 알아보겠습니다
Crypto API
코드는 단순 예시라서 사용 플로우만 확인하면 좋을 거 같습니다..!
라이브러리 추가
implementation("androidx.security:security-crypto:1.1.0-alpha06")
Master Key 생성
MasterKey는 암호화/복호화에 직접 사용되는 키라기보다는, Android Keystore에 저장되는 최상위 키 역할입니다
즉, MasterKey를 통해 Android Keystore에 위임되기 때문에 키가 코드나 저장소에 노출되는 것을 방지할 수 있습니다
private fun getMasterKey(context: Context): MasterKey {
return MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
}
암호화/복호화
암호화
fun encrypt(
context: Context,
plainText: String
): String {
val masterKey = getMasterKey(context)
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
//MasterKey가 관리하는 실제 SecretKey 가져오기,,
val secretKey = keyStore.getKey(masterKey.keyAlias, null) as SecretKey
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv = cipher.iv
val encryptedBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))
val combined = ByteArray(iv.size + encryptedBytes.size)
System.arraycopy(iv, 0, combined, 0, iv.size)
System.arraycopy(encryptedBytes, 0, combined, iv.size, encryptedBytes.size)
return Base64.encodeToString(combined, Base64.DEFAULT)
}
복호화
fun decrypt(
context: Context,
encryptedText: String
): String {
val masterKey = getMasterKey(context)
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
load(null)
}
val secretKey = keyStore.getKey(masterKey.keyAlias, null) as SecretKey
val combined = Base64.decode(encryptedText, Base64.DEFAULT)
val iv = combined.copyOfRange(0, 12)
val encryptedBytes = combined.copyOfRange(12, combined.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
val decryptedBytes = cipher.doFinal(encryptedBytes)
return String(decryptedBytes, Charsets.UTF_8)
}
DataStore에 암호화 적용하기
저장
suspend fun saveEncryptedToken(
context: Context,
token: String
) {
val encrypted = encrypt(context, token)
context.dataStore.edit { prefs ->
prefs[stringPreferencesKey("저장할 때 쓸 키 값")] = encrypted
}
}
불러오기
fun getDecryptedToken(
context: Context
): Flow<String?> {
return context.dataStore.data.map { prefs ->
prefs[stringPreferencesKey("저장할 때 썼던 키 값")]
?.let { decrypt(context, it) }
}
}
암호화를 사용해서 어떻게 저장하는지 간단한 예시로 알아봤어요 ..!
DataStore에 저장하고 불러오는 모든 값을 암호화할 필요는 없지만, 아래와 같은 노출되면 안 되는 정보들은 암호화가 필수입니다
- Access Token / Refresh Token
- 사용자 식별 정보
- 이메일, 전화번호 등 개인정보