Realm Database 암호화하여 안전하게 관리하는 방법
개요
Realm Database에 암호화를 적용하여 모바일 앱에 저장된 데이터를 안전하게 관리하는 기능입니다.
이 글은 데이터베이스 암호화 키를 생성하고, 암호화 키를 암호화하여 관리하는 방법을 설명합니다.
Realm은 내부적으로 AES-256+SHA-2 방식으로 디스크의 Realm 파일을 암호화하고 초기화 시 64-byte 키를 적용해야 합니다.
암호화 키의 처음 256 bits는 AES-256으로 데이터를 암복호화하고, 나머지 256 bits는 무결성(HMAC)을 검증하는 데 사용됩니다.
Encrypt a Local Realm
암호화를 위한 64-bytes 키를 생성하여 Realm configuration에 지정하고 Realm을 실행하는 예제 코드입니다.
fun generateRealmKey(): ByteArray {
val dbKey = ByteArray(64)
val secureRandom = SecureRandom()
secureRandom.nextBytes(dbKey)
return dbKey
}
fun openRealm(encryptionKey: ByteArray) {
// Create the configuration
val configuration = RealmConfiguration.Builder(setOf(UserRealm::class)).apply {
deleteRealmIfMigrationNeeded()
// Specify the encryption key
encryptionKey(encryptionKey)
}.build()
// Open the realm with the configuration
val realm = Realm.open(configuration)
}
val encryptionKey = generateRealmKey()
openRealm(encryptionKey)
암호화가 적용된 Realm은 실행 시마다 해당 키가 필요하고 잘못된 키를 적용하는 경우 오류를 반환합니다.
Store & Reuse Realm Key
Realm 데이터베이스 암호화 시 사용한 키(이하 Realm Key)를 단말에서 안전하게 관리하는 방법을 설명합니다.
Android KeyStore에서 대칭키를 생성하여 Realm Key를 암호화하고, 암호화된 Realm Key를 filesystem에 저장합니다.
최초 실행 시
1.Realm Key 생성
2.Android KeyStore에서 암호화에 사용할 대칭키(AES/GCM/NoPadding)를 생성
class EncryptionRealm {
companion object {
private const val AES_MODE_M = "AES/GCM/NoPadding"
private const val KEY_ALIAS = "realm_key"
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
}
private var keyStore: KeyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
init {
keyStore.load(null)
}
private fun generateEncryptKey() {
try {
if (!keyStore.containsAlias(KEY_ALIAS)) {
val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)
val keyGenParameterSpecBuilder = KeyGenParameterSpec.Builder(
KEY_ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
val keyGenParameter = keyGenParameterSpecBuilder
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build()
keyGenerator.init(keyGenParameter)
keyGenerator.generateKey()
}
} catch (e: KeyStoreException) {
e.printStackTrace()
}
}
}
3.Realm Key 암호화
private fun encryptKey(input: ByteArray): Pair<String, String> {
return try {
val aesKey = keyStore.getKey(KEY_ALIAS, null) as SecretKey
val cipher: Cipher = Cipher.getInstance(AES_MODE_M)
cipher.init(Cipher.ENCRYPT_MODE, aesKey)
val encodedBytes: ByteArray = cipher.doFinal(input)
val encryptedRealmKey = Base64.encodeToString(encodedBytes, Base64.DEFAULT)
val iv = Base64.encodeToString(cipher.iv, Base64.DEFAULT)
Pair(iv, encryptedRealmKey)
} catch (e: Exception) {
e.printStackTrace()
Pair("", "")
}
}
4.암호화된 Realm Key를 filesystem에 저장
키 저장을 위한 filesystem으로 DataStore 또는 SharedPreferences를 사용하면 됩니다.
암호화된 Realm Key와 InitialVector를 잘 관리하여 추후 Realm 실행 시 사용합니다.
5.Open Realm with Realm Key
앱 실행 시
1.저장된 Realm Key 호출
filesystem에 저장된 Realm Key와 InitialVector를 불러옵니다.
2.Realm Key 복호화
Android KeyStore에서 기존에 생성한 대칭키를 불러와 암호화된 Realm Key를 복호화합니다.
private fun decryptKey(iv: String, encrypted: String): ByteArray {
return try {
val decodedValue = Base64.decode(encrypted.toByteArray(Charsets.UTF_8), Base64.DEFAULT)
val aesKey = keyStore.getKey(KEY_ALIAS, null) as SecretKey
val cipher: Cipher = Cipher.getInstance(AES_MODE_M)
val parameterSpec = GCMParameterSpec(128, Base64.decode(iv, Base64.DEFAULT))
cipher.init(Cipher.DECRYPT_MODE, aesKey, parameterSpec)
val decryptedVal: ByteArray = cipher.doFinal(decodedValue)
decryptedVal
} catch (e: Exception) {
e.printStackTrace()
byteArrayOf()
}
}
3.Open Realm with Realm Key
마무리
Realm 데이터베이스를 암호화하는 방법과, 암호화에 사용한 키를 평문으로 관리하지 않고 Android KeyStore에서 생성한 대칭키를 통해 암호화하여 관리하는 방법을 다뤄보았습니다.
결국 사용자의 데이터를 보호하고 암호화에 사용한 키를 안전하게 관리하는 것이 중요하다고 생각합니다.
해당 작업은 개인 프로젝트에 구현하였고, 관련 코드는 아래에서 확인할 수 있습니다.
- create and manage realm key with AndroidKeyStore. EncryptionRealm.kt
- initialize and open Realm Instance. BaseRealm.kt
- manage(save, read) initial vector and encrypted realm key. RealmDataStore.kt