Вітання!
Минуло досить багато часу, як з'явився Fingerprint API для Android, в мережі багато розрізнених семплів коду по його впровадженню і використанню, але на Хабре з якоїсь причини цю тему обходили стороною. На мій погляд, настав час виправити це непорозуміння.
Всіх зацікавлених прошу під кат.
Найкоротший лікнеп
Отже, що ж являє собою Fingerprint API?
API дозволяє користувачеві аутентифицироваться за допомогою свого відбитку, очевидно.
Для роботи з сенсором API пропонує нам FingerprintManager. досить простий в освоєнні.
Як його використовувати?
А ось це вже цікавіше.
Практично скрізь, де потрібно аутентифікація по паролю, можна прикрутити аутентифікацію за відбитком.
Уявіть собі додаток, що складається з LoginActivity і MainActivity. При запуску ми потрапляємо на екран логіна, вводимо пін-код, проходимо до даних. Але ми хочемо замінити вхід по пін-коду на вхід за відбитком.
До слова, повністю замінити не вийде, ми можемо лише позбавити користувача від ручного введення пін-коду, підставляючи раніше збережений пін-код (мається на увазі клієнт-серверний додаток, в якому потрібно відправити пароль сервера).
Приступимо.
Де сенсор?
Щоб почати отримувати профіт від нового API, насамперед потрібно додати permission в маніфесті:
Само собою, використовувати Fingerprint API можна тільки на пристроях, його підтримують: відповідно, це пристрої Android 6+ з сенсором.
Сумісність можна легко перевірити за допомогою методу:
FingerprintManagerCompat - це зручна обгортка для звичайного FingerprintManager'а. яка спрощує перевірку пристрою на сумісність, інкапсуліруя в собі перевірку версії API. В даному випадку, isHardwareDetected () поверне false. якщо API нижче 23.
Далі, нам потрібно зрозуміти, чи готовий сенсор до використання. Для цього визначимо enum станів:
І скористаємося методом:
Код досить тривіальний. Невелике непорозуміння може викликати момент, коли ми перевіряємо заблоковано чи пристрій. Нам потрібна ця перевірка, так як, хоч Android і не дозволяє додавати відбитки в незахищене пристрій, деякі виробники це обходять, тому підстрахуватися не завадить.
Різні стану можна використовувати для того, щоб дати користувачеві зрозуміти, що відбувається і направити його на шлях істинний.
підготовка
Отже, не зациклюючись на перевірці пін-коду на валідність, прикинемо наступну спрощену логіку дій:
- Користувач вводить пін-код, якщо SensorState.READY. то ми зберігаємо пін-код, запускаємо MainActivity.
- Рестарт додаток, якщо SensorState.READY. то зчитуємо відбиток, дістаємо пін-код, імітуємо його введення, запускаємо MainActivity.
Схема була б досить простий, якби не одне але: Гугл наполегливо рекомендує не зберігати приватні дані користувача у відкритому вигляді. Тому нам потрібен механізм шифровки і розшифровування для, відповідно, збереження і використання. Займемося цим.
Що нам потрібно для шифровки і розшифровування:
- Захищене сховище для ключів.
- Криптографічний ключ.
- шифрувальник
Для роботи з відбитками система надає нам свій кейстор - "AndroidKeyStore" і гарантує захист від несанкціонованого доступу. Скористаємося їм:
Має бути прийнято, зрозуміти і пробачити, що кейстор зберігає тільки криптографічні ключі. Паролі, пін і інші приватні дані там зберігати не можна.
На вибір у нас два варіанти ключів: симетричний ключ і пара з публічного і приватного ключа. З міркувань UX ми скористаємося парою. Це дозволить нам відокремити введення відбитка від шифрування пін-коду.
Ключі ми будемо діставати з кейстора, але спочатку потрібно їх туди покласти. Для створення ключа скористаємося генератором.
При ініціалізації ми вказуємо, в якій кейстор підуть згенеровані ключі і для якого алгоритму призначений цей ключ.
Сама ж генерація відбувається наступним чином:
Тут слід звернути увагу на два місця:
- KEY_ALIAS - це псевдонім ключа, за яким ми будемо висмикувати його з кейстора, звичайний psfs.
- .setUserAuthenticationRequired (true) - цей прапор вказує, що кожен раз, коли нам потрібно буде скористатися ключем, потрібно буде підтвердити себе, в нашому випадку - за допомогою відбитка.
Перевіряти наявність ключа будемо наступним чином:
шифрувальник
Кодуванням і дешифруванням в Java займається об'єкт Cipher. Ініціалізіруем його:
Пекельних мішанина в аргументі - це рядок трансформації, яка включає в себе алгоритм. режим змішування і доповнення.
Після того, як ми отримали Cipher. потрібно підготувати його до роботи.
При генерації ключа ми вказали, що будемо використовувати його тільки для шифровки і розшифровування. Відповідно, Cipher теж буде для цих цілей:
де initDecodeCipher () і initEncodeCiper () наступні:
Неважко помітити, що зашифровувати Cipher трохи складніше форматувати. Це косяк самого Гугла, суть якого в тому, що публічний ключ вимагає підтвердження користувача. Ми обходимо цю вимогу за допомогою зліпка ключа (милицю. Ага).
Момент з KeyPermanentlyInvalidatedException - якщо з якоїсь причини ключ не можна використовувати, вистрілює це виняток. Можливі причини - додавання нового відбитка до існуючого, зміна або повне видалення блокування. Тоді ключ більш не має сенсу зберігати, і ми його видаляємо.
Метод, який збирає весь ланцюжок підготовки:
Шифрування і розшифрування
Опишемо метод, який зашифровує рядок аргумент:
В результаті ми отримуємо Base64 -строку, яку можна спокійно зберігати в преференси додатки.
Для розшифровки ж використовуємо наступний метод:
Опа, на вхід він отримує не тільки зашифровану рядок, а й об'єкт Cipher.
Звідки він там узявся, стане ясно пізніше.
Чи не той палець
Для того щоб нарешті використати сенсор, потрібно скористатися методом FingerprintManagerCompat:
Хендлер і прапори нам зараз не потрібні, сигнал використовується, щоб скасувати режим зчитування відбитків (при згортанні додатка, наприклад), коллбекі повертають результат конкретного зчитування, а ось над кріптооб'ектом зупинимося детальніше.
CryptoObject в даному випадку використовується як обгортка для Cipher'a. Щоб його отримати, використовуємо метод:
Як я вже говорив, результати зчитування сенсора ми отримуємо в методах коллбека. Ось як вони виглядають:
У разі успішного розпізнавання ми отримуємо AuthenticationResult. з якого можемо дістати об'єкт Cipher c вже підтвердженим ключем:
Дякуємо за увагу.