본문 바로가기

카테고리 없음

Keystore 및 Keychain 이용 암복호화 및 키생성 주요 내용

<주요 정리>

1. Keystore

- 안드로이드에서는 KeyGenParameterSpec.Builder에서 setUserAuthenticationRequired 옵션을 true로 설정하면 해당 키 사용시 추가 인증(지문 인증 등)이 요청된다.

=> 인증에 실패한 경우 UserNotAuthenticatedException 이 발생함

- 이 경우, 인증장치 정보(지문 등) 삭제 또는 해제가 발생하면 해당 키를 비활성화되며 지문 인증시 반환되는 Cipher 객체를 이용하여 암호화 및 서명을 수행하므로 사용자의 승인 없이 Keystore 내 키 사용이 불가능하다.

- 지문 인증 삭제/추가 시 기존 Keystore내 모든 키가 비활성화되므로 지문 재인증을 통해 키정보를 새로 구축해야 한다.


2. iOS

- iOS에서 개인키 생성시 속성으로 kSecAttrIsPermanent를 true로 지정하면 keychain에 키가 자동으로 저장한다. (단, keychain 저장이 SecureEnclave 저장을 의미하는것은 아니다)

- iOS에서 SecKeyCreateRandomKey() 함수를 이용해 개인키 생성시 SecAttrTokenIDSecureEnclave를 설정하면 SecureEnclave에 개인키가 저장된다(단 EC-256bit 만 지원, iOS9 부터 지원)

- iOS에서 SecAccessControlCreateWithFlags의 옵션인 SecAccessControlCreateFlags에

  1) privateKeyUsage를 설정하면 SecureEnclave를 통해 서명이 가능하다. 설정안하면 서명이 안된다.

  2) userPresence를 설정하면 사용자 추가 인증이 있는 경우에만 서명이 가능하다. (단 이 경우 인증장치 정보(지문 등) 해제가 발생하면 해당 정보는 삭제된다)

 * SecItemAdd() 사용할때도 kSecAttrAccessible를 설정하여 touchID 사용을 강제화할 수 있다.

- iOS는 kSecAttrApplicationTag를 통해서 SecureEnclave의 개인키를 구별한다.

- iOS의 경우 지문 인증 해제시 데이터 삭제 또는 유지를 물어본다 

- FaceID는 해제시 별도 선택없이 데이터를 유지하므로 기존 얼굴 인증 데이터를 재사용이 가능하다.


<실제 구현 참고용 github 소스>

(iOS) https://github.com/trailofbits/SecureEnclaveCrypto

(Android) https://github.com/sitepoint-editors/AndroidFingerprintAPI


<키스토어와 지문인식 연동 방법>

- 지문인식 성공시 cipher 객체를 전달받아 암복호화 가능

public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {

            Cipher cipher = result.getCryptoObject().getCipher();

            byte[] output;

            try {

                output = cipher.doFinal(input);

            } catch (IllegalBlockSizeException | BadPaddingException e) {

                throw new RuntimeException(e);

            }

            callback.done(output);

        }


<키체인 및 SecureEnclave 연동 세부 방법>

1. iOS Keychain은 Keychain과 Secure Enclave를 연동하기 위해서는 XXXthisDeviceOnly 옵션을 넣어야한다. 특히 서명용으로 사용하려면 privateKeyUsage 옵션을 추가하여야 한다

let access =

    SecAccessControlCreateWithFlags(kCFAllocatorDefault,

                                    kSecAttrAccessibleWhenUnlockedThisDeviceOnly,

                                    .privateKeyUsage,

                                    nil)!   // Ignore error


2. 또한 SecCreateRandomKey() 호출시 kSecAttrTokenIDSecureEnclave를 지정해야 SecureEnclave에 저장된다.

let attributes: [String, Any] = [

  kSecAttrKeyType as String:            type,

  kSecAttrKeySizeInBits as String:      256,

  kSecAttrTokenID as String:            kSecAttrTokenIDSecureEnclave,

  kSecPrivateKeyAttrs as String: [

    kSecAttrIsPermanent as String:      true,

    kSecAttrApplicationTag as String:   <# a tag #>,

    kSecAttrAccessControl as String:    access

  ]

]


guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {

    throw error!.takeRetainedValue() as Error

}

3. 또한 userPresence 옵션을 추가하면 키체인 접근시 별도 인증(passcode, touch ID)를 받도록 제한할 수 있다.


* iOS 구현 참고자료

https://github.com/satyamtyagi/swiftcrypto

https://www.linkedin.com/pulse/ios-10-how-use-secure-enclave-touch-id-protect-your-keys-satyam-tyagi/


* 키생성시 접근제어 옵션 설정관련 객체 : kSecAttrAccessible, kSecAttrAccessControl 


----------------------------------------------------------------------------------------------------------------

* 가설1 : Keychain 항목은 SecureEnclave의 마스터키로 암호화된다

* 가설2 : 반면 안드로이드는 Keystore를 통해 키 생성시 키가 TZ영역에 저장되지 않고 일반영역에 저장된다. 단 PKEY의 생성을 TZ에서 수행하며 PKEY의 개인키 부분은 TZ를 통해 암호화되어 일반영역에 저장된다.(Qcom의 경우)

* 가설3 : 안드로이드는 암호화된 개인키를 유출할 수 있고, iOS는 그나마도 유출할 수 없다.(iOS 승?)

* 가설4 : 안드로이드는 암호화된 개인키를 TZ가 가져가서 복호화하고 서명을 수행할거 같다. 머 어째든 TZ에서 일어남은 동일하다.