KeyStore (Android)는 하드웨어 격리 기능을 갖춘 안전한 키 저장소를 위해 Android에 통합된 Java Cryptography Architecture (JCA) 암호화 제공자의 구현입니다. Android 4.3 (API 18)부터 KeyStore는 Keymaster HAL을 통해 하드웨어 키를 지원하며, Android 9 (API 28)부터는 전용 Secure Element의 키에 대해 StrongBox Keymaster를 지원합니다. Android 보안 문서에 따르면, “AndroidKeyStore” 제공자는 표준 Bouncy Castle 또는 OpenSSL KeyStore를 대체하여 무단 키 추출에 대한 시스템 수준의 보호를 제공합니다.
핵심 포인트
Android의 KeyStore는 별도의 애플리케이션이나 파일이 아니라 java.security.KeyStore 인터페이스를 구현하는 암호화 제공자입니다. 비공개 키, 대칭 키 및 신뢰할 수 있는 CA 인증서를 저장하고 사용하기 위한 통합 API를 제공합니다. 제공자는 “AndroidKeyStore”라는 이름으로 등록되며 표준 KeyStore.getInstance()를 통해 액세스할 수 있습니다.
Android 4.3 이전에는 암호화 작업이 Bouncy Castle을 통해 수행되었습니다. Android 4.3은 Keymaster HAL 1.0을 도입하여 ARM TrustZone에서 TEE 사용을 가능하게 했습니다. Android 6.0 (API 23)은 하드웨어 기반 지문 인증을 갖춘 Keymaster 2.0을 추가했습니다. Android 9 (API 28)은 전용 Secure Element를 위한 Keymaster 4.0과 StrongBox Keymaster를 도입했습니다.
Keymaster의 각 버전은 새로운 기능을 추가하고 키 격리를 개선합니다. 최신 기기(2022년 이상)는 Google Mobile Services 인증을 위해 Keymaster 4.0을 지원해야 하며, 이는 모든 Android 애플리케이션에 TEE 가용성을 보장합니다.
Android KeyStore는 세 가지 계층으로 구성됩니다: Java API (KeyStore, KeyPairGenerator), 시스템 프로세스 keystore (C++, 시스템 서비스로 실행), Keymaster HAL (TEE 또는 Secure Element의 라이브러리). 애플리케이션이 API를 호출하면 keystore 서비스가 요청을 Keymaster로 라우팅하고 작업이 안전한 환경에서 실행됩니다.
모든 비공개 키는 TEE에 저장되며 사용자 공간에서 읽을 수 없습니다. 시스템 keystore 서비스조차도 원시 키에 액세스할 수 없으며, Keymaster 내부의 키를 가리키는 핸들에만 액세스할 수 있습니다.
Android KeyStore는 표준 JCA 서비스 제공자 인터페이스를 구현합니다. 애플리케이션이 Cipher.getInstance(“RSA/ECB/PKCS1Padding”, “AndroidKeyStore”)를 호출하면 Android 보안 제공자가 체인을 통해 작업을 Keymaster에 위임합니다: Java → JNI → keystore 서비스 → Keymaster HAL.
AndroidKeyStore 제공자는 프로세스 시작 시 자동으로 등록됩니다. 그 우선순위는 Bouncy Castle이나 Conscrypt보다 높습니다. 따라서 제공자를 지정하지 않고 KeyStore.getInstance()를 호출하면 대부분의 경우 AndroidKeyStore가 반환됩니다. 명시적 호출을 위해서는 KeyStore.getInstance(“AndroidKeyStore”)를 사용하세요.
각 Android 애플리케이션은 KeyStore에 격리된 컨테이너를 가지고 있습니다. 동일한 UID(shared userId)를 가진 애플리케이션은 특정 키에 대한 공유 액세스 권한을 가질 수 있지만, 표준 설정은 애플리케이션 A가 애플리케이션 B의 키를 읽을 수 없도록 보장합니다.
load(null) — KeyStore 초기화. AndroidKeyStore의 경우 매개변수는 항상 null입니다. setEntry — 지정된 KeyProtection (목적, 다이제스트, 패딩)으로 키를 저장합니다. getEntry — KeyStore.PrivateKeyEntry, SecretKeyEntry 또는 TrustedCertificateEntry를 검색합니다. containsAlias — 키 존재 여부를 확인합니다. deleteEntry — 키를 영구적으로 삭제합니다.
import java.security.KeyStore
import java.security.KeyPairGenerator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
object KeyStoreManager {
private val keyStore by lazy {
KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
}
fun createRsaKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).setKeySize(2048)
.setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGN_PADDING_RSA_PKCS1)
.build()
val kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
kpg.initialize(spec)
kpg.generateKeyPair()
}
}
Android KeyStore는 기기의 Keymaster HAL 버전에 따라 다양한 광범위한 암호화 알고리즘을 지원합니다. 개발자는 생성 시도 시 KeyGenParameterSpec.Builder를 통해 지원되는 알고리즘 목록을 얻을 수 있습니다. 호환되지 않는 매개변수는 InvalidAlgorithmParameterException을 발생시킵니다.
RSA (1024~4096비트) — 서명 (SHA-1/SHA-256/SHA-384/SHA-512를 사용한 PKCS1, PSS) 및 암호화 (SHA-1/SHA-256을 사용한 OAEP) 용도. EC (P-224, P-256, P-384, P-521) — ECDSA 서명 및 ECDH 키 교환 용도. X25519 및 Ed25519 — Android 12 (API 31)부터 최신 암호화 프로토콜용.
비대칭 키의 경우 항상 Keymaster 내에서 생성하고 비공개 키를 절대 가져오지 마세요. 가져온 비공개 키는 하드웨어로 보호되지 않으며, 소프트웨어 계층에 저장되어 애플리케이션 프로세스가 손상되면 취약해집니다.
AES (128, 256비트) — CBC, CTR, GCM 모드의 대칭 암호화용. HMAC (SHA-1, SHA-256, SHA-512) — 메시지 인증용. ChaCha20 (Android 12+) — Poly1305 인증을 사용한 고성능 스트림 암호화용.
| 알고리즘 | Keymaster | 목적 | API |
|---|---|---|---|
| RSA | KM 1.0+ | 서명, 암호화 | 18+ |
| EC | KM 1.0+ | ECDSA, ECDH | 18+ |
| AES | KM 2.0+ | 대칭 암호화 | 23+ |
| HMAC | KM 2.0+ | 인증 코드 | 23+ |
| ChaCha20 | KM 3.0+ | 스트림 암호화 | 31+ |
| X25519/Ed25519 | KM 3.0+ | 키 교환 | 31+ |
KeyStore.PrivateKeyEntry — 비공개 키 (내보내기 불가) 및 인증서 체인을 포함합니다. KeyStore.SecretKeyEntry — 대칭 키용. KeyStore.TrustedCertificateEntry — 신뢰할 수 있는 CA 인증서용. 공개 키는 keyStore.getCertificate(alias).publicKey를 통해 내보낼 수 있습니다.
전체 시나리오를 살펴보겠습니다: 데이터 암호화용 AES 키 생성 및 생체 인식 보호 기능이 있는 서명용 EC 키 생성. 두 키 모두 하드웨어 지원과 함께 Android KeyStore 내에서 생성됩니다.
AES 키는 KeyGenParameterSpec과 함께 KeyGenerator를 통해 생성됩니다. 매개변수: PURPOSE_ENCRYPT + PURPOSE_DECRYPT, BLOCK_MODE_GCM (인증이 포함된 권장 모드), ENCRYPTION_PADDING_NONE (GCM에는 패딩 불필요).
import javax.crypto.KeyGenerator
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
fun generateAndEncrypt(alias: String, plainText: ByteArray): ByteArray {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(256)
.build()
val kg = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore")
kg.initialize(spec)
kg.generateKey()
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, getKeyFromStore(alias))
return cipher.doFinal(plainText)
}
userAuthenticationRequired=true가 설정된 EC 키는 각 서명 작업 전에 사용자 인증이 필요합니다. 이를 위해 Signature 객체를 포함하는 CryptoObject와 함께 BiometricPrompt가 사용됩니다. 생체 인식 확인이 성공하면 Keymaster가 작업을 허용합니다.
fun createBiometricSignKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN
).setAlgorithmParameterSpec(
ECGenParameterSpec("secp256r1")
).setDigests(KeyProperties.DIGEST_SHA256)
.setUserAuthenticationRequired(true)
.setInvalidatedByBiometricEnrollment(true)
.build()
val kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC,
"AndroidKeyStore"
)
kpg.initialize(spec)
kpg.generateKeyPair()
}
Android KeyStore는 소프트웨어 기반 KeyStore (JKS, BKS)가 제공할 수 없는 하드웨어 수준의 보안 보장을 제공합니다. 키는 SoC 수준에서 보호되며, Android 사용자 공간에 대한 완전한 제어권을 가져도 비공개 키를 추출할 수 없습니다.
Key Attestation은 애플리케이션 (및 서버)이 키가 생성된 환경을 확인할 수 있게 하는 메커니즘입니다. Android Keystore는 키 특성 목록 (알고리즘, 크기, 목적, 하드웨어 지원 여부, 생성 방식)이 포함된 인증서에 서명합니다. 서버는 Google 루트 인증서까지 인증서 체인을 확인합니다.
이는 금융 애플리케이션에 매우 중요합니다: 서버는 키가 하드웨어 환경에서 생성되었음을 요구하고 (Hardware-Backed = True) 소프트웨어 Keystore에서 생성된 키를 거부할 수 있습니다. Key Attestation은 공격자가 Keystore를 에뮬레이터로 대체하는 공격을 방지합니다.
setInvalidatedByBiometricEnrollment(true)는 생체 인식 템플릿이 변경되거나 제거될 때 Keymaster가 자동으로 키를 삭제함을 의미합니다. 이는 공격자가 기존 계정에 자신의 지문을 추가하는 공격으로부터 보호합니다. 새 지문이 추가되면 이전 키에 액세스할 수 없게 됩니다.
실패한 생체 인증 시도 카운터도 Keymaster에 의해 관리됩니다. maxBiometricAttempt (제조업체 구성 가능, 일반적으로 5회) 이후 Keymaster는 생체 인식 키를 사용한 모든 작업을 30초 동안 차단합니다. 10회 실패 시도 후에는 기기 비밀번호 (비밀 PIN)를 입력할 때까지 차단됩니다.
자주 묻는 질문
Bouncy Castle (BKS)은 비밀번호로 보호된 파일에 키를 저장하는 소프트웨어 기반 KeyStore입니다. Android KeyStore는 하드웨어 격리 TEE/StrongBox를 사용합니다. BKS 키는 루트 액세스로 추출할 수 있지만 Android KeyStore 키는 추출할 수 없습니다. BKS는 CA 인증서에 적합하고 Android KeyStore는 비공개 키에 적합합니다.
네, 생성 시 PURPOSE_ENCRYPT 또는 PURPOSE_DECRYPT 또는 PURPOSE_SIGN 또는 PURPOSE_VERIFY를 지정하면 가능합니다. 그러나 모범 사례는 다른 작업에 대해 별도의 키를 만드는 것입니다. 이는 하나의 키가 손상되었을 때 피해를 제한하고 최소 권한 원칙을 따릅니다.
android.security.keystore를 통해 사용 가능한 KeyStore.getKeyCharacteristics(alias)를 사용하세요. 이 메서드는 플래그 세트를 반환합니다: FLAG_HARDWARE — TEE의 키, FLAG_SECURE_ELEMENT — StrongBox의 키. 플래그가 없으면 키는 소프트웨어 전용입니다.
setInvalidatedByBiometricEnrollment(true)로 생성된 모든 키는 Keymaster에 의해 자동으로 무효화됩니다. 사용을 시도하면 애플리케이션은 KeyPermanentlyInvalidatedException을 받습니다. 이 키로 암호화된 데이터는 영구적으로 손실됩니다.
하드웨어 키 (TEE/StrongBox 내)는 백업을 지원하지 않습니다. 특정 기기에 연결되어 있습니다. 소프트웨어 기반 키는 Google Drive 백업에 포함될 수 있습니다. 기기 간 데이터 전송을 위해 서버에서 데이터를 암호화하고 새 기기에서 복호화하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.