Biometric Crypto-based authentication
앱의 보안을 강화하기위해 생체인식을 통한 인증을 진행 할 수가 있습니다.
생체인식은 아래와 같은 인증유형을 선택할 수 있습니다.
public interface Authenticators {
/**
* Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
* requirements for <strong>Class 3</strong> (formerly <strong>Strong</strong>), as defined
* by the Android CDD.
*/
int BIOMETRIC_STRONG = 0x000F;
/**
* Any biometric (e.g. fingerprint, iris, or face) on the device that meets or exceeds the
* requirements for <strong>Class 2</strong> (formerly <strong>Weak</strong>), as defined by
* the Android CDD.
*
* <p>Note that this is a superset of {@link #BIOMETRIC_STRONG} and is defined such that
* {@code BIOMETRIC_STRONG | BIOMETRIC_WEAK == BIOMETRIC_WEAK}.
*/
int BIOMETRIC_WEAK = 0x00FF;
/**
* The non-biometric credential used to secure the device (i.e. PIN, pattern, or password).
* This should typically only be used in combination with a biometric auth type, such as
* {@link #BIOMETRIC_WEAK}.
*/
int DEVICE_CREDENTIAL = 1 << 15;
}디바이스에서 지원되는 인증유형은 얼굴인식, 지문, 홍채인식이 있다고 하더라도 위의 인증유형에 따라 디바이스 별 노출은 다를 수 있습니다.
일반적으로는 지문이 Class 3 의 인증유형에 속합니다.
BiometricPrompt.PromptInfo.Builder().apply {
setTitle(title)
setDescription(des)
setConfirmationRequired(false)
setNegativeButtonText(negativeText)
setAllowedAuthenticators(BIOMETRIC_STRONG)
subTitle?.let { sub ->
setSubtitle(sub)
}
}.build()하지만 생체인식의 더 강한 보안을 원할 수 있습니다. 그 때는 생체인식의 암호화를 사용할 수가 있습니다.
biometricPrompt.authenticate(
promptInfo,
BiometricPrompt.CryptoObject(cipher)
)암호화기능은 아래와 같은 설명처럼 Class 3 의 인증유형을 가집니다.
Shows the biometric prompt to the user. The prompt survives lifecycle changes by default. To cancel authentication and dismiss the prompt, use cancelAuthentication().
Calling this method invokes crypto-based authentication, which is incompatible with Class 2 (formerly Weak) biometrics and (prior to Android 11) device credential. Therefore, it is an error for info to explicitly allow any of these authenticator types on an incompatible Android version.
Params:
info — An object describing the appearance and behavior of the prompt.
crypto — A crypto object to be associated with this authentication.
Throws:
IllegalArgumentException — If any of the allowed authenticator types specified by info do not support crypto-based authentication.
See Also:
authenticate(BiometricPrompt.PromptInfo), BiometricPrompt.PromptInfo.Builder.setAllowedAuthenticators(int)
암호화를 하기위해 아래와 같은 CrytographyManager 를 만들어 생체인증요청 시 암호화를 위한 Cipher 로 사용될 수 있게합니다.
암호화를 사용하게 되면 먼저 등록과정을 거치고 그다음에 인증과정을 진행하도록 합니다.
그 전에 먼저 BiometricPrompt 를 획득합니다.
Get parkwow13’s stories in your inbox
Join Medium for free to get updates from this writer.
1.BiometricPrompt.AuthenticationCallback()
val callback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
error(errorCode)
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Logger.d("result.authenticationType ${result.authenticationType}")
success(result)
}
}
val executor = ContextCompat.getMainExecutor(this)2. BiometricPrompt
fun Fragment.createBiometricPrompt(
error: (Int) -> Unit,
success: (BiometricPrompt.AuthenticationResult) -> Unit
): BiometricPrompt {
val (executor, callback) = requireContext().biometricPromptControl(
error = error
) { authResult ->
success(authResult)
}
return BiometricPrompt(this, executor, callback)
}생체인식 등록
생체인식을 등록하는 과정은 Encrypt 를 수행합니다.
fun authenticateToEncrypt(
context: Context,
biometricPrompt: BiometricPrompt,
cryptographyManager: CryptographyManager
) {
val promptInfo = context.defaultPrompt()
val cipher =
cryptographyManager.getInitializedCipherForEncryption(BuildConfig.SECRET_KEY_NAME, true)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}BiometricPrompt 에 CyptoObject 를 생체인식하기위한 값으로 넣어줍니다.
이후 생체인식이 성공하면 CyptoObject 에 넣은 Cipher를 이용해 암호화를 진행해줍니다.
fun encryptBiometricResult(
context: Context,
cryptographyManager: CryptographyManager,
authResult: BiometricPrompt.AuthenticationResult
) {
authResult.cryptoObject?.cipher?.apply {
val fakeToken = UUID.randomUUID().toString()
val encryptedServerTokenWrapper = cryptographyManager.encryptData(fakeToken, this)
cryptographyManager.persistCiphertextWrapperToSharedPrefs(
encryptedServerTokenWrapper,
context,
Constant.SHARED_PREFS_FILENAME,
Context.MODE_PRIVATE,
Constant.CIPHERTEXT_WRAPPER
)
}
}생체인식 인증
인증과정은 반대로 Decrypt 작업을 진행합니다.
fun authenticateToDecrypt(
context: Context,
biometricPrompt: BiometricPrompt,
cryptographyManager: CryptographyManager,
cipherWrapper: CiphertextWrapper
) {
val promptInfo = context.defaultPrompt()
val cipher = cryptographyManager.getInitializedCipherForDecryption(
BuildConfig.SECRET_KEY_NAME,
cipherWrapper.initializationVector
)
biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
}위와 같이 인증할 때 CryptObject 에 복호화를 위한 Cipher 를 넣어줍니다.
그리고 CipherTextWrapper 를 정보를 가져옵니다.
fun getCiphertextWrapper(
context: Context,
cryptographyManager: CryptographyManager
): CiphertextWrapper? {
return cryptographyManager.getCiphertextWrapperFromSharedPrefs(
context,
Constant.SHARED_PREFS_FILENAME,
Context.MODE_PRIVATE,
Constant.CIPHERTEXT_WRAPPER
)
}그리고 인증이 성공할 때 복호화를 진행합니다.
fun decryptBiometricResult(
cipherWrapper: CiphertextWrapper,
cryptographyManager: CryptographyManager,
authResult: BiometricPrompt.AuthenticationResult
) {
authResult.cryptoObject?.cipher?.let { cipher ->
val decryptText = cryptographyManager.decryptData(cipherWrapper.ciphertext, cipher)
Logger.d("decryptText : $decryptText")
}
}만약 복호화가 실패하였다면 KeyPermanentlyInvalidatedException 이 발생합니다. 이 것을 이용하여 생체인식기능의 추가, 변경 유무도 확인 할 수가 있습니다.
참조 :





