Android fingerprint api криє аутентифікацію за відбитком

Вітання!
Минуло досить багато часу, як з'явився Fingerprint API для Android, в мережі багато розрізнених семплів коду по його впровадженню і використанню, але на Хабре з якоїсь причини цю тему обходили стороною. На мій погляд, настав час виправити це непорозуміння.
Всіх зацікавлених прошу під кат.

Android fingerprint api криє аутентифікацію за відбитком

Найкоротший лікнеп

Отже, що ж являє собою 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.

Схема була б досить простий, якби не одне але: Гугл наполегливо рекомендує не зберігати приватні дані користувача у відкритому вигляді. Тому нам потрібен механізм шифровки і розшифровування для, відповідно, збереження і використання. Займемося цим.

Що нам потрібно для шифровки і розшифровування:

  1. Захищене сховище для ключів.
  2. Криптографічний ключ.
  3. шифрувальник

Для роботи з відбитками система надає нам свій кейстор - "AndroidKeyStore" і гарантує захист від несанкціонованого доступу. Скористаємося їм:

Має бути прийнято, зрозуміти і пробачити, що кейстор зберігає тільки криптографічні ключі. Паролі, пін і інші приватні дані там зберігати не можна.

На вибір у нас два варіанти ключів: симетричний ключ і пара з публічного і приватного ключа. З міркувань UX ми скористаємося парою. Це дозволить нам відокремити введення відбитка від шифрування пін-коду.

Ключі ми будемо діставати з кейстора, але спочатку потрібно їх туди покласти. Для створення ключа скористаємося генератором.

При ініціалізації ми вказуємо, в якій кейстор підуть згенеровані ключі і для якого алгоритму призначений цей ключ.
Сама ж генерація відбувається наступним чином:

Тут слід звернути увагу на два місця:

  • KEY_ALIAS - це псевдонім ключа, за яким ми будемо висмикувати його з кейстора, звичайний psfs.
  • .setUserAuthenticationRequired (true) - цей прапор вказує, що кожен раз, коли нам потрібно буде скористатися ключем, потрібно буде підтвердити себе, в нашому випадку - за допомогою відбитка.

Перевіряти наявність ключа будемо наступним чином:

шифрувальник

Кодуванням і дешифруванням в Java займається об'єкт Cipher. Ініціалізіруем його:

Пекельних мішанина в аргументі - це рядок трансформації, яка включає в себе алгоритм. режим змішування і доповнення.

Після того, як ми отримали Cipher. потрібно підготувати його до роботи.
При генерації ключа ми вказали, що будемо використовувати його тільки для шифровки і розшифровування. Відповідно, Cipher теж буде для цих цілей:

де initDecodeCipher () і initEncodeCiper () наступні:

Неважко помітити, що зашифровувати Cipher трохи складніше форматувати. Це косяк самого Гугла, суть якого в тому, що публічний ключ вимагає підтвердження користувача. Ми обходимо цю вимогу за допомогою зліпка ключа (милицю. Ага).

Момент з KeyPermanentlyInvalidatedException - якщо з якоїсь причини ключ не можна використовувати, вистрілює це виняток. Можливі причини - додавання нового відбитка до існуючого, зміна або повне видалення блокування. Тоді ключ більш не має сенсу зберігати, і ми його видаляємо.

Метод, який збирає весь ланцюжок підготовки:

Шифрування і розшифрування

Опишемо метод, який зашифровує рядок аргумент:

В результаті ми отримуємо Base64 -строку, яку можна спокійно зберігати в преференси додатки.

Для розшифровки ж використовуємо наступний метод:

Опа, на вхід він отримує не тільки зашифровану рядок, а й об'єкт Cipher.
Звідки він там узявся, стане ясно пізніше.

Чи не той палець

Для того щоб нарешті використати сенсор, потрібно скористатися методом FingerprintManagerCompat:

Хендлер і прапори нам зараз не потрібні, сигнал використовується, щоб скасувати режим зчитування відбитків (при згортанні додатка, наприклад), коллбекі повертають результат конкретного зчитування, а ось над кріптооб'ектом зупинимося детальніше.
CryptoObject в даному випадку використовується як обгортка для Cipher'a. Щоб його отримати, використовуємо метод:

Як я вже говорив, результати зчитування сенсора ми отримуємо в методах коллбека. Ось як вони виглядають:

У разі успішного розпізнавання ми отримуємо AuthenticationResult. з якого можемо дістати об'єкт Cipher c вже підтвердженим ключем:

Дякуємо за увагу.