Android 키 저장소 시스템

Android 키 저장소 시스템을 사용하면 암호화 키를 컨테이너에 저장하여 기기에서 키를 추출하기 어렵게 할 수 있습니다. 키 저장소에 키가 저장되면 키 자료는 내보낼 수 없는 상태로 유지하면서 키를 암호화 작업에 사용할 수 있습니다. 또한 키 저장소 시스템을 통해 키 사용 시기와 방법을 제한할 수 있습니다(예: 키 사용을 위해 사용자 인증을 요구하거나 특정 암호화 모드에서만 사용하도록 키를 제한). 자세한 내용은 보안 기능 섹션을 참고하세요.

키 저장소 시스템은 Android 4.0(API 수준 14)에서 도입된 KeyChain API와 Android 4.3(API 수준 18)에서 도입된 Android 키 저장소 제공자 기능에서도 사용됩니다. 이 문서에서는 Android 키 저장소 시스템 사용 시기 및 방법을 설명합니다.

보안 기능

Android 키 저장소 시스템은 키 자료를 무단으로 사용하지 못하도록 두 가지 방법으로 보호합니다. 첫째, 애플리케이션 프로세스와 Android 기기 전체에서 키 자료를 추출하지 못하도록 하여 Android 기기 외부에서 키 자료를 무단으로 사용하는 위험을 줄입니다. 둘째, 키 저장소 시스템은 앱이 앱 키의 승인된 사용을 지정하도록 하고 이러한 제한을 앱 프로세스 외부에 적용하여 Android 기기 내에서 키 자료를 무단으로 사용하는 위험을 줄입니다.

추출 차단

Android 키 저장소 키의 키 자료 추출을 차단하기 위해 두 가지 보안 조치가 사용됩니다.

  • 키 자료는 애플리케이션 프로세스에 포함되지 않습니다. 앱에서 Android 키 저장소 키를 사용하여 암호화 작업을 실행하는 경우, 백그라운드 작업을 통해 서명하거나 확인할 일반 텍스트, 암호문, 메시지가 암호화 작업을 실행하는 시스템 프로세스로 공급됩니다. 앱의 프로세스가 손상된 경우 공격자가 앱의 키를 사용할 수는 있지만 키 자료를 추출(예: Android 기기 외부에서 사용하기 위해)할 수는 없습니다.
  • 키 자료는 신뢰할 수 있는 실행 환경(TEE) 또는 보안 요소(SE)와 같은 Android 기기의 보안 하드웨어에 바인딩될 수 있습니다. 키에 이 기능을 사용하도록 설정하면 키 자료가 보안 하드웨어 외부에 노출되지 않습니다. Android OS가 손상되거나 공격자가 기기의 내부 저장소를 읽을 수 있는 경우 공격자는 Android 기기에서 모든 앱의 Android 키 저장소 키를 사용할 수도 있지만 기기에서 키를 추출할 수는 없습니다. 이 기능은 기기의 보안 하드웨어에서 키 사용이 승인된 키 알고리즘, 차단 모드, 패딩 스킴, 다이제스트 등의 특정한 조합을 지원하는 경우에만 사용 설정됩니다.

    키에 이 기능이 사용 설정되어 있는지 확인하려면 키의 KeyInfo를 가져오세요. 다음 단계는 앱의 타겟 SDK 버전에 따라 다릅니다.

    • 앱이 Android 10(API 수준 29) 이상을 타겟팅한다면 getSecurityLevel()의 반환 값을 검사합니다. KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT 또는 KeyProperties.SecurityLevelEnum.STRONGBOX와 일치하는 반환 값은 키가 보안 하드웨어 내에 있음을 나타냅니다.
    • 앱이 Android 9(API 수준 28) 이하를 타겟팅한다면 KeyInfo.isInsideSecurityHardware()의 불리언 반환 값을 검사합니다.

하드웨어 보안 모듈

