Android Keystore — 암호화 프로바이더로, 암호화 키를 분리 실행 환경(TEE)에서 생성 및 저장하며 운영 체제도 접근할 수 없습니다. AOSP Security Documentation (2025)에 따르면, Google Play 상위 100개 Android 앱의 80% 이상이 토큰 보호 및 데이터 암호화에 Keystore를 사용합니다. Android Keystore를 이해하는 것은 Android에서 안전한 키 저장을 위해 매우 중요합니다.
핵심 사항
Android Keystore — Android 플랫폼의 시스템 구성 요소로, 보호된 환경에서 암호화 키를 생성, 저장 및 사용하기 위한 API를 제공합니다. 소프트웨어 암호화 라이브러리(Bouncy Castle, Conscrypt)와 달리, Keystore는 개인 키가 절대 격리 실행 영역을 벗어나지 않음을 보장합니다.
Keystore는 Android 4.3(API 18)에서 RSA를 지원하는 소프트웨어 프로바이더로 등장했습니다. Android 6.0(API 23)부터 Keystore는 Keymaster Hardware Abstraction Layer(HAL)를 통해 하드웨어 지원을 받아, 호환 장치에서 암호화 작업을 Trusted Execution Environment(TEE)에 위임합니다. Android Compatibility Definition Document(2025)에 따르면, Android 9+의 모든 장치는 TEE 또는 StrongBox를 통한 하드웨어 Keystore 지원이 필수입니다.
Keystore의 키는 별칭(alias)으로 식별됩니다 — 키 생성 또는 로드 시 전달되는 문자열입니다. Keystore는 키의 원시 자료를 얻는 것을 허용하지 않습니다: Keystore에서 생성된 키의 경우 getEncoded() 메서드가 null을 반환합니다. 이것이 소프트웨어 키와의 근본적인 차이점입니다 — 공격자는 장치를 완전히 제어하더라도 개인 키를 추출할 수 없습니다.
Keystore는 Android의 다른 보안 메커니즘과 통합됩니다: 생체 인증(BiometricPrompt), 파일 수준 암호화(File-Based Encryption), SafetyNet/Play Integrity 확인 기능. 키는 특정 조건에서 자동 삭제되도록 구성할 수 있습니다: 비밀번호 제거 시, 새 지문 추가 시 또는 만료 시.
Android Keystore의 아키텍처는 하드웨어 보호 수준이 다른 세 가지 구현 수준을 포함합니다. 수준은 장치의 하드웨어 기능에 따라 다릅니다.
TEE(Trusted Execution Environment) — 메인 OS와 동일한 프로세서에서 병렬로 실행되는 격리 영역입니다. TEE는 ARM TrustZone 기술을 사용하여 프로세서의 물리적 코어를 Normal World(Android)와 Secure World(TEE)의 두 가상 영역으로 분할합니다. Secure World의 코드는 Normal World에서 접근할 수 없는 메모리와 주변 장치에 접근할 수 있습니다.
앱이 Keystore를 통해 암호화 작업을 호출하면, 요청은 Keymaster HAL을 통해 TEE로 전송되어 하드웨어에서 작업이 실행됩니다. 결과는 앱에 반환되지만 개인 키는 TEE의 보호된 메모리에 남아 있습니다. TEE는 GlobalPlatform TEE Protection Profile을 준수하여 인증되었으며, TrustZone을 지원하는 프로세서가 탑재된 Android 9+ 장치에 필수입니다.
TEE는 AES/GCM(128, 256비트), RSA(2048, 4096비트), EC(P-256, P-384, P-521) 및 HMAC-SHA256 알고리즘을 지원합니다. TEE의 성능은 소프트웨어 암호화보다 낮지만(20~40% 저하), 일반적인 작업(JWT 서명, 세션 키 복호화)의 지연 시간은 10~50ms를 초과하지 않습니다.
StrongBox — 메인 프로세서와 물리적으로 분리된 전용 보안 칩입니다. 프로세서 시간을 Android와 공유하는 TEE와 달리, StrongBox는 자체 CPU, RAM, True Random Number Generator(TRNG) 및 보호 스토리지(One-Time Programmable memory)를 갖추고 있습니다. StrongBox는 Common Criteria EAL 4+ 및 Secure IC Protection Profile에서 인증되었습니다.
StrongBox는 Android 9+ 장치에서 해당 칩이 있는 경우 사용 가능합니다(예: Google Pixel의 Titan M, Samsung Galaxy의 Knox). 개발자는 KeyGenParameterSpec에서 setIsStrongBoxBacked(true) 플래그를 사용하여 StrongBox를 활성화합니다. 하드웨어 지원이 없으면 플래그가 무시되고 Keystore는 TEE로 전환됩니다.
StrongBox의 제한 사항: 제한된 알고리즘 세트(AES-256, EC P-256, HMAC-SHA256) 지원, 작업 큐는 한 번에 최대 1개, 작업 수는 칩 리소스에 의해 제한됩니다. StrongBox는 고부하 시나리오에 적합하지 않습니다 — 빈번한 작업에는 TEE를 사용하고 StrongBox는 중요한 키(마스터 암호화 키, 서명 키)에만 사용하세요.
소프트웨어 기반 Keystore — TEE 또는 StrongBox 하드웨어 지원이 없는 장치에서 사용되는 소프트웨어 구현입니다. 키는 파일 시스템에 암호화되어 저장되지만, 개인 키는 일시적으로 RAM에서 복호화될 수 있습니다. 소프트웨어 Keystore는 덜 안전합니다 — 루트 액세스 권한을 가진 공격자가 메모리에서 키를 가로챌 수 있습니다.
Android 12(API 31)부터 Google은 모든 새 장치에 하드웨어 Keystore 지원을 의무화합니다. Android 9~11 장치에서는 저가형 모델에 소프트웨어 Keystore가 있을 수 있습니다. 개발자는 KeyStore.getKeyCharacteristics()로 보안 수준을 확인할 수 있습니다 — SECURITY_LEVEL_TRUSTED_ENVIRONMENT 또는 SECURITY_LEVEL_STRONGBOX 속성이 하드웨어 보호를 확인합니다.
Android Keystore는 키 유형에 따라 분류된 광범위한 암호화 알고리즘 세트를 지원합니다. 알고리즘 선택은 성능, 호환성 및 보안 수준에 영향을 미칩니다.
AES(Advanced Encryption Standard) — 장치에서 데이터 보호를 위한 대칭 암호화. 권장 모드: AES/GCM/NoPadding(256비트). GCM은 인증된 암호화(AEAD)를 제공하여 암호화된 데이터의 무결성을 검증합니다. GCM의 IV(Initialization Vector) 크기: 12바이트. AES/ECB는 사용하지 마십시오 — 적절한 보호를 제공하지 않습니다.
RSA(Rivest–Shamir–Adleman) — 세션 키 보호 및 디지털 서명을 위한 비대칭 암호화. 권장 크기: 2048 또는 4096비트. 모드: RSA/ECB/PKCS1Padding(암호화) 및 RSA/ECB/PKCS1Sign(서명). RSA 1024는 구식으로 간주되며 새 애플리케이션에 권장되지 않습니다(NIST SP 800-131A Rev. 2).
EC(Elliptic Curve) — 서명 및 키 교환을 위한 타원 곡선 암호화. 지원되는 곡선: secp256r1(P-256, 필수), secp384r1(P-384), secp521r1(P-521). EC는 훨씬 작은 키 크기로 RSA와 동등한 보안을 제공합니다. P-256은 대부분의 시나리오에 권장됩니다: 모든 장치에서 지원되며 128비트 보안 수준을 제공합니다.
HMAC(Hash-based Message Authentication Code) — 메시지의 대칭 인증. 지원되는 해시 함수: SHA-256, SHA-384, SHA-512. HMAC은 데이터 무결성 및 신뢰성 검증에 사용됩니다. 예: webhook 요청 확인 또는 구성 무결성 검사.
모든 알고리즘은 KeyGenParameterSpec.Builder.setUserAuthenticationRequired(true)를 통해 생체 인증에 바인딩할 수 있습니다. Android 11+에서는 생체 인증 후 재요청 없이 키를 사용할 수 있는 시간 제한(초)을 지정하는 setUserAuthenticationParameters() 플래그를 사용할 수 있습니다.
Android Keystore를 사용한 Kotlin의 실제 예제를 살펴보겠습니다: AES 키 생성, 데이터 암호화 및 서명용 비대칭 키 쌍 생성.
이 예제는 생체 인증에 바인딩된 256비트 AES/GCM 키를 생성합니다. 키는 getEncoded()를 통해 내보낼 수 없습니다.
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import java.security.KeyStore
private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
fun generateAesKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_ENCRYPT or
KeyProperties.PURPOSE_DECRYPT
)
.setKeySize(256)
.setBlockModes(KeyProperties.KEY_BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
.build()
val generator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
generator.init(spec)
generator.generateKey()
}
이 예제는 Android Keystore의 키를 사용하여 데이터를 암호화합니다. Cipher가 별칭으로 키를 가져와 AES/GCM 암호화를 초기화하고 IV와 함께 암호화된 데이터를 반환합니다.
fun encryptData(alias: String, plaintext: ByteArray): ByteArray {
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val secretKey = keyStore.getKey(alias, null) as SecretKey
cipher.init(Cipher.ENCRYPT_MODE, secretKey)
val iv = cipher.getIV()
val encrypted = cipher.doFinal(plaintext)
// IV + 암호화된 데이터
return iv + encrypted
}
fun decryptData(alias: String, ciphertextWithIv: ByteArray): ByteArray {
val iv = ciphertextWithIv.copyOfRange(0, 12)
val encrypted = ciphertextWithIv.copyOfRange(12, ciphertextWithIv.size)
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val secretKey = keyStore.getKey(alias, null) as SecretKey
val spec = GCMParameterSpec(128, iv)
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec)
return cipher.doFinal(encrypted)
}
이 예제는 StrongBox에 바인딩된 RSA-2048 키 쌍을 Keystore에 생성합니다. 개인 키는 서명에 사용되고, 공개 키는 getEncoded()를 통해 내보낼 수 있습니다.
fun generateRsaKeyPair(alias: String) {
val spec = KeyGenParameterSpec.Builder(
alias,
KeyProperties.PURPOSE_SIGN or
KeyProperties.PURPOSE_VERIFY
)
.setKeySize(2048)
.setSignaturePaddings(
KeyProperties.SIGNATURE_PADDING_RSA_PKCS1
)
.setDigests(KeyProperties.DIGEST_SHA256)
.setIsStrongBoxBacked(true)
.build()
val pair = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
).apply { init(spec) }
.generateKeyPair()
// 공개 키는 내보낼 수 있음
val publicKey = pair.public // X509EncodedKeySpec
}
Android Keystore를 효과적으로 사용하려면 성능을 유지하면서 최대한의 보호를 보장하는 규칙을 따라야 합니다.
KeyGenParameterSpec은 최소한의 필수 매개변수로 사용하세요: 실제로 사용하는 purpose, block modes 및 paddings만 지정하세요. 과도한 매개변수(예: 서명에만 사용하는 키에 PURPOSE_ENCRYPT 지정)는 불필요한 공격 벡터를 만듭니다. Android는 서명용 다이제스트를 명시적으로 지정할 것을 권장합니다 — SHA256이 최소 허용 수준(SHA1은 구식).
중요한 작업에는 키를 생체 인증에 바인딩하세요: setUserAuthenticationRequired(true)는 키가 생체 인증 후에만 사용 가능함을 보장합니다. Android 11+에서는 한 세션 내에서 매번 생체 인증을 요청하지 않도록 시간 제한(권장 30~60초)을 지정하여 setUserAuthenticationParameters()를 사용하세요. setInvalidatedByBiometricEnrollment(true)는 새 지문이나 얼굴이 추가되면 자동으로 키를 무효화하여 이전 생체 데이터로의 접근을 방지합니다.
초기화 단계에서 보안 수준을 확인하세요: KeyStore.getKeyCharacteristics()를 사용하여 SECURITY_LEVEL을 확인합니다. 장치가 소프트웨어 Keystore(SECURITY_LEVEL_SOFTWARE)만 지원하는 경우, 기능을 거부하거나 추가 암호화(예: 사용자 비밀번호를 통한 키 래핑)를 사용할지 결정합니다. StrongBox가 보장되지 않으면 이에 의존하지 말고, 항상 setIsStrongBoxBacked(true) 플래그를 지정하고 getKeyCharacteristics로 결과를 확인하세요.
키를 정기적으로 업데이트하세요: 암호화 키에는 권장 수명이 있습니다. NIST SP 800-57은 AES 키를 1~2년마다, RSA/EC 쌍을 2~3년마다 변경할 것을 권장합니다. 키 순환 메커니즘을 구현하세요: 앱 실행 시 키 생성 날짜(KeyGenParameterSpec.Builder.setKeyValidityStart/End)를 확인하고 만료 시 새 키를 생성합니다. 이전 키로 암호화된 이전 데이터는 복호화하여 새 키로 다시 암호화해야 합니다.
대규모 데이터에 Keystore를 사용하지 마세요: Keystore는 키 저장(수백 바이트)을 위한 것이며 대용량 파일 암호화용이 아닙니다. 데이터 암호화에는 다음 방식을 사용하세요: 임의의 AES 키(DEK — Data Encryption Key)를 생성하고, 데이터를 이 키로 암호화한 후 DEK를 Keystore 키(KEK — Key Encryption Key)로 암호화합니다. Android의 EncryptedSharedPreferences도 이 방식을 사용합니다: 마스터 키는 Keystore에, 데이터는 AES-256 GCM입니다.
자주 묻는 질문
아니요, Android Keystore는 개인 키가 TEE나 StrongBox를 절대 벗어나지 않도록 설계되었습니다. Keystore에서 생성된 키의 경우 getEncoded()가 null을 반환합니다. 키는 Cipher, Signature 또는 Mac API를 통해서만 사용할 수 있으며 원시 자료는 사용할 수 없습니다.
TEE(TrustZone) — 동일한 프로세서의 가상 격리로, 시간 분할을 사용합니다. StrongBox는 자체 CPU와 메모리를 갖춘 별도 칩입니다. StrongBox가 더 안전하지만(Common Criteria EAL 4+), 속도가 느리고 지원하는 알고리즘이 적습니다. TEE는 빈번한 작업에 적합하고, StrongBox는 중요한 키에 적합합니다.
setIsStrongBoxBacked(true) 플래그로 키를 생성한 후 KeyStore.getKeyCharacteristics()를 사용합니다. SECURITY_LEVEL_STRONGBOX 속성이 하드웨어 지원을 확인합니다. 장치가 StrongBox를 지원하지 않으면 Keystore가 오류 없이 TEE로 전환됩니다 — 보안 수준을 명시적으로 확인해야 합니다.
Keystore의 키는 장치에서 앱을 삭제하면 자동으로 삭제됩니다. Android 10+에서는 앱 매니페스트에 allowBackup=true 플래그가 있으면 키가 유지될 수 있지만, 재설치 후에는 사용할 수 없게 됩니다. 클린 설치 시 키를 다시 생성하는 것이 좋습니다.
아니요, Android Keystore는 특정 장치의 하드웨어에 바인딩됩니다. 한 장치의 TEE에서 생성된 키를 다른 장치로 전송할 수 없습니다. 크로스 플랫폼 암호화에는 다음 방식을 사용하세요: Keystore가 장치의 키를 보호하고, 세션 키는 비대칭 암호화를 사용하여 보안 API를 통해 전송됩니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.