Аутентификация с помощью отпечатков пальцев в 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();
}
А наглядно?
Конечно. Пример приложения и его исходный код вы найдете на канале.