Android 9(API 수준 28) 이상을 실행하는 지원되는 기기에는 하드웨어 보안 모듈과 같은 보안 요소에 있는 Keymaster 또는 Keymint HAL 구현인 StrongBox Keymaster가 있을 수 있습니다. 하드웨어 보안 모듈은 TEE와 같이 Linux 커널 손상으로는 드러낼 수 없는 다양한 키 저장소 구현을 참조할 수 있지만 StrongBox는 삽입된 보안 요소(eSE) 또는 SoC 보안 처리 장치(iSE)와 같은 기기를 명시적으로 참조합니다.

모듈에 포함된 구성요소는 다음과 같습니다.

  • 자체 CPU
  • 보안 저장소
  • 순수 난수 생성기
  • 패키지 변조와 앱의 무단 사이드로드를 방지하는 추가 메커니즘
  • 보안 타이머
  • 범용 입력/출력(GPIO)과 같은 재부팅 알림 핀(또는 이와 동등한 핀)

저전력 StrongBox 구현을 지원하기 위해 다음과 같은 일부 알고리즘과 키 크기가 지원됩니다.

  • RSA 2048
  • AES 128 및 256
  • ECDSA, ECDH P-256
  • HMAC-SHA256(8~64바이트의 키 크기 지원)
  • Triple DES
  • 연장된 APDU
  • 키 증명
  • 업그레이드를 위한 개정안 H 지원

KeyStore 클래스를 사용하여 키를 생성하거나 가져오는 경우 setIsStrongBoxBacked() 메서드로 true를 전달하여 StrongBox Keymaster에 키 저장에 관한 환경설정을 표시합니다.

StrongBox는 TEE에 비해 약간 느리고 리소스 제약이 있지만(즉, 동시 실행 작업을 더 적게 지원) StrongBox는 물리적 공격과 부채널 공격에 더 나은 보안을 보장합니다. 강력한 보안 보장을 앱 리소스 효율성보다 우선하려면 가능한 한 기기에서 StrongBox를 사용하는 것이 좋습니다. StrongBox를 사용할 수 없는 경우 앱은 항상 TEE로 대체하여 키 자료를 저장할 수 있습니다.

키 사용 승인

Android 기기에서 키의 무단 사용을 방지하기 위해 Android 키 저장소를 사용하면 앱에서 키를 생성하거나 가져올 때 키의 승인된 사용을 지정할 수 있습니다. 키를 생성하거나 가져온 후에는 승인을 변경할 수 없습니다. 그런 다음, 키가 사용될 때마다 Android 키 저장소에서 승인을 실행합니다. 이 기능은 고급 보안 기능이며, 일반적으로 키 생성/가져오기 이후(이전이나 도중은 아님) 애플리케이션 프로세스가 손상되더라도 키 무단 사용으로 이어져서는 안 된다는 요구사항이 있는 경우에만 유용합니다.

지원되는 키 사용 승인은 다음과 같은 범주로 구분됩니다.

  • 암호화: 승인된 키 알고리즘, 작업 또는 목적(암호화, 복호화, 서명, 확인), 패딩 스킴, 차단 모드 또는 다이제스트로만 키를 사용할 수 있습니다.
  • 시간적 유효성 간격: 정의된 시간 간격 동안에만 키 사용이 승인됩니다.
  • 사용자 인증: 최근에 사용자가 인증된 경우에만 키를 사용할 수 있습니다. 키 사용을 위한 사용자 인증 요구를 참고하세요.

키 자료가 보안 하드웨어 내부에 있는 키(KeyInfo.isInsideSecurityHardware() 참고 또는 Android 10(API 수준 29) 이상을 타겟팅하는 앱의 경우 KeyInfo.getSecurityLevel() 참고)의 추가적인 보안 조치로 일부 키 사용 승인은 Android 기기에 따라 보안 하드웨어에서 시행할 수 있습니다. 보안 하드웨어는 일반적으로 암호화 및 사용자 인증 승인을 시행합니다. 그러나 보안 하드웨어는 대개 시간적 유효성 간격 승인을 시행하지 않습니다. 독립적인 보안 실시간 시계가 없는 경우가 일반적이기 때문입니다.

보안 하드웨어에서 키의 사용자 인증 승인을 시행하는지 KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()를 사용하여 쿼리할 수 있습니다.

키 체인 및 Android 키 저장소 제공자 중에 선택

시스템 수준의 사용자 인증 정보를 원하는 경우 KeyChain API를 사용합니다. 앱에서 KeyChain API를 통해 사용자 인증 정보 사용을 요청하는 경우 사용자는 설치된 사용자 인증 정보 중 앱이 액세스할 수 있는 사용자 인증 정보를 시스템 제공 UI를 통해 선택할 수 있습니다. 이렇게 하면 사용자 동의를 받아 여러 앱에서 동일한 사용자 인증 정보 집합을 사용할 수 있습니다.

Android 키 저장소 제공자를 사용하면 개별 앱이 해당 앱만 액세스할 수 있는 자체 사용자 인증 정보를 저장할 수 있습니다. 이를 통해 앱은 KeyChain API가 시스템 수준의 사용자 인증 정보에 제공하는 것과 동일한 보안 이점을 제공하면서 자체적으로만 사용할 수 있는 사용자 인증 정보를 관리할 수 있습니다. 이 방법에서는 사용자가 사용자 인증 정보를 선택하지 않아도 됩니다.

Android 키 저장소 제공자 사용

이 기능을 사용하려면 표준 KeyStoreKeyPairGenerator 또는 KeyGenerator 클래스와 Android 4.3(API 수준 18)에서 도입된 AndroidKeyStore 제공자를 함께 사용하세요.

AndroidKeyStoreKeyStore.getInstance(type) 메서드와 사용할 KeyStore 유형으로 등록되고 KeyPairGenerator.getInstance(algorithm, provider)KeyGenerator.getInstance(algorithm, provider) 메서드와 사용할 제공자로 등록됩니다.

새 비공개 키 또는 보안 비밀 키 생성

PrivateKey가 포함된 새 KeyPair를 생성하려면 인증서의 초기 X.509 속성을 지정해야 합니다. KeyStore.setKeyEntry()를 사용하면 나중에 이 인증서를 인증 기관(CA)이 서명한 인증서로 바꿀 수 있습니다.

키 쌍을 생성하려면 KeyPairGeneratorKeyGenParameterSpec을 함께 사용합니다.

Kotlin

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    build()
}

kpg.initialize(parameterSpec)

val kp = kpg.generateKeyPair()

Java

/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
        .setDigests(KeyProperties.DIGEST_SHA256,
            KeyProperties.DIGEST_SHA512)
        .build());

KeyPair kp = kpg.generateKeyPair();

암호화된 키를 보안 하드웨어로 가져오기

Android 9(API 수준 28) 이상에서는 ASN.1로 인코딩된 키 형식을 사용하여 암호화된 키를 키 저장소로 안전하게 가져올 수 있습니다. 그런 다음, Keymaster가 키 저장소의 키를 복호화하므로 키의 내용이 기기의 호스트 메모리에 일반 텍스트로 표시되지 않습니다. 이 프로세스는 추가적인 키 복호화 보안을 제공합니다.

암호화된 키를 키 저장소로 안전하게 가져오도록 지원하려면 다음 단계를 완료하세요.

  1. PURPOSE_WRAP_KEY 용도로 사용되는 키 쌍을 생성합니다. 이 키 쌍에 증명도 추가하는 것이 좋습니다.

  2. 신뢰할 수 있는 서버나 컴퓨터에서 SecureKeyWrapper의 ASN.1 메시지를 생성합니다.

    래퍼에는 다음 스키마가 있습니다.

       KeyDescription ::= SEQUENCE {
           keyFormat INTEGER,
           authorizationList AuthorizationList
       }
    
       SecureKeyWrapper ::= SEQUENCE {
           wrapperFormatVersion INTEGER,
           encryptedTransportKey OCTET_STRING,
           initializationVector OCTET_STRING,
           keyDescription KeyDescription,
           secureKey OCTET_STRING,
           tag OCTET_STRING
       }
    
  3. ASN.1 메시지를 바이트 배열로 전달하는 WrappedKeyEntry 객체를 만듭니다.

  4. Keystore.Entry 객체를 허용하는 setEntry()의 오버로드로 이 WrappedKeyEntry 객체를 전달합니다.

키 저장소 항목 작업

모든 표준 KeyStore API를 통해 AndroidKeyStore 제공자에 액세스할 수 있습니다.

항목 나열

aliases() 메서드를 호출하여 키 저장소의 항목을 나열합니다.

Kotlin

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
   load(null)
}
val aliases: Enumeration<String> = ks.aliases()

자바

/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();

데이터 서명 및 확인

키 저장소의 KeyStore.Entry를 가져오고 sign()과 같은 Signature API를 사용하여 데이터에 서명합니다.

Kotlin

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry: KeyStore.Entry = ks.getEntry(alias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return null
}
val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run {
    initSign(entry.privateKey)
    update(data)
    sign()
}

자바

/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return null;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(((PrivateKeyEntry) entry).getPrivateKey());
s.update(data);
byte[] signature = s.sign();

마찬가지로 verify(byte[]) 메서드를 사용하여 데이터를 확인합니다.

Kotlin

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
val ks = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
if (entry == null) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return false
}
val valid: Boolean = Signature.getInstance("SHA256withECDSA").run {
    initVerify(entry.certificate)
    update(data)
    verify(signature)
}

자바

/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return false;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(((PrivateKeyEntry) entry).getCertificate());
s.update(data);
boolean valid = s.verify(signature);

키 사용을 위한 사용자 인증 요구

키를 생성하거나 AndroidKeyStore로 가져올 때 사용자가 인증된 경우에만 키 사용을 승인하도록 지정할 수 있습니다. 사용자는 보안 잠금 화면 사용자 인증 정보(패턴/PIN/비밀번호, 생체 인식 사용자 인증 정보)의 하위 집합을 사용하여 인증됩니다.

이 기능은 고급 보안 기능이며, 일반적으로 키 생성/가져오기 이후(이전이나 도중은 아님) 애플리케이션 프로세스가 손상되더라도 키 사용을 위한 사용자 인증 요구사항이 무시되어서는 안 된다는 요구사항이 있는 경우에만 유용합니다.

사용자가 인증된 경우에만 키 사용을 승인받은 경우 setUserAuthenticationParameters()를 호출하여 다음 모드 중 하나에서 작동하도록 구성할 수 있습니다.

일정 기간 동안 승인
사용자가 지정된 사용자 인증 정보 중 하나를 사용하여 인증하는 즉시 모든 키의 사용이 승인됩니다.
특정 암호화 작업 기간 동안 승인

특정 키와 관련된 각 작업은 사용자가 개별적으로 승인해야 합니다.

앱은 BiometricPrompt 인스턴스에서 authenticate()를 호출하여 이 프로세스를 시작합니다.

생성하는 키마다 강력한 생체 인식 사용자 인증 정보잠금 화면 사용자 인증 정보 또는 두 유형의 사용자 인증 정보를 모두 지원하도록 선택할 수 있습니다. 앱 키에서 사용하는 사용자 인증 정보를 사용자가 설정했는지 확인하려면 canAuthenticate()를 호출하세요.

키가 생체 인식 사용자 인증 정보만 지원하는 경우 키는 새로운 생체 인식 등록이 추가될 때마다 기본적으로 무효화됩니다. 새 생체 인식 등록이 추가될 때 키의 유효성이 유지되도록 구성할 수 있습니다. 이렇게 하려면 falsesetInvalidatedByBiometricEnrollment()에 전달합니다.

생체 인식 인증 대화상자를 표시하는 방법을 비롯하여 앱에 생체 인식 인증 기능을 추가하는 방법을 자세히 알아보세요.

지원되는 알고리즘

블로그 문서

블로그 항목 ICS에서 키 저장소 액세스 통합을 참고하세요.