EncryptedSharedPreferences로 데이터 암호화하기

이번 포스팅에서는 SharedPreferences의 암호화에 대해 알아보도록 하겠습니다.

SharedPreferences의 저장방식

SharedPreferences에 대해서는 SharedPreferences로 앱 설정값 저장하고 불러오기에서 설명했던 적이 있습니다. 소규모의 데이터를 앱 안에 XML 파일로 간편하게 저장할 수 있는 저장소인데요, XML 파일은 앱 안의 다음 경로에 만들어지게 됩니다.

1
/data/data/{package_name}/shared_prefs/{filename}.xml

Android Studio의 Device File Explorer를 사용하면 지정한 위치에 파일이 생성되어 있는것을 알 수 있고 열어서 내용을 볼 수도 있습니다. 지난 강의에서 만들었던 xml의 내용은 다음과 같이 되어 있네요. 키와 값이 앱에서 지정한대로 기입되어있는 것을 확인할 수 있습니다.

1
2
3
4
5
6
7
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <int name="graphic_quality" value="2131231054" />
    <int name="music_volume" value="42" />
    <boolean name="vertical_sync" value="true" />
    <int name="sfx_volume" value="17" />
</map>

Security Library

하지만 앱의 데이터를 이렇게 쉽게 열어볼 수 있게 하는건 좋은 생각이 아닌 것 같습니다. 그래서 구글에서는 SharedPreferences를 암호화 하여 사용할 수 있게 하는 Security 라이브러리를 제공하고 있습니다.

Security는 Android 6.0 (API level 23) 이상부터 사용 가능한데, 이 라이브러리를 사용하면 암호화에 사용할 마스터키를 만들고 그 키로 SharedPreferences의 내용을 암호화/복호화 할 수 있습니다.

데이터를 암호화하는데에는 키가 필요한데요, 이 키를 앱 내부에 저장하면 어떤 형태로 숨겨도 결국은 유출이 가능하다고 생각하시면 됩니다. 그래서 Security 라이브러리는 Android keystore system을 사용해 키를 앱 내부가 아닌, 시스템만이 접근 가능한 컨테이너에 저장하도록 하였습니다.

Android keystore system을 이용해 마스터키를 만들었으면 EncryptedSharedPreferences를 써서 SharedPreferences를 암호화/복호화할 수 있습니다.

앱에 적용해보기

우선 Security 라이브러리를 추가합니다.

1
implementation "androidx.security:security-crypto-ktx:1.1.0-alpha03"

다음은 읽기/쓰기에 사용하던 sharedPreferences 객체를 SharedPreferences가 아닌 EncryptedSharedPreferences 로부터 작성하면 됩니다. 기존의 인스턴스는 주석처리하고 다음과 같은 인스턴스를 만들어줍니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
private val sharedPreferences: SharedPreferences by lazy {

    val masterKeyAlias = MasterKey
        .Builder(applicationContext, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
        .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
        .build()


    EncryptedSharedPreferences.create(
        applicationContext,
        FILE_NAME,
        masterKeyAlias,
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )
}

companion object {
    ...
    private const val FILE_NAME = "encrypted_settings"
}

우선은 MasterKey 클래스로 암호화에 사용할 마스터키를 생성합니다. 키를 작성하는 알고리즘은 커스텀하여 사용할 수도 있게 되어있지만 여기서는 이미 잘 정의되어있는 AES-256 GCM 알고리즘을 사용하도록 하겠습니다.

다음은 EncryptedSharedPreferences를 이용해 읽고쓰기에 사용할 sharedPreferences 인스턴스를 만들어줍니다. 초기화를 할 때에는 위에서 만든 마스터키와 xml 파일에 적용할 파일 이름, 그리고 데이터를 암호화할 방식을 전달해주면 됩니다. 여기서는 Key의 암호화에 AES256_SIV, 그리고 Value의 암호화에는 AES256_GCM을 사용하도록 했습니다.

그리고 나머지는 SharedPreferences와 동일하게 사용하면 됩니다. 설정을 다시 저장한 뒤 encrypted_settings.xml의 내용을 확인해보면 다음과 같이 암호화가 되어 있는 것을 알 수 있습니다.

1
2
3
4
5
6
7
8
9
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">12a901d15f8baa24b9e88a9e68f3eb32ad1136cb4af22776b91caea9459bdd5a76d6992b57e268408944152c70171556e4fa065b13160a6951b75a32dc8172d6ee9cbf8b9828f7cc80d68c23794340221ad89ac94306ec5848af9d9c6e6372fdfd42d745be4b4a6d532adc365491e249e92503b64743defab475808f196b90842cabb874cce6a934d0f646f9d9f285814ded11f3d081a72abeb09d6a1849b7768b9e9899601c59797868b0a71a4408beb9cea707123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e4165735369764b6579100118beb9cea7072001</string>
    <string name="AXTznL5begfyWVUl+GcGajk7xoVmW0h2DCtZudxTAghw8BjV">ARdDw2jfHocuzA3PXT73sMwCNATF/f+ZNWWcG6mgY0mhuMvLz9hGLys=</string>
    <string name="AXTznL6Ev8dJ1E4Ws4dwJfyeDUQgK077hhUwLNnCt+3opw==">ARdDw2g859mf9i8UgWzWHvbZbBQytmCdcaXBVQgfy6+Qq/eIAs4=</string>
    <string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">128801aa5d8f25163f993b8f95a2a874a1353f227ffca6f2e5ee1dad9243b8b3a1ea16531bfa8a10da864655c9b33e865bf072eafcdad659912a5198bf32218f186c02ff3efff27338a50617f194c3fd8b06ebfad829c3a53dbfd8b221fa7f9103ad8e8ad1583250b5c1779b20b618e46a3edb0b50d3a3bdc8c7ace84c21a69f36d4996f9e7fe81e8bf01b1a4408e8868fba01123c0a30747970652e676f6f676c65617069732e636f6d2f676f6f676c652e63727970746f2e74696e6b2e41657347636d4b6579100118e8868fba012001</string>
    <string name="AXTznL4uW8pTacv6rnmZqMoGZHNWTJqwuD8q0XFlWnvw">ARdDw2gTVJYJ3PCEaYgwoZjKMCEAFAv7aLRACnmcKGyjRaHYgeBJZJE=</string>
    <string name="AXTznL5AE+UHfloGh5ZBZ+3iZL2hplrdIffsC3I/qA==">ARdDw2gRmqy+haYjIRDGML/ai32T5alKEQKaqlJDRhfQHcUIzthVAQo=</string>
</map>

이렇게 해서 SharedPreference를 암호화하여 사용하는 법에 대해 알아보았습니다.

Built with Hugo
Theme Stack designed by Jimmy