안드로이드 자바
[Java][Android] SharedPreferences 데이터 암호화
teamnova
2025. 5. 26. 14:26
728x90
SharedPreferences의 보안 취약점
기본 SharedPreferences는 데이터를 일반 텍스트(Plain Text) 형태로 XML 파일에 저장합니다. 이 파일은 앱의 내부 저장소 (/data/data/<패키지명>/shared_prefs/)에 위치하며, 루팅되지 않은 일반 기기에서는 다른 앱이나 사용자가 직접 접근하기 어렵습니다.
주요 특징:
자동 암호화/복호화: 데이터를 저장할 때 자동으로 암호화하고, 읽을 때 자동으로 복호화합니다. 개발자는 암호화 과정을 신경 쓸
필요 없이 기존 SharedPreferences처럼 사용하면 됩니다.
암호화 알고리즘: AES-256 GCM과 같은 강력한 암호화 표준을 사용합니다.
마스터 키 관리: 암호화에 사용되는 마스터 키를 Android Keystore 시스템을 통해 안전하게 생성하고 관리합니다. Android Keystore는 하드웨어 지원 보안 기능을 활용하여 키를 기기 내에 안전하게 보관합니다.
예제코드
dependencies {
implementation libs.security.crypto // 최신 안정 버전 확인 권장
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<EditText
android:id="@+id/editTextUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="사용자 이름 입력"
android:inputType="textPersonName"
android:layout_marginBottom="16dp"/>
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/switchDarkMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="다크 모드 활성화"
android:textSize="16sp"
android:layout_marginBottom="24dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="24dp">
<Button
android:id="@+id/buttonSave"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="저장하기"
android:layout_marginEnd="8dp"/>
<Button
android:id="@+id/buttonLoad"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="불러오기"/>
</LinearLayout>
<TextView
android:id="@+id/textViewLoadedData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="불러온 데이터가 여기에 표시됩니다."
android:textSize="16sp"
android:padding="8dp"
android:background="#f0f0f0"/>
</LinearLayout>
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SwitchCompat;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;
import java.io.IOException;
import java.security.GeneralSecurityException;
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey; // MasterKey.Builder를 사용
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final String KEY_USERNAME = "username";
private static final String KEY_DARK_MODE = "dark_mode_enabled";
EditText editTextUsername;
SwitchCompat switchDarkMode; // <--- 타입을 SwitchCompat으로 변경!
Button buttonSave, buttonLoad;
TextView textViewLoadedData;
SharedPreferences encryptedPrefs;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 레이아웃 파일 설정 필요
editTextUsername = findViewById(R.id.editTextUsername);
switchDarkMode = findViewById(R.id.switchDarkMode);
buttonSave = findViewById(R.id.buttonSave);
buttonLoad = findViewById(R.id.buttonLoad);
textViewLoadedData = findViewById(R.id.textViewLoadedData);
// EncryptedSharedPreferences 초기화
try {
encryptedPrefs = getEncryptedSharedPreferences();
} catch (RuntimeException e) {
// 오류 발생 시 사용자에게 알리거나 대체 로직 수행
Toast.makeText(this, "보안 저장소 초기화 실패!", Toast.LENGTH_LONG).show();
Log.e(TAG, "EncryptedSharedPreferences 초기화에 실패했습니다.", e);
// 일반 SharedPreferences로 대체할 수도 있습니다.
// encryptedPrefs = getSharedPreferences("app_prefs_fallback", MODE_PRIVATE);
finish(); // 또는 앱 종료
return;
}
buttonSave.setOnClickListener(v -> saveData());
buttonLoad.setOnClickListener(v -> loadData());
// 앱 시작 시 저장된 데이터 자동 로드 (선택 사항)
loadData();
}
private SharedPreferences getEncryptedSharedPreferences() {
try {
// 1. MasterKey.Builder를 사용하여 마스터 키 생성
MasterKey mainKey = new MasterKey.Builder(getApplicationContext())
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM) // 권장되는 키 스킴
.build();
// 2. EncryptedSharedPreferences 인스턴스 생성
return EncryptedSharedPreferences.create(
getApplicationContext(),
"secure_app_prefs_v2", // 파일 이름
mainKey, // 생성된 MasterKey 객체
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, // 키 암호화 방식
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM // 값 암호화 방식
);
} catch (GeneralSecurityException | IOException e) {
Log.e("EncryptedPrefs", "EncryptedSharedPreferences를 생성하지 못했습니다.", e);
throw new RuntimeException("Failed to create EncryptedSharedPreferences", e);
}
}
private void saveData() {
if (encryptedPrefs == null) return;
String username = editTextUsername.getText().toString();
boolean darkModeEnabled = switchDarkMode.isChecked();
SharedPreferences.Editor editor = encryptedPrefs.edit();
editor.putString(KEY_USERNAME, username);
editor.putBoolean(KEY_DARK_MODE, darkModeEnabled);
editor.apply(); // 비동기 저장 (또는 editor.commit()으로 동기 저장)
Toast.makeText(this, "데이터가 안전하게 저장되었습니다.", Toast.LENGTH_SHORT).show();
Log.d(TAG, "저장된 데이터: 사용자 이름=" + username + ", 다크모드=" + darkModeEnabled);
}
private void loadData() {
if (encryptedPrefs == null) return;
String username = encryptedPrefs.getString(KEY_USERNAME, "사용자 이름 없음");
boolean darkModeEnabled = encryptedPrefs.getBoolean(KEY_DARK_MODE, false);
editTextUsername.setText(username); // 불러온 데이터로 UI 업데이트 (선택적)
switchDarkMode.setChecked(darkModeEnabled);
String loadedText = "불러온 데이터:사용자 이름: " + username + "다크 모드: " + (darkModeEnabled ? "활성화됨" : "비활성화됨");
textViewLoadedData.setText(loadedText);
Log.d(TAG, "불러온 데이터:사용자 이름:" + username + ", 다크 모드=" + darkModeEnabled);
}
}
시연영상