ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android Fingerprint, BiometricPrompt를 이용한 생체인증 예제
    Android 외 개발 2019. 2. 15. 15:41

    Android 6.0 API 부터 지문 인증과 관련하여 지문 스캔을 사용하여 사용자를 인증하는 새로운 API를 제공합니다.


    https://developer.android.com/about/versions/marshmallow/android-6.0


    Id, Password 로 로그인 하는 방식 외에 생체 인증을 추가하기 위해서 Example Project 로 만들어 보았습니다.


    먼저, 프로젝트를 생성하고 AndroidManifest.xml에 권한을 먼저 추가하였습니다.


    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="newspring.fingerprinttest">

    <uses-permission android:name="android.permission.USE_FINGERPRINT" />


    권한을 추가하면 USE_FINGERPRINT is deprecated 경고가 발생합니다.

    Android developer 를 살펴보면 API Level 28 부터는 BiometricPrompt 를 사용하게 변경이 되었습니다.


    This class was deprecated in API level 28. See BiometricPrompt which shows a system-provided dialog upon starting authentication. In a world where devices may have different types of biometric authentication, it's much more realistic to have a system-provided authentication dialog since the method may vary by vendor/device.

     

    <uses-permission android:name="android.permission.USE_BIOMETRIC"/>


    새로운 permission은 추후 구현하기 위해서 추가하였습니다.


    Fingerprint 를 사용하기 위한 Class 를 생성하였습니다.

    Android 6.0 이후부터 사용하기 위해 RequiresApi을 추가하였고, 

    Fingerprint 에 관련된 부분을 모두 처리하기 위해서  AuthenticationCallback, OnCancellListener 를 사용하였습니다. 


    https://developer.android.com/reference/android/hardware/fingerprint/FingerprintManager.AuthenticationCallback


    @RequiresApi(Build.VERSION_CODES.M)
    public class FingerprintManagerCallback extends FingerprintManager.AuthenticationCallback
    implements CancellationSignal.OnCancelListener {
    public FingerprintManagerCallback(Context context) {
    fingerprintManager = context.getSystemService(FingerprintManager.class);
    }

    먼저, 사용시 가능 여부를 판단하기 위한 함수를 추가하였습니다.


    public boolean isFingerprintAuthAvailable() {
    return fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints();
    }

    fingerprint hardware 가 존재하는지 여부와 등록된 지문이 있는지 체크를 합니다.


    두번째로 지문 인증을 위한 암호키를 생성합니다.


    private KeyGenerator createkey(String keyName, boolean invalidatedByBiometricEnrollment) throws Exception {
    KeyGenerator mKeyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
    KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
    KeyProperties.PURPOSE_ENCRYPT |
    KeyProperties.PURPOSE_DECRYPT)
    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
    .setUserAuthenticationRequired(true)
    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    builder.setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment);
    }
    mKeyGenerator.init(builder.build());
    mKeyGenerator.generateKey();
    return mKeyGenerator;
    }

    AES 알고리즘을 사용한 암호키를 생성하였습니다.

    생성된 키로 데이터 암호화를 사용하기 위해 Cipher class 를 사용하였습니다.


    private Cipher cipher;
    public boolean init() {
    try {
    KeyGenerator keyGenerator = createkey(DEFAULT_KEY_NAME, true);

    cipher = Cipher.getInstance(
    KeyProperties.KEY_ALGORITHM_AES + "/"
    + KeyProperties.BLOCK_MODE_CBC + "/"
    + KeyProperties.ENCRYPTION_PADDING_PKCS7);

    cipher.init(Cipher.ENCRYPT_MODE, keyGenerator.generateKey());

    return true;
    } catch (Exception e){
    e.printStackTrace();
    }
    return false;
    }

    지금까지 만든 함수들로 지문인증을 시작하는 함수를 구현하였습니다.


    public void startListening() {
    if (!isFingerprintAuthAvailable()) {
    return;
    }
    if(init()){
    cancellationSignal = new CancellationSignal();
    cancellationSignal.setOnCancelListener(this);
    fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), cancellationSignal, 0 , this, null);
    }else{
    Trace.w("fingerprint init failure");
    }
    }

    CancellationSignal 의 경우 인증 취소를 위하여 추가하였습니다.

    public void stopListening() {
    if (cancellationSignal != null) {
    cancellationSignal.cancel();
    cancellationSignal = null;
    }
    }

    이제 Fingerprint 관련 함수의 구현이 끝났습니다.

    지문 인증 시도시 결과를 받을 수 있는 interface 를 추가하였습니다.


    public interface AuthenticationListener{
    void succedded();
    void failed();
    }

    AuthenticationListener authenticationListener;

    public void setAuthenticationListener(AuthenticationListener authenticationListener) {
    this.authenticationListener = authenticationListener;
    }

    해당 Listener 를 등록하여 UI 를 변경할 수 있도록 처리하였습니다.

    @Override
    public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
    super.onAuthenticationSucceeded(result);

    authenticationListener.succedded();

    }

    @Override
    public void onAuthenticationFailed() {
    super.onAuthenticationFailed();

    authenticationListener.failed();
    }


    버튼을 누를시 startListening() 함수를 호출하도록 구현하였습니다.

    이제 지문이 등록된 디바이스에서 현재까지 구현된 부분을 처리한 결과 화면입니다.

    (UI 부분 소스는 구현하시는 화면에 따라 다르므로 적지 않았습니다.)



    정상적으로 onAuthenticationSucceeded 가 호출되는 것을 확인 할 수 있습니다.


    위의 구현상으로 테스트 해 본 결과 두가지 부족한 점이 있습니다.


    1. Galaxy S5 (Android 6.0) 등 특정기기에서 동작하지 않음.


    https://stackoverflow.com/questions/40802019/android-fingerprint-ishardwaredetected-not-working


    현재 검색 결과 Samsung Pass SDK 는 Deprecated 상태로 받을 수 없습니다.


    2. Android API Level 28 에서는 BiometricPrompt 함수로 구현


    위의 Class 와 동일한 구조로 생성하였습니다.


    @RequiresApi(Build.VERSION_CODES.P)
    public class BiometricPromptCallback extends BiometricPrompt.AuthenticationCallback
    implements CancellationSignal.OnCancelListener {

    생성자를 보면 아시겠지만, 별도의 팝업창이 생성되며 타이틀, 서브타이틀, 설명, 취소버튼을 등록할 수 있게 되어 있습니다.

    public BiometricPromptCallback(Context context) {
    Trace.d("BiometricPromptCallback");
    this.context = context;
    mBiometricPrompt = new BiometricPrompt.Builder(context)
    .setDescription("Description")
    .setTitle("Title")
    .setSubtitle("Subtitle")
    .setNegativeButton("Cancel", context.getMainExecutor(), new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialogInterface, int i) {

    }
    })
    .build();
    }

    사용시 가능 여부를 판단하기 위한 함수를 추가하기 위해 찾아봤지만, 현재까지는 찾지 못했습니다.

    (추가적으로 찾게되면 업데이트 하도록 하겠습니다)


    암호키 생성 부분으로 EC 알고리즘을 사용하였습니다.

    private KeyPairGenerator createkey(String keyName, boolean invalidatedByBiometricEnrollment) throws Exception {
    Trace.d("createkey");
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");

    KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
    KeyProperties.PURPOSE_SIGN)
    .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
    .setDigests(KeyProperties.DIGEST_SHA256,
    KeyProperties.DIGEST_SHA384,
    KeyProperties.DIGEST_SHA512)
    .setUserAuthenticationRequired(true)
    .setInvalidatedByBiometricEnrollment(invalidatedByBiometricEnrollment);

    keyPairGenerator.initialize(builder.build());

    return keyPairGenerator;
    }


    public boolean init() {
    Trace.d("init");
    try {
    KeyPairGenerator keyPairGenerator = createkey(DEFAULT_KEY_NAME, true);
    signature = Signature.getInstance("SHA256withECDSA");
    signature.initSign(keyPairGenerator.generateKeyPair().getPrivate());

    return true;
    } catch (Exception e){
    e.printStackTrace();
    }
    return false;
    }

    암호화를 위한 signature 생성 후 시작 함수입니다.

    public void startListening() {
    Trace.d("startListening");
    if(init()){
    cancellationSignal = new CancellationSignal();
    cancellationSignal.setOnCancelListener(this);
    mBiometricPrompt.authenticate(new BiometricPrompt.CryptoObject(signature),cancellationSignal, context.getMainExecutor(),this);
    }else{
    Trace.w("init failure");
    authenticationListener.failed();
    }
    }

    실행하는 부분에서 분기로 처리하였습니다.

    if(Build.VERSION.SDK_INT  > Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.P){
    fingerprintManagerCallback.startListening();
    }else if(Build.VERSION.SDK_INT == Build.VERSION_CODES.P){
    biometricPromptCallback.startListening();
    }


    완료 화면을 찍을려고 했지만 보안으로 스크린샷이 찍히지 않아서 첨부를 하지 못했습니다.


    갤럭시S9 으로 테스트 결과,홍채, 지문의 경우 단독 등록된 경우 동작하나 얼굴 인식만 단독으로 등록 시 동작하지 않습니다.

    얼굴 인식을 사용할 경우 홍채나 지문이 등록된 상태에서 사용가능합니다.


    추가적으로 필요한 내용이 빠진 경우 업데이트 하도록 하겠습니다.





Designed by Tistory.