들어가기 전에
안드로이드에서 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 를 사용해 저장할때는 어떻게 암호화하고 복호화하는지 알아보도록 하겠습니다!
'Android' 카테고리의 다른 글
[페스타고] 안드로이드에서 중첩된 페이지는 어떻게 개발해야 할까 (0) | 2024.06.17 |
---|---|
페스타고 상세 페이지 이동 애니메이션 변경 및 UX 개선 (0) | 2024.05.12 |
[안드로이드] DataStore 이해하기 (0) | 2024.01.08 |
RecyclerView 목록 스크롤에 CoordinatorLayout 적용하기 (0) | 2023.11.17 |
[안드로이드] Repository Pattern (저장소 패턴) (0) | 2023.10.26 |