본문 바로가기
안드로이드 코틀린

[Kotlin][Android] Keystore를 사용해 암호화하기

by teamnova 2025. 7. 3.
728x90

안녕하세요.

오늘은 안드로이드에서 keystore를 사용해서 안전하게 데이터를 보호하는 방법에 대해 알아보겠습니다.

 

1. keystore

https://developer.android.com/privacy-and-security/keystore?hl=ko

 

Android 키 저장소 시스템  |  Security  |  Android Developers

Android 키 저장소 시스템 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android 키 저장소 시스템을 사용하면 암호화 키를 컨테이너에 저장하여 기기에서 키

developer.android.com

 

Keystore는 안드로이드에서 제공하는 안전한 저장소입니다.

쉽게 말해, 중요한 비밀(예: 암호화 키, 인증서 등)을 스마트폰 내부의 안전한 공간에 보관해주는 금고 역할을 합니다.

 

만약 암호화 키를 그냥 앱 내부에 저장하면, 해커가 앱을 해킹해서 키를 빼낼 수 있습니다. 하지만 Keystore를 사용하면, 키가 외부로 노출되지 않고, 오직 Keystore만이 키를 사용해 암호화/복호화 작업을 할 수 있습니다.

 

키는 OS(운영체제)에서 관리하는 보안 영역에 저장되어, 일반 파일처럼 접근할 수 없습니다.

2. 암호화란?

암호화는 데이터를 다른 사람이 알아볼 수 없게 '암호문'으로 바꾸는 기술입니다.

예를 들어, 사용자의 비밀번호, 신용카드 번호 등 민감한 정보를 앱에 저장할 때 암호화를 사용합니다.

  • 암호화 종류
    • 대칭키 암호화: 하나의 키로 암호화와 복호화를 모두 함. (예: AES)
    • 비대칭키 암호화: 공개키로 암호화, 개인키로 복호화. (예: RSA)

https://developer.android.com/privacy-and-security/cryptography?hl=ko

 

암호화  |  App quality  |  Android Developers

Android의 암호화 기능을 알아보세요.

developer.android.com

 

3. Keystore를 사용한 AES 암호화 예제

이제 실제로 Keystore를 사용해 데이터를 암호화하고, 복호화하는 방법을 단계별로 알아보겠습니다.

Step 1: Keystore에 AES 키 생성하기

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey

// Keystore에 저장할 키의 별칭(이름)
private const val KEY_ALIAS = "my_aes_key"

fun createAESKey() {
    // 이미 키가 존재하면 새로 만들 필요 없음
    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    if (keyStore.containsAlias(KEY_ALIAS)) return

    // 키 생성기 설정
    val keyGenerator = KeyGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"
    )
    val keyGenParameterSpec = KeyGenParameterSpec.Builder(
        KEY_ALIAS,
        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
    )
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM) // GCM 모드 사용
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .setRandomizedEncryptionRequired(true)
        .build()

    keyGenerator.init(keyGenParameterSpec)
    keyGenerator.generateKey() // 키 생성 및 Keystore에 저장
}

 

  • KEY_ALIAS는 키를 찾을 때 사용할 별명입니다.
  • KeyGenerator를 통해 AES 키를 생성하고, Keystore에 저장합니다.
  • 이미 키가 있으면 생성을 건너뜁니다.

Step 2: 암호화 함수 만들기

import javax.crypto.Cipher
import javax.crypto.SecretKey
import javax.crypto.spec.GCMParameterSpec
import android.util.Base64

// 암호화 함수
fun encryptData(plainText: String): Pair<String, String> {
    // Keystore에서 키 가져오기
    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    val secretKey = keyStore.getKey(KEY_ALIAS, null) as SecretKey

    // Cipher(암호화 엔진) 준비
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKey)

    // 암호화
    val iv = cipher.iv // 초기화 벡터
    val encryptedBytes = cipher.doFinal(plainText.toByteArray(Charsets.UTF_8))

    // 결과를 Base64로 인코딩해서 반환 (문자열로 저장하기 위함)
    val encryptedBase64 = Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
    val ivBase64 = Base64.encodeToString(iv, Base64.DEFAULT)

    // 암호문과 IV를 반환 (IV는 복호화에 필요)
    return Pair(encryptedBase64, ivBase64)
}
  • Cipher는 암호화/복호화 엔진입니다.
  • IV(초기화 벡터)는 암호화의 무작위성을 높여줍니다. 복호화 시 필요하므로 저장해야 합니다.
  • 암호화된 데이터와 IV를 Base64로 인코딩하여 반환합니다.

Step 3: 복호화 함수 만들기

fun decryptData(encryptedBase64: String, ivBase64: String): String {
    val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
    val secretKey = keyStore.getKey(KEY_ALIAS, null) as SecretKey

    // Base64로 인코딩된 암호문과 IV를 다시 바이트 배열로 변환
    val encryptedBytes = Base64.decode(encryptedBase64, Base64.DEFAULT)
    val iv = Base64.decode(ivBase64, Base64.DEFAULT)

    // Cipher 준비 (복호화 모드)
    val cipher = Cipher.getInstance("AES/GCM/NoPadding")
    val spec = GCMParameterSpec(128, iv)
    cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)

    // 복호화
    val decryptedBytes = cipher.doFinal(encryptedBytes)
    return String(decryptedBytes, Charsets.UTF_8)
}
  • 암호화 때 사용한 IV와 암호문을 받아 복호화합니다.
  • 복호화된 바이트를 문자열로 변환해 반환합니다.

Step 4: 실제로 사용해보기

// 예시 사용법
createAESKey() // 앱 최초 실행 시 1회만 호출

val plainText = "안녕하세요, Monica!"
val (encrypted, iv) = encryptData(plainText)
println("암호문: $encrypted")
println("IV: $iv")

val decrypted = decryptData(encrypted, iv)
println("복호화 결과: $decrypted")

 

주의사항

  • IV(초기화 벡터)는 암호문과 함께 안전하게 저장해야 복호화가 가능합니다.
  • Keystore에 저장된 키는 앱을 삭제하면 같이 사라집니다