안드로이드 생체 인증을 구현해보기 위해 작성한 글, 코드기 때문에 참고만 하시고 혹시 문제가 있는 부분이 있다면 말씀해주세요 !!
안드로이드에서 생체 인증을 사용할 수 있던 것은 API Level 23 ( Android 6.0 ) 부터이다.
이 때는 fingerprintManager 라는 녀석을 사용했다.
하지만 API level 28 ( Android 9.0 ) 에 와서는 지문만이 아닌 다른 생체 인증 방식이 생겨나면서 deprecated 되었다.
이후부터는 BiometricPrompt 라는 녀석을 사용하게 된다.
먼저 fingerprintManager 를 살펴보도록 하자
우선 지문 사용에 대한 권한을 허용해줘야 한다.
AndroidManifest.xml
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
다음은 fingerprintManager 를 사용해 인증을 하는 코드입니다.
fingerprintManager 는 암호화된FingerprintManager.CryptoObject
를 인자로 받는다. 때문에 별도로 암호화 과정이 필요하다.
BioAuthManager.java
// api 23 ( ANDROID 6.0 ) 부터 api 28 ( ANDROID 9.0 ) 까지는 fingerprint 사용
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
fingerprintManager = (FingerprintManager) context.getSystemService(FINGERPRINT_SERVICE);
keyguardManager = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE);
String comment = "";
// 지문을 사용할 수 없는 디바이스인 경우
if (!fingerprintManager.isHardwareDetected())
{
Log.d("fingerprint", "it is not device that can use fingerprint");
}
// 지문 인증 사용을 거부한 경우
else if (ContextCompat.checkSelfPermission(context, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED)
{
Log.d("fingerprint", "permission denied");
}
// 잠금 화면이 설정되지 않은 경우
else if (!keyguardManager.isKeyguardSecure())
{
Log.d("fingerprint", "please set lock screen");
}
// 등록된 지문이 없는 경우
else if (!fingerprintManager.hasEnrolledFingerprints())
{
Log.d("fingerprint", "please enroll fingerprint");
}
else
{
Log.d("fingerprint", "requirement fingerprint needed all pass");
keyManager.generateKey();
if (keyManager.cipherInit())
{
Cipher cipher = keyManager.getCipher();
CancellationSignal signal = keyManager.getSignal();
cryptoObject = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, signal, 0, new FingerprintManager.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
Log.d("fingerprint", String.valueOf(errorCode));
}
@Override
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
Log.d("fingerprint", "auth success");
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Log.d("fingerprint", "auth failed");
}
}, null);
}
}
암호화된 FingerprintManager.CryptoObject
를 제공하기 위한 별도의 클래스를 만들어서 제공하였다.
KeyManager
public class KeyManager {
private static KeyManager instance;
private KeyManager() {
signal = new CancellationSignal();
};
public static KeyManager getInstance() {
if (instance == null)
{
instance = new KeyManager();
}
return instance;
}
private static final String KEY_NAME = "BIOAUTH_KEY";
private KeyStore keyStore;
private KeyGenerator keyGenerator;
@Getter @Setter
private Cipher cipher;
@Getter @Setter
private CancellationSignal signal;
/**
* 지문 인증을 사용하기 위한 키를 생성하는 함수
*/
public void generateKey() {
try {
// 안드로이드에서 기본적으로 제공하는 KeyStore 인듯하다 ( AndroidKeyStore )
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyStore.load(null);
keyGenerator.init(new KeyGenParameterSpec.Builder(
KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build());
keyGenerator.generateKey();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 키스토어 저장된 키를 암호화하는 함수
* @return Boolean
*/
public Boolean cipherInit() {
try {
cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
SecretKey key = (SecretKey) keyStore.getKey(KEY_NAME, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
API level 28 ( Android 9.0 ) 부터는 biometricPrompt 를 사용하여야 한다.
이 또한 생체 정보를 사용하기 위한 권한을 허용해주어야 한다.
AndroidManifest.xml
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
또한 , gradle 에 의존성을 추가해주어야 한다.
build.gradle(:app)
// 2022.09.22 기준 최신버전
dependencies {
implementation 'androidx.biometric:biometric:1.1.0'
}
다음은 BiometricPrompt 를 사용하여 생체 인증을 진행하는 코드이다.
BioAuthManager
// api 28 ( ANDROID 9.0 ) 이상은 biometricPrompt 사용
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
executor = ContextCompat.getMainExecutor(context);
biometricPrompt = new BiometricPrompt(activity, executor, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
Log.d("bioAuth", errString.toString());
}
@Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
Log.d("bioAuth", "auth success");
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Log.d("bioAuth", "auth failed");
}
});
promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle("지문 인증")
.setSubtitle("기기에 등록된 지문을 이용하여 지문을 인증해주세요.")
.setDescription("생체 인증 설명")
// BIOMETRIC_STRONG 은 안드로이드 11 에서 정의한 클래스 3 생체 인식을 사용하는 인증 - 암호화된 키 필요
// BIOMETRIC_WEAK 은 안드로이드 11 에서 정의한 클래스 2 생체 인식을 사용하는 인증 - 암호화된 키까지는 불필요
// DEVICE_CREDENTIAL 은 화면 잠금 사용자 인증 정보를 사용하는 인증 - 사용자의 PIN, 패턴 또는 비밀번호
.setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_WEAK)
.setConfirmationRequired(false) // 명시적인 사용자 작업 ( 생체 인식 전 한번더 체크 ) 없이 인증할건지 default : true
.setNegativeButtonText("취소")
.build();
keyManager.generateKey();
if (keyManager.cipherInit())
{
bioCryptoObject = new BiometricPrompt.CryptoObject(keyManager.getCipher());
biometricPrompt.authenticate(promptInfo, bioCryptoObject);
}
biometricPrompt.authenticate(promptInfo);
}
전체코드는 깃허브를 참조하여 주세요 .