Salah satu metode untuk melindungi informasi sensitif atau konten premium dalam aplikasi Anda adalah dengan meminta autentikasi biometrik, seperti menggunakan pengenalan wajah atau pengenalan sidik jari. Panduan ini menjelaskan cara mendukung alur login biometrik dalam aplikasi Anda.
Sebagai aturan umum, Anda harus menggunakan Pengelola Kredensial untuk login awal di perangkat. Otorisasi ulang berikutnya dapat dilakukan dengan Biometric Prompt, atau Pengelola Kredensial. Keuntungan menggunakan Perintah Biometrik adalah menawarkan lebih banyak opsi penyesuaian, sedangkan Pengelola Kredensial menawarkan satu implementasi di kedua alur.
Mendeklarasikan jenis autentikasi yang didukung aplikasi Anda
Untuk menentukan jenis autentikasi yang didukung aplikasi Anda, gunakan
antarmuka
BiometricManager.Authenticators
. Sistem ini memungkinkan Anda mendeklarasikan jenis
autentikasi berikut:
BIOMETRIC_STRONG
- Autentikasi menggunakan biometrik Class 3, seperti yang didefinisikan pada halaman Definisi kompatibilitas Android.
BIOMETRIC_WEAK
- Autentikasi menggunakan biometrik Class 2, seperti yang didefinisikan pada halaman Definisi kompatibilitas Android.
DEVICE_CREDENTIAL
- Autentikasi menggunakan kredensial kunci layar, yaitu PIN, pola, atau sandi pengguna.
Untuk mulai menggunakan pengautentikasi, pengguna harus membuat PIN, pola, atau sandi. Jika belum memilikinya, alur pendaftaran biometrik akan meminta pengguna membuatnya.
Untuk menentukan jenis autentikasi biometrik yang diterima aplikasi Anda, teruskan salah satu jenis
autentikasi atau kombinasi bitwise dari beberapa jenis ke dalam metode
setAllowedAuthenticators()
.
Cuplikan kode berikut menunjukkan cara mendukung autentikasi menggunakan
biometrik Class 3 atau kredensial kunci layar.
Kotlin
// Lets the user authenticate using either a Class 3 biometric or // their lock screen credential (PIN, pattern, or password). promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setAllowedAuthenticators(BIOMETRIC_STRONG or DEVICE_CREDENTIAL) .build()
Java
// Lets user authenticate using either a Class 3 biometric or // their lock screen credential (PIN, pattern, or password). promptInfo = new BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setAllowedAuthenticators(BIOMETRIC_STRONG | DEVICE_CREDENTIAL) .build();
Kombinasi jenis pengautentikasi berikut tidak didukung di
Android 10 (level API 29) dan versi yang lebih lama: DEVICE_CREDENTIAL
dan
BIOMETRIC_STRONG | DEVICE_CREDENTIAL
. Untuk memeriksa keberadaan PIN,
pola, atau sandi di Android 10 dan versi yang lebih lama, gunakan
metode
KeyguardManager.isDeviceSecure()
.
Memeriksa ketersediaan autentikasi biometrik
Setelah Anda memutuskan elemen autentikasi yang didukung aplikasi Anda, periksa apakah
elemen tersebut tersedia. Untuk melakukannya, teruskan
kombinasi bitwise yang sama dari jenis yang Anda deklarasikan menggunakan
metode setAllowedAuthenticators()
ke dalam
metode canAuthenticate()
.
Jika perlu, panggil tindakan intent
ACTION_BIOMETRIC_ENROLL
.
Dalam tambahan intent, sediakan kumpulan pengautentikasi yang diterima aplikasi
Anda. Intent ini meminta pengguna mendaftarkan kredensial untuk
pengautentikasi yang diterima aplikasi Anda.
Kotlin
val biometricManager = BiometricManager.from(this) when (biometricManager.canAuthenticate(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) { BiometricManager.BIOMETRIC_SUCCESS -> Log.d("MY_APP_TAG", "App can authenticate using biometrics.") BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> Log.e("MY_APP_TAG", "No biometric features available on this device.") BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> Log.e("MY_APP_TAG", "Biometric features are currently unavailable.") BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> { // Prompts the user to create credentials that your app accepts. val enrollIntent = Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply { putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, BIOMETRIC_STRONG or DEVICE_CREDENTIAL) } startActivityForResult(enrollIntent, REQUEST_CODE) } }
Java
BiometricManager biometricManager = BiometricManager.from(this); switch (biometricManager.canAuthenticate(BIOMETRIC_STRONG | DEVICE_CREDENTIAL)) { case BiometricManager.BIOMETRIC_SUCCESS: Log.d("MY_APP_TAG", "App can authenticate using biometrics."); break; case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE: Log.e("MY_APP_TAG", "No biometric features available on this device."); break; case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE: Log.e("MY_APP_TAG", "Biometric features are currently unavailable."); break; case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED: // Prompts the user to create credentials that your app accepts. final Intent enrollIntent = new Intent(Settings.ACTION_BIOMETRIC_ENROLL); enrollIntent.putExtra(Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, BIOMETRIC_STRONG | DEVICE_CREDENTIAL); startActivityForResult(enrollIntent, REQUEST_CODE); break; }
Menentukan cara pengguna diautentikasi
Setelah pengguna melakukan autentikasi, Anda dapat memeriksa apakah pengguna tersebut diautentikasi menggunakan
kredensial perangkat atau kredensial biometrik dengan memanggil
getAuthenticationType()
.
Menampilkan permintaan login
Untuk menampilkan permintaan sistem yang meminta pengguna untuk mengautentikasi menggunakan kredensial biometrik, gunakan Library biometrik. Dialog bawaan sistem ini konsisten di seluruh aplikasi yang menggunakannya, sehingga memberikan pengalaman pengguna yang tepercaya. Salah satu contoh dialognya ditunjukkan pada gambar 1.
Untuk menambahkan autentikasi biometrik ke aplikasi Anda menggunakan Library biometrik, lakukan langkah-langkah berikut:
Dalam file
build.gradle
modul aplikasi, tambahkan dependensi pada libraryandroidx.biometric
.Pada aktivitas atau fragmen yang menghosting dialog login biometrik, tampilkan dialog menggunakan logika yang ditunjukkan dalam cuplikan kode berikut:
Kotlin
private lateinit var executor: Executor private lateinit var biometricPrompt: BiometricPrompt private lateinit var promptInfo: BiometricPrompt.PromptInfo override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_login) executor = ContextCompat.getMainExecutor(this) biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) Toast.makeText(applicationContext, "Authentication error: $errString", Toast.LENGTH_SHORT) .show() } override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) Toast.makeText(applicationContext, "Authentication succeeded!", Toast.LENGTH_SHORT) .show() } override fun onAuthenticationFailed() { super.onAuthenticationFailed() Toast.makeText(applicationContext, "Authentication failed", Toast.LENGTH_SHORT) .show() } }) promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .build() // Prompt appears when user clicks "Log in". // Consider integrating with the keystore to unlock cryptographic operations, // if needed by your app. val biometricLoginButton = findViewById<Button>(R.id.biometric_login) biometricLoginButton.setOnClickListener { biometricPrompt.authenticate(promptInfo) } }
Java
private Executor executor; private BiometricPrompt biometricPrompt; private BiometricPrompt.PromptInfo promptInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); executor = ContextCompat.getMainExecutor(this); biometricPrompt = new BiometricPrompt(MainActivity.this, executor, new BiometricPrompt.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { super.onAuthenticationError(errorCode, errString); Toast.makeText(getApplicationContext(), "Authentication error: " + errString, Toast.LENGTH_SHORT) .show(); } @Override public void onAuthenticationSucceeded( @NonNull BiometricPrompt.AuthenticationResult result) { super.onAuthenticationSucceeded(result); Toast.makeText(getApplicationContext(), "Authentication succeeded!", Toast.LENGTH_SHORT).show(); } @Override public void onAuthenticationFailed() { super.onAuthenticationFailed(); Toast.makeText(getApplicationContext(), "Authentication failed", Toast.LENGTH_SHORT) .show(); } }); promptInfo = new BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .build(); // Prompt appears when user clicks "Log in". // Consider integrating with the keystore to unlock cryptographic operations, // if needed by your app. Button biometricLoginButton = findViewById(R.id.biometric_login); biometricLoginButton.setOnClickListener(view -> { biometricPrompt.authenticate(promptInfo); }); }
Menggunakan solusi kriptografi yang bergantung pada autentikasi
Untuk melindungi informasi sensitif dalam aplikasi lebih lanjut, Anda dapat memasukkan
kriptografi ke dalam alur kerja autentikasi biometrik Anda menggunakan instance
CryptoObject
.
Framework ini mendukung objek kriptografi berikut:
Signature
,
Cipher
, dan
Mac
.
Setelah pengguna berhasil mengautentikasi menggunakan perintah biometrik, aplikasi Anda dapat
melakukan operasi kriptografi. Misalnya, jika Anda mengautentikasi menggunakan objek
Cipher
, aplikasi Anda nantinya dapat melakukan enkripsi dan dekripsi menggunakan objek
SecretKey
.
Bagian berikut membahas contoh penggunaan objek Cipher
dan objek
SecretKey
untuk mengenkripsi data. Setiap contoh menggunakan
metode berikut:
Kotlin
private fun generateSecretKey(keyGenParameterSpec: KeyGenParameterSpec) { val keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") keyGenerator.init(keyGenParameterSpec) keyGenerator.generateKey() } private fun getSecretKey(): SecretKey { val keyStore = KeyStore.getInstance("AndroidKeyStore") // Before the keystore can be accessed, it must be loaded. keyStore.load(null) return keyStore.getKey(KEY_NAME, null) as SecretKey } private fun getCipher(): Cipher { return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7) }
Java
private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) { KeyGenerator keyGenerator = KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); keyGenerator.init(keyGenParameterSpec); keyGenerator.generateKey(); } private SecretKey getSecretKey() { KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); // Before the keystore can be accessed, it must be loaded. keyStore.load(null); return ((SecretKey)keyStore.getKey(KEY_NAME, null)); } private Cipher getCipher() { return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); }
Mengautentikasi hanya menggunakan kredensial biometrik
Jika aplikasi Anda menggunakan kunci rahasia yang memerlukan kredensial biometrik untuk membuka kunci, pengguna harus selalu mengautentikasi kredensial biometriknya sebelum aplikasi Anda mengakses kunci tersebut.
Untuk mengenkripsi informasi sensitif hanya setelah pengguna mengautentikasi menggunakan kredensial biometrik, lakukan langkah-langkah berikut:
Buat kunci yang menggunakan konfigurasi
KeyGenParameterSpec
berikut:Kotlin
generateSecretKey(KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) // Invalidate the keys if the user has registered a new biometric // credential, such as a new fingerprint. Can call this method only // on Android 7.0 (API level 24) or higher. The variable // "invalidatedByBiometricEnrollment" is true by default. .setInvalidatedByBiometricEnrollment(true) .build())
Java
generateSecretKey(new KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) // Invalidate the keys if the user has registered a new biometric // credential, such as a new fingerprint. Can call this method only // on Android 7.0 (API level 24) or higher. The variable // "invalidatedByBiometricEnrollment" is true by default. .setInvalidatedByBiometricEnrollment(true) .build());
Mulai alur kerja autentikasi biometrik yang menggabungkan cipher:
Kotlin
biometricLoginButton.setOnClickListener { // Exceptions are unhandled within this snippet. val cipher = getCipher() val secretKey = getSecretKey() cipher.init(Cipher.ENCRYPT_MODE, secretKey) biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher)) }
Java
biometricLoginButton.setOnClickListener(view -> { // Exceptions are unhandled within this snippet. Cipher cipher = getCipher(); SecretKey secretKey = getSecretKey(); cipher.init(Cipher.ENCRYPT_MODE, secretKey); biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher)); });
Dalam callback autentikasi biometrik, gunakan kunci rahasia untuk mengenkripsi informasi sensitif:
Kotlin
override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { val encryptedInfo: ByteArray = result.cryptoObject.cipher?.doFinal( // plaintext-string text is whatever data the developer would like // to encrypt. It happens to be plain-text in this example, but it // can be anything plaintext-string.toByteArray(Charset.defaultCharset()) ) Log.d("MY_APP_TAG", "Encrypted information: " + Arrays.toString(encryptedInfo)) }
Java
@Override public void onAuthenticationSucceeded( @NonNull BiometricPrompt.AuthenticationResult result) { // NullPointerException is unhandled; use Objects.requireNonNull(). byte[] encryptedInfo = result.getCryptoObject().getCipher().doFinal( // plaintext-string text is whatever data the developer would like // to encrypt. It happens to be plain-text in this example, but it // can be anything plaintext-string.getBytes(Charset.defaultCharset())); Log.d("MY_APP_TAG", "Encrypted information: " + Arrays.toString(encryptedInfo)); }
Mengautentikasi menggunakan kredensial biometrik atau layar kunci
Anda dapat menggunakan kunci rahasia yang memungkinkan autentikasi menggunakan kredensial biometrik atau kredensial layar kunci (PIN, pola, atau sandi). Saat mengonfigurasi kunci ini, tentukan jangka waktu validitas. Selama jangka waktu ini, aplikasi Anda dapat melakukan beberapa operasi kriptografi tanpa perlu autentikasi ulang oleh pengguna.
Untuk mengenkripsi informasi sensitif setelah pengguna mengautentikasi menggunakan kredensial biometrik atau layar kunci, lakukan langkah-langkah berikut:
Buat kunci yang menggunakan konfigurasi
KeyGenParameterSpec
berikut:Kotlin
generateSecretKey(KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS, ALLOWED_AUTHENTICATORS) .build())
Java
generateSecretKey(new KeyGenParameterSpec.Builder( KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .setUserAuthenticationRequired(true) .setUserAuthenticationParameters(VALIDITY_DURATION_SECONDS, ALLOWED_AUTHENTICATORS) .build());
Dalam jangka waktu
VALIDITY_DURATION_SECONDS
setelah pengguna melakukan autentikasi, enkripsi informasi sensitif:Kotlin
private fun encryptSecretInformation() { // Exceptions are unhandled for getCipher() and getSecretKey(). val cipher = getCipher() val secretKey = getSecretKey() try { cipher.init(Cipher.ENCRYPT_MODE, secretKey) val encryptedInfo: ByteArray = cipher.doFinal( // plaintext-string text is whatever data the developer would // like to encrypt. It happens to be plain-text in this example, // but it can be anything plaintext-string.toByteArray(Charset.defaultCharset())) Log.d("MY_APP_TAG", "Encrypted information: " + Arrays.toString(encryptedInfo)) } catch (e: InvalidKeyException) { Log.e("MY_APP_TAG", "Key is invalid.") } catch (e: UserNotAuthenticatedException) { Log.d("MY_APP_TAG", "The key's validity timed out.") biometricPrompt.authenticate(promptInfo) }
Java
private void encryptSecretInformation() { // Exceptions are unhandled for getCipher() and getSecretKey(). Cipher cipher = getCipher(); SecretKey secretKey = getSecretKey(); try { // NullPointerException is unhandled; use Objects.requireNonNull(). ciper.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedInfo = cipher.doFinal( // plaintext-string text is whatever data the developer would // like to encrypt. It happens to be plain-text in this example, // but it can be anything plaintext-string.getBytes(Charset.defaultCharset())); } catch (InvalidKeyException e) { Log.e("MY_APP_TAG", "Key is invalid."); } catch (UserNotAuthenticatedException e) { Log.d("MY_APP_TAG", "The key's validity timed out."); biometricPrompt.authenticate(promptInfo); } }
Mengautentikasi menggunakan kunci autentikasi per penggunaan
Anda dapat memberikan dukungan untuk kunci autentikasi per penggunaan dalam instance
BiometricPrompt
. Kunci tersebut
mengharuskan pengguna untuk memberikan kredensial biometrik atau kredensial
perangkat setiap kali aplikasi Anda perlu mengakses data yang dilindungi oleh
kunci tersebut. Kunci autentikasi per penggunaan dapat berguna untuk transaksi bernilai tinggi, seperti
melakukan pembayaran berjumlah besar atau memperbarui catatan kesehatan seseorang.
Untuk mengaitkan objek BiometricPrompt
dengan kunci autentikasi per penggunaan, tambahkan kode
seperti contoh berikut:
Kotlin
val authPerOpKeyGenParameterSpec = KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose) // Accept either a biometric credential or a device credential. // To accept only one type of credential, include only that type as the // second argument. .setUserAuthenticationParameters(0 /* duration */, KeyProperties.AUTH_BIOMETRIC_STRONG or KeyProperties.AUTH_DEVICE_CREDENTIAL) .build()
Java
KeyGenParameterSpec authPerOpKeyGenParameterSpec = new KeyGenParameterSpec.Builder("myKeystoreAlias", key-purpose) // Accept either a biometric credential or a device credential. // To accept only one type of credential, include only that type as the // second argument. .setUserAuthenticationParameters(0 /* duration */, KeyProperties.AUTH_BIOMETRIC_STRONG | KeyProperties.AUTH_DEVICE_CREDENTIAL) .build();
Mengautentikasi tanpa tindakan eksplisit dari pengguna
Secara default, sistem mengharuskan pengguna melakukan tindakan tertentu, seperti menekan tombol, setelah kredensial biometriknya diterima. Sebaiknya gunakan konfigurasi ini jika aplikasi Anda menampilkan dialog untuk mengonfirmasi tindakan yang sensitif atau berisiko tinggi, seperti melakukan pembelian.
Namun, jika aplikasi Anda menampilkan dialog autentikasi biometrik untuk tindakan berisiko rendah,
Anda dapat memberikan petunjuk kepada sistem bahwa pengguna tidak perlu
mengonfirmasi autentikasi. Petunjuk ini memungkinkan pengguna melihat konten dalam aplikasi Anda
lebih cepat setelah mengautentikasi ulang menggunakan modalitas pasif, seperti pengenalan berdasarkan
wajah atau iris. Untuk memberikan petunjuk ini, teruskan false
ke metode
setConfirmationRequired()
.
Gambar 2 menunjukkan dua versi dari dialog yang sama. Versi pertama memerlukan tindakan eksplisit dari pengguna; versi lainnya tidak.
Cuplikan kode berikut menunjukkan cara menampilkan dialog yang tidak memerlukan tindakan eksplisit dari pengguna untuk menyelesaikan proses autentikasi:
Kotlin
// Lets the user authenticate without performing an action, such as pressing a // button, after their biometric credential is accepted. promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .setConfirmationRequired(false) .build()
Java
// Lets the user authenticate without performing an action, such as pressing a // button, after their biometric credential is accepted. promptInfo = new BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Use account password") .setConfirmationRequired(false) .build();
Mengizinkan fallback ke kredensial non-biometrik
Jika Anda ingin aplikasi mengizinkan autentikasi menggunakan kredensial biometrik atau
perangkat, Anda dapat mendeklarasikan bahwa aplikasi mendukung kredensial
perangkat dengan menyertakan
DEVICE_CREDENTIAL
dalam kumpulan nilai yang Anda teruskan ke
setAllowedAuthenticators()
.
Jika aplikasi Anda saat ini menggunakan
createConfirmDeviceCredentialIntent()
atau setDeviceCredentialAllowed()
untuk memberikan kemampuan ini, beralihlah untuk menggunakan setAllowedAuthenticators()
.
Referensi lainnya
Untuk mempelajari autentikasi biometrik di Android lebih lanjut, lihat referensi berikut.