Android Keystore는 암호화 키를 하드웨어 격리 환경에 안전하게 저장하기 위한 Android의 시스템 메커니즘입니다. 시스템은 ARM TrustZone이 있는 기기에서 Trusted Execution Environment(TEE) 또는 전용 Secure Element를 사용하여 칩 수준에서 키를 보호합니다. Android Open Source Project에 따르면 Keystore는 보안 환경에서 직접 키를 생성하는 RSA, EC, AES 및 HMAC 알고리즘을 지원합니다.
핵심 요점
Android Keystore는 Android API 1(Android 1.0)부터 구현된 암호화 제공자이지만, 완전한 하드웨어 지원은 Android 4.3(API 18)부터 나타났습니다. Keystore는 운영 체제가 손상되더라도 공격자가 평문으로 키를 추출할 수 없도록 개인 키를 안전하게 저장하는 문제를 해결합니다.
Android Keystore 아키텍처는 애플리케이션 API(java.security.KeyStore), 시스템 서비스(keystore 데몬), 하드웨어 수준(Keymaster HAL)의 세 계층으로 구성됩니다. 애플리케이션은 표준 Java Cryptography Architecture(JCA) API를 통해 액세스하며, 시스템 서비스는 TEE에서 실행되는 Keymaster로 요청을 라우팅합니다.
키를 사용한 모든 암호화 작업(서명, 복호화)은 TEE 또는 Secure Element 내부에서 수행됩니다. 키는 절대 보안 환경을 벗어나지 않으며, 애플리케이션은 키를 참조하기 위한 핸들(별칭)만 받습니다. 이는 키가 프로세스 메모리에서 잠재적으로 액세스 가능한 소프트웨어 KeyStore와의 근본적인 차이점입니다.
표준 JKS(Java KeyStore) 또는 BKS(Bouncy Castle)는 비밀번호로 보호된 파일에 키를 저장합니다. Android Keystore는 하드웨어 격리 환경에 키를 저장하여 루트 사용자로부터도 보호합니다. JKS는 직접 파일 시스템 액세스에 취약하지만 Android Keystore는 그렇지 않습니다.
또 다른 차이점: Android Keystore에서 키는 생성 시 지정된 엄격한 사용 매개변수(purpose — sign/verify/encrypt/decrypt만)를 가집니다. 이후에 변경할 수 없어 키의 오용을 방지합니다.
새 키를 생성할 때 애플리케이션은 KeyGenParameterSpec(향후 키의 모든 매개변수 포함)과 함께 KeyPairGenerator 또는 KeyGenerator를 호출합니다. 시스템은 Keymaster HAL에 요청을 전달하고, Keymaster는 TEE 내부에서 키를 생성하여 핸들을 반환합니다.
KeyGenParameterSpec.Builder 메서드는 필수 매개변수(Keystore 내 키 이름, 목적(PURPOSE_SIGN, PURPOSE_ENCRYPT), 알고리즘(RSA, EC, AES))를 받습니다. 추가 매개변수: digest(SHA-256), padding(PKCS7), userAuthenticationRequired(생체 인증), keyValidityStart/End(시간 제한).
매개변수를 설정한 후 KeyPairGenerator.generateKeyPair()는 KeyPair를 반환하며, PrivateKey는 Keymaster에 작업을 위임하는 객체입니다. 공개 키는 추출할 수 있지만 개인 키는 추출할 수 없습니다. 개인 키는 TEE 내부에만 존재합니다.
import java.security.KeyPairGenerator
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
fun generateKey(alias: String) {
val spec = KeyGenParameterSpec.Builder(alias,
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).setDigests(KeyProperties.DIGEST_SHA256)
.setSignaturePaddings(KeyProperties.SIGN_PADDING_RSA_PKCS1)
.setUserAuthenticationRequired(true)
.build()
val kpGen = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA,
"AndroidKeyStore"
)
kpGen.initialize(spec)
kpGen.generateKeyPair()
}
ECDSA 또는 RSA-PSS용 Signature는 표준 API를 통해 생성됩니다: Signature.getInstance(algorithm).initSign(privateKey). 서명 작업은 TEE에서 수행되며, 애플리케이션이 데이터를 전달하면 Keymaster가 하드웨어에서 서명하고 서명을 반환합니다. 키와 데이터는 공유 메모리에서 혼합되지 않습니다.
생체 인증 보호를 위해 서명 전에 사용자가 BiometricPrompt를 통해 인증해야 합니다. 인증에 실패하면 Keymaster는 작업을 수행하지 않고 CryptoAuthenticationException을 반환합니다.
import java.security.KeyStore
import java.security.Signature
import androidx.biometric.BiometricPrompt
fun signWithBiometric(alias: String) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val entry = ks.getEntry(alias, null) as KeyStore.PrivateKeyEntry
val signature = Signature.getInstance("SHA256withRSA")
signature.initSign(entry.privateKey)
// BiometricPrompt with CryptoObject(signature)가 FaceID/PIN을 요청합니다
}
Android는 두 가지 키 저장소 모드를 지원합니다: 소프트웨어(TEE가 없는 기기)와 하드웨어(TEE 또는 Secure Element가 있는 기기). 모드는 SoC 기능과 Android 버전에 따라 달라집니다.
Trusted Execution Environment이 없는 기기(Android 4.3 이전 또는 저가형 SoC)에서는 잠금 화면 비밀번호에서 파생된 마스터 키를 사용하여 암호화된 키가 저장됩니다. 이 모드는 덜 안전하며 암호화 작업 중에 키가 프로세스 메모리에서 액세스 가능합니다.
보호 수준은 AES-256-GCM을 사용한 KeyStore 파일 암호화를 기반으로 합니다. 암호화 키는 사용자의 비밀번호 또는 PIN에서 Scrypt(높은 반복 횟수의 PBKDF2)를 통해 생성됩니다.
최신 기기에서는 TEE(ARM TrustZone)에서 Keymaster 4.x를 사용합니다. 키는 TrustZone 내에서만 생성, 저장 및 사용됩니다. Linux 커널조차 개인 키에 액세스할 수 없으며 Keymaster HAL만 작업을 수행할 수 있습니다.
Secure Element(예: Samsung Knox의 eSE 또는 Google Pixel 3+의 StrongBox)는 자체 프로세서와 메모리를 가진 별도의 칩입니다. Common Criteria EAL 4+ 인증을 받았으며 물리적 변조 방지를 포함한 최대 수준의 보호를 제공합니다.
| 유형 | 저장 위치 | 보호 수준 | API부터 사용 가능 |
|---|---|---|---|
| 소프트웨어 | 파일 /data/misc/keystore | 중간 (AES-256) | API 1+ |
| Keymaster 3 | TEE (TrustZone) | 높음 | API 23+ |
| Keymaster 4 | TEE + Secure I/O | 매우 높음 | API 28+ |
| StrongBox | 하드웨어 Secure Element | 최대 | API 28+, 선택 사항 |
Android Keystore는 Java Cryptography Architecture(JCA)에 통합되어 있습니다. 제공자에 액세스하려면 표준 KeyStore.getInstance("AndroidKeyStore")를 사용합니다. API는 API 18부터 사용 가능합니다.
KeyStore.load(null) 메서드는 애플리케이션의 KeyStore 컨테이너를 로드합니다. 비밀번호가 필요하지 않으며 Android는 애플리케이션 컨텍스트와 해당 UID를 사용하여 액세스를 제어합니다. 공유 UID를 사용하지 않는 한 각 애플리케이션은 자신의 항목만 볼 수 있습니다.
setEntry 및 getEntry 메서드는 KeyStore.PrivateKeyEntry, SecretKeyEntry 또는 TrustedCertificateEntry와 함께 작동합니다. ProtectionParameter 매개변수는 Android Keystore의 경우 항상 null입니다(보호는 시스템 수준에서 구현됨).
import java.security.KeyStore
import java.security.cert.Certificate
import android.security.keystore.KeyProtection
fun storeSecretKey(alias: String, key: SecretKey) {
val ks = KeyStore.getInstance("AndroidKeyStore")
ks.load(null)
val prot = KeyProtection.Builder(
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setUserAuthenticationRequired(true)
.build()
ks.setEntry(alias, KeyStore.SecretKeyEntry(key), prot)
}
KeyCharacteristics를 사용하여 키가 저장된 환경(소프트웨어 KeyStore, TEE 또는 StrongBox)을 확인할 수 있습니다. getKeyCharacteristics() 메서드는 플래그 세트를 반환합니다: FLAG_HARDWARE(keymaster), FLAG_SECURE_ELEMENT(StrongBox), FLAG_TRUSTED_USER_PRESENCE_REQUIRED(생체 인증).
Android Keystore는 비대칭, 대칭 및 MAC의 세 가지 범주로 나뉜 광범위한 암호화 알고리즘을 지원합니다. 특정 알고리즘 지원은 Keymaster HAL 버전에 따라 다릅니다.
RSA(1024~4096비트) — 서명(PKCS1, PSS) 및 암호화(OAEP, PKCS1)용. EC(P-224, P-256, P-384, P-521) — ECDSA 서명 및 ECDH 키 교환용. AES(128, 256비트) — CBC, CTR, GCM 모드의 대칭 암호화용. HMAC(SHA1, SHA256, SHA512) — 메시지 인증용.
각 키에 대해 setPurposes가 지정되어 가능한 작업을 제한합니다. PURPOSE_SIGN이 있는 RSA 키는 공격자가 API에 액세스할 수 있더라도 암호화에 사용할 수 없습니다. 이는 하드웨어 수준의 키 사용 적용입니다.
Keymaster에는 생체 인증 실패 시도 횟수 카운터가 포함되어 있습니다. 지정된 실패 �수(setInvalidatedByBiometricEnrollment를 통해 구성 가능)를 초과하면 키를 사용할 수 없게 되고 삭제/재생성이 필요합니다. 모든 생체 인증 템플릿이 제거되면 userAuthenticationRequired=true인 모든 키가 자동으로 무효화됩니다.
Key Attestation(Android 8.1+)도 지원됩니다. 애플리케이션의 요청에 따라 Keymaster는 키 특성(하드웨어/소프트웨어, 알고리즘, 목적)에 대한 정보가 포함된 인증서에 서명합니다. 서버는 이 인증서를 확인하여 키가 신뢰할 수 있는 환경에서 생성되었는지 확인할 수 있습니다.
자주 묻는 질문
Java KeyStore는 비밀번호로 보호된 파일(JKS, BKS)에 키를 저장합니다. Android Keystore는 TEE 또는 Secure Element를 통한 하드웨어 격리를 사용합니다. Java KeyStore는 루트 액세스에 취약하지만 Android Keystore는 개인 키가 보안 환경을 절대 벗어나지 않으므로 취약하지 않습니다.
네, KeyProtection과 함께 KeyStore.setEntry를 통해 가능합니다. 그러나 가져온 키에는 하드웨어 보호가 없으며 마스터 키로 암호화된 소프트웨어 Keystore에 저장됩니다. 최대 보안을 위해 항상 Keystore 내부에서 키를 생성하세요.
KeyChain.isBoundKeyAlgorithm을 사용하거나 키 생성 후 KeyCharacteristics를 확인하세요. 특성에 FLAG_HARDWARE가 있으면 키가 TEE에서 생성되었음을 의미합니다. android.security.keystore.isHardwareBacked()도 확인할 수 있습니다.
애플리케이션이 제거되면 Android는 Keystore에서 해당 키를 모두 삭제합니다. 데이터는 복구할 수 없게 손실됩니다. 재설치 시 애플리케이션은 새 키를 생성해야 합니다. TEE를 통한 키 백업은 아키텍처상 불가능합니다.
잠긴 기기에서 Keymaster는 작업을 수행하지 않습니다. userAuthenticationRequired=true인 키는 매번 생체 인증 확인이 필요합니다. 루트 액세스가 있더라도 공격자는 Keymaster를 직접 호출할 수 없으며 Android Keystore 서비스를 통해서만 가능합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.