Android

[Android] Encryption & Decryption in Android

베르_최성훈 2024. 1. 10. 16:47

들어가기 전에

 

안드로이드에서 DataStore 를 사용할 때 암호화, 복호화를 어떻게 처리해야할지 고민이라 찾다가

 

이 영상을 먼저 보는게 좋다고 하길래 정리해봅니다.

 

FULL Guide to Encryption & Decryption in Android (Keystore, Ciphers and more)

https://www.youtube.com/watch?v=aaSck7jBDbw

 

 

Encryption & Decryption with KeyStore

예를 들어서 비밀번호를 매번 기억해서 입력하기 힘드니깐 자동 저장한다고 생각해봅시다.

그럼 핸드폰에 접근해서 누군가 비밀번호를 볼 수도 있겠죠..?

 

그 경우를 방지해서 중요한 정보를 저장할 때 Key 를 사용해 저장합니다. 데이터를 읽거나 쓸 때 Key 가 있어야한다는 말이죠.

그런데...이 Key 만 탈취한다면 다른 사람도 비밀번호를 자유롭게 읽거나 쓸 수 있습니다. 그럼 이 Key 를 관리하는게 중요하겠죠?

 

그렇다면 이 Key 는 어디에 저장할까요..?

 

 

 

일반적으로 생각할 때 앱 저장소를 먼저 떠올리실겁니다. 다른 앱에서는 접근 불가능하고 해당 앱에서만 접근 가능한 저장소 말이죠. 

 

그러나 공격자가 Android 기기의 rooted access 를 가진다면 모든 저장소, 예를 들어 Shared Preferences 같은 곳에 접근할 수 있으며 모든 일을 할 수 있습니다. 즉, key 를 탈취하는 것도 가능합니다.

 

이 문제의 해결 방법으로 KeyStore 시스템이 있습니다.

KeyStore 시스템은 공격자가 key 들을 Android 기기에서 빼내는 것을 방지합니다.

 

 

 

그러나 여기서 이 말은 공격자가 내 key 를 가지고 keyStore 시스템을 사용하지 못한다는 말은 아니에요.

 

만약 그들이 OS 에 대한 root(or basically) access 권한을 가지고 있다면 막을 수 없습니다.

그러나 그들은 key 를 읽거나 기기로부터 탈취해서 다른 일을 할 수 없습니다.

 

 

 

이게 어떻게 가능할까요?

 

그것은 specific hardware 인 TEE (Trusted excution environment) 덕분입니다. 

이는 안드로이드 OS 가 아닌 separate 된 hardware part 인데 Android OS 의 root access 를 가져도 keyStore 를 관리하는 root access 는 가질 수 없기 때문이입니다.

 

앱이 소유한 키로 암호화하거나 암호를 해독하도록 KeyStore 시스템에 요청할 수 있어요.

 

 

 

요약하자면 KeyStore 는 공격자가 우리의 key 를 사용하지 못하게 막는 것은 아닙니다. 그 목적은 공격자가 Android 시스템에서 우리의 key 를 빼내는 것을 막기 위함이죠. 결국 그들은 우리의 Android 기기 밖에서 key를 사용할 수 없습니다.

 

 

 

그럼 이제 KeyStore 를 사용해봅시다.

 

KeyStore 사용

 

keyStore 는 다음과 같이 type 전달하고 생성할 수 있습니다.

 

객체 생성을 완료 했으면 암호화, 복호화에 필요한 암호화 방법 즉 Crypto Algorithm 을 정해야하죠.

 

 

동반 객체에 위와 같이 암호화 방식을 정의했습니다. 이 방법이 각각 무엇인지는 따로 설명하지 않겠습니다.

 

그런다음 Key 를 만들어 주겠습니다.

 

 

key 가 있으면 가져오고 없으면 생성합니다. 암호화 방식은 동반 객체에서 정의한대로 사용하고 사용자 인증(지문 등 생체 인식으로 예상)은 false 로 주고 랜덤 암호화는 true 로 설정했습니다.

 

그런 다음 encrypt, decrypt 두 가지 function 을 정의합니다.

 

이때 getDectyptCipherForIv 라는 함수가 보일텐데 inputStream 이라는 ByteArray 를 읽어서 이 함수에 전달하면 키를 사용해 decrypt 할 수 있습니다.

 

 

 

간단한 화면을 붙여 잘 작동하는지 확인해보겠습니다.

 

화면은 컴포즈로 작성하였고 다음과 같습니다.

@Composable
    fun Something(cryptoManager: CryptoManager) {
        var messageToEncrypt by remember {
            mutableStateOf("")
        }
        var messageToDecrypt by remember {
            mutableStateOf("")
        }
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(32.dp),
        ) {
            TextField(
                value = messageToEncrypt,
                onValueChange = { messageToEncrypt = it },
                modifier = Modifier.fillMaxWidth(),
                placeholder = { Text(text = "Encrypt string") },
            )
            Spacer(modifier = Modifier.height(8.dp))
            Row {
                Button(onClick = {
                    val bytes = messageToEncrypt.encodeToByteArray()
                    val file = File(filesDir, "secret.txt")
                    if (!file.exists()) {
                        file.createNewFile()
                    }
                    val fos = FileOutputStream(file)

                    messageToDecrypt = cryptoManager.encrypt(
                        bytes = bytes,
                        outputStream = fos,
                    ).decodeToString()
                }) {
                    Text(text = "Encrypt")
                }
                Spacer(modifier = Modifier.height(16.dp))
                Button(onClick = {
                    val file = File(filesDir, "secret.txt")
                    messageToEncrypt = cryptoManager.decrypt(
                        inputStream = FileInputStream(file),
                    ).decodeToString()
                }) {
                    Text(text = "Decrypt")
                }
            }
            Text(text = messageToDecrypt)
        }
    }
}

 

 

실행 시 다음과 같이 TextField 에 입력해서 암호화할 수 있어요.

 

 

 

문자열을 입력하고 Encrypt 버튼을 클릭하면 암호화된 문자열이 나타납니다.

 

 

우측 하단의 device explorer 를 열고 [/data/data/프로젝트 패키지 이름/files] 경로를 따라 들어가면 본래 존재하지 않았던 secret.txt 가 생성된 것을 확인할 수 있어요!

 

 

들어가면 암호화되어 저장된 것을 확인할 수 있죠.

 

그런 다음 앱을 종료하고 다시 실행합니다.

 

그 다음 Decrypt 버튼을 클릭하면...

 

똑같은 문자열이 복호화되어 나타나는 것을 확인할 수 있습니다!

 

 

마무리하며

오늘은 CryptoManager 를 만들어 문자열을 암호화하고 ByteArray 로 파일에 저장했다가 복호화해서 다시 불러오는 일련의 과정을 공부해봤습니다.

 

다음에는 DataStore 를 사용해 저장할때는 어떻게 암호화하고 복호화하는지 알아보도록 하겠습니다!