Аутентификация с помощью отпечатков пальцев в Android
Мой канал: @VolfsChannel
Биометрия... Ваши отпечатки пальцев в смартфонах... Ещё 5-7 лет назад данный вид аутентификации казался баловством. Я что, храню коды запуска ракет? Зачем мне этот сканер? Да графический ключ удобнее и быстрее! Знакомые фразы? Может быть вы и сами так думали, но сейчас глупо отрицать - такого рода биометрическая аутентификация является лидирующей на портативных устройствах. Сказывается дешевизна ($3-5 за модуль сканера) и относительная безопасность.
Сканеры отпечатков пальцев в смартфонах стали появляться ещё во времена Android 4.4. Только тогда, из-за отсутствия API, данный сканер мог работать только в приложениях от производителя, и был скорее забавной игрушкой, чем методом авторизации. Всё изменилось с релизом Android 6.0. Было представлено Fingerprint Manager API, с помощью которого разработчики могут реализовать авторизацию по фингерпринту.
Почитав пару статей на Хабре, я так ничего и не понял. Что? Как? Зачем? Путаные инструкции и нуль конкретики.
Сегодня я хочу разобраться в API, а поможет мне в этом специальный класс-хелпер который является модификацией одноименного класса из Magisk Manager.
Подготовка
Для использования сканера отпечатков ваше приложение должно удовлетворять следующим требованиям:
- Целевая версия SDK 23 или выше
- Разрешение <uses-permission android:name="android.permission.USE_FINGERPRINT" /> в манифесте
Реализация
Желательно завести объект-наследник класса android.app.Application, чтобы без труда получить контекст.
import android.app.Application; public class Girl extends Application { private static Girl instance; public Girl(){ instance = this; } public static Girl get() { return instance; } @Override public void onCreate() { // TODO: Implement this method super.onCreate(); } }
Зарегистрируйте класс в файле AndroidManifest.xml
<application android:name=".Girl" ... ... ...> ... ... ... </application>
Создайте класс FingerprintHelper.java, который и будет отвечать за работу со сканером:
import android.annotation.TargetApi; import android.app.KeyguardManager; import android.hardware.fingerprint.FingerprintManager; import android.os.Build; import android.os.CancellationSignal; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyPermanentlyInvalidatedException; import android.security.keystore.KeyProperties; import java.security.KeyStore; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; @TargetApi(Build.VERSION_CODES.M) public abstract class FingerprintHelper { private FingerprintManager manager; private Cipher cipher; private CancellationSignal cancel; private String keystoreTag = "GirlTag"; // Проверяем наличие сканера public static boolean canUseFingerprint() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false; Girl girl = Girl.get(); KeyguardManager km = girl.getSystemService(KeyguardManager.class); FingerprintManager fm = girl.getSystemService(FingerprintManager.class); return km.isKeyguardSecure() && fm != null && fm.isHardwareDetected() && fm.hasEnrolledFingerprints(); } // Мы не можем просто так взять и использовать отпечаток без пароля // Нам необходимо создать хранилище ключей, по которому будет // будет производиться авторизация protected FingerprintHelper() throws Exception { // Получение контекста Girl girl = Girl.get(); // Создание экземпляра хранилища ключей KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); // Стучимся к сервису распознавания отпечатков manager = girl.getSystemService(FingerprintManager.class); // Создание экземпляра шифровальщика cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); // Загрузка хранилища ключей keyStore.load(null); // Получаем секретный ключ из загруженного хранилища // если отсутствует - создаём SecretKey key = (SecretKey) keyStore.getKey(keystoreTag, null); if (key == null) { key = generateKey(); } // Расшифровка try { cipher.init(Cipher.ENCRYPT_MODE, key); } catch (KeyPermanentlyInvalidatedException e) { // Фикс бага Android Marshmallow key = generateKey(); cipher.init(Cipher.ENCRYPT_MODE, key); } } // Если в процессе авторизации произошла ошибка public abstract void onAuthenticationError(int errorCode, CharSequence errString); // Получение статуса авторизации public abstract void onAuthenticationHelp(int helpCode, CharSequence helpString); // Успешная авторизация public abstract void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result); // Ошибка public abstract void onAuthenticationFailed(); // Запуск авторизации public void startAuth() { cancel = new CancellationSignal(); FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher); manager.authenticate(cryptoObject, cancel, 0, new FingerprintManager.AuthenticationCallback() { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { FingerprintHelper.this.onAuthenticationError(errorCode, errString); } @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { FingerprintHelper.this.onAuthenticationHelp(helpCode, helpString); } @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { FingerprintHelper.this.onAuthenticationSucceeded(result); } @Override public void onAuthenticationFailed() { FingerprintHelper.this.onAuthenticationFailed(); } }, null); } // Если пользователь отменил операцию public void cancel() { if (cancel != null) cancel.cancel(); } // Создание ключа авторизации private SecretKey generateKey() throws Exception { KeyGenerator keygen = KeyGenerator .getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder( keystoreTag, 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(false); } keygen.init(builder.build()); return keygen.generateKey(); } }
Я добавил комментарии к коду, так что вопросов возникнуть не должно.
Пример реализации в Activity:
private FingerprintHelper fingerprint; private TextView warning; private void init() { if (FingerprintHelper.canUseFingerprint()) { try { fingerprint = new FingerprintHelper() { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { warning.setText(errString); } @Override public void onAuthenticationHelp(int helpCode, CharSequence helpString) { warning.setText(helpString); } @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) { warning.setText("Success!"); } @Override public void onAuthenticationFailed() { warning.setText("Failed!"); } }; fingerprint.startAuth(); } catch (Exception e) { e.printStackTrace(); } } } @Override public void finish() { if (fingerprint != null) fingerprint.cancel(); super.finish(); }
А наглядно?
Конечно. Пример приложения и его исходный код вы найдете на канале.