<주요 정리>
1. Keystore
- 안드로이드에서는 KeyGenParameterSpec.Builder에서 setUserAuthenticationRequired 옵션을 true로 설정하면 해당 키 사용시 추가 인증(지문 인증 등)이 요청된다.
- 이 경우, 인증장치 정보(지문 등) 삭제 또는 해제가 발생하면 해당 키를 비활성화되며 지문 인증시 반환되는 Cipher 객체를 이용하여 암호화 및 서명을 수행하므로 사용자의 승인 없이 Keystore 내 키 사용이 불가능하다.
- 지문 인증 삭제/추가 시 기존 Keystore내 모든 키가 비활성화되므로 지문 재인증을 통해 키정보를 새로 구축해야 한다.
- 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
* 키생성시 접근제어 옵션 설정관련 객체 : kSecAttrAccessible, kSecAttrAccessControl
----------------------------------------------------------------------------------------------------------------
* 가설1 : Keychain 항목은 SecureEnclave의 마스터키로 암호화된다
* 가설2 : 반면 안드로이드는 Keystore를 통해 키 생성시 키가 TZ영역에 저장되지 않고 일반영역에 저장된다. 단 PKEY의 생성을 TZ에서 수행하며 PKEY의 개인키 부분은 TZ를 통해 암호화되어 일반영역에 저장된다.(Qcom의 경우)
* 가설3 : 안드로이드는 암호화된 개인키를 유출할 수 있고, iOS는 그나마도 유출할 수 없다.(iOS 승?)
* 가설4 : 안드로이드는 암호화된 개인키를 TZ가 가져가서 복호화하고 서명을 수행할거 같다. 머 어째든 TZ에서 일어남은 동일하다.