티스토리 뷰
앱 개발 시에 고도화된 암복호화 방식을 사용해보기 위해,
기존 SharedPreferences를 사용하던 부분을
MasterKey 기반 보안 저장소인 EncryptedSharedPreferences로 교체했다.
그 과정에서 새롭게 알게된 부분과 겪은 이슈를 정리해보려고 한다.
MasterKey
- AndroidX Security 라이브러리에서 EncryptedSharedPreferences 또는 EncryptedFile 사용 시 내부적으로 관리되는 AES 암호화용 루트 키
- 앱 내 암복호화 시 공통적으로 사용되는 키를 안전하게 관리하는 객체
- 실제 키는 Android Keystore(System-level secure storage)에 저장됨
- 앱의 인증서/서명에 따라 관리되며, 앱 재설치 시 다른 키로 재생성됨
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
EncryptedSharedPreferences
- 내부적으로 MasterKey를 사용해 AES 암호화를 적용하는 SharedPreferences
- 키(Key)는 AES256-SIV (deterministic encryption)
- 값(Value)은 AES256-GCM (authenticated encryption)
- 앱 재설치 시, MasterKey 교체로 인해 이전 데이터 복호화 불가
val sharedPrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
문제 상황
- MasterKey는 앱의 서명 인증서(Signing Certificate)를 기반으로 생성, 관리된다.
때문에 앱 서명이 달라지거나, 앱이 재설치되어 Keysotre 영역이 초기화되면 새로운 key가 생성된다. - EncryptedSharedPreferences는 기본적으로 앱 삭제 시 함께 초기화된다.
그러나 AndroidManifest에 allowBackup=true 로 설정되어 있으면, SharedPreferences 파일이 자동 복원될 수 있다.
이 경우 복호화에 필요한 MasterKey는 복원되지 않기 때문에, 복원된 데이터는 복호화 오류를 일으킨다.
내 경우, Room DB에 데이터를 암호화하여 저장할 때 사용한 secretKey를 EncryptedSharedPreferences에 저장했다.
이 때 앱에서 allowBackup을 true로 설정해놓은 것을 놓쳐, 복원된 EncryptedSharedPreferences이 복호화가 불가능한 문제가 발생했다. 고려할 수 있는 해결 방안은 아래와 같았다.
해결 방법
방안 1. auto backup 허용하되, 특정 SharedPreferences만 제외
allowBackup="true" 설정 자체를 없애는 대신, 아래와 같이 backup_rules.xml 파일을 정의하여 문제가 되는 EncryptedSharedPreferences 파일을 백업에서 제외할 수 있다.
// AndroidManifest.xml
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules" />
// res/xml/backup_rules.xml
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<!-- 기본적으로 모든 데이터를 백업 -->
<include domain="sharedpref" path="." />
<!-- 제외 대상 -->
<exclude domain="sharedpref" path="secure_prefs.xml" />
</full-backup-content>
직접 manifest 파일을 제어해 allowBackup 설정 자체를 건드릴 수 없어 고려한 방식이지만, 이 방법 역시 manifest에 해당 파일을 선언해줘야했다.
방안 2. 복호화 실패 시 복구 로직 추가
기존 코드에서 문제가 되어 Exception이 발생할 때, secretKey를 저장해놓은 EncryptedSharedPreferences 파일 자체를 삭제하는 방법도 있다. runtime에서 처리할 수 있다는 장점이 있어 방어코드로 처리할만한 로직이다.
try {
val prefs = EncryptedSharedPreferences.create(...)
prefs.getString("sdk_secret_key", null)
} catch (e: BadPaddingException) {
context.deleteSharedPreferences("secure_prefs")
}
✅ 방안 3. MasterKey 의존 제거
최종적으로는, EncryptedSharedPreferences를 제거하고 직접 AES 암복호화를 적용했다.
secretKey는 일반 SharedPreferences에 Base64 형태로 저장했다.
보안성은 MasterKey 적용방식보다는 낮지만, 현재 문제가 된 기능은 안정성을 최우선으로 하기 때문에 앱 업데이트 또는 재설치에 안전하게 작동하는 것이 더 중요해 이 방법을 채택했다.
운영 안정성, 보안 수준, 코드 복잡도 등을 모두 신경쓰며
적정수준을 충족하는 타협점을 찾는 것에 많은 고민을 하게 된 경험이였다.
이번 기능은 안정성이 최우선이기 때문에 오히려 과도한 로직을 덜어내고 코드를 간소화해 방식을 채택했지만,
민감정보가 포함되어 보안성이 중요시 되는 기능에서 정보 탈취등의 문제로 이어질 수도 있으니
앱에서 보안을 챙기기 위한 방안에 대해서도 더 해봐야겠다.
'Android' 카테고리의 다른 글
| [Jetpack] App Startup (2) | 2025.07.27 |
|---|---|
| [Android & Kotlin] Intent로 Data Class 타입 객체 넘기기 (1) | 2020.08.21 |
| [Android] 안드로이드 스레드 (Android Thread) (0) | 2020.06.24 |
| [Android] 안드로이드 Activity와 Fragment의 생명주기 (0) | 2020.06.23 |
| [Android] 어플리케이션 기본 항목 - 4대 컴포넌트, App Manifest (0) | 2020.06.23 |