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

Android Fingerprint API: криє аутентифікацію за відбитком +19

  • 20.12.16 8:14 •
  • nullpex •
  • # 317706 •
  • Хабрахабр •
  • Tutorial •
  • 0 •
  • 4600

- такий же як Forbes, тільки краще.

Привіт, Хабр! Минуло досить багато часу, як з'явився 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. Щоб його отримати, використовуємо метод:


Як видно з коду, кріптооб'ект створюється з расшіфровивающегоCipher. Якщо цей Cipher прямо зараз відправити в метод decode (). то вилетить виняток, який повідомляє про те, що ми намагаємося використовувати ключ без підтвердження.

Якщо getCryptoObject () повернув null. то це означає, що при ініціалізації Chiper 'а стався KeyPermanentlyInvalidatedException. Тут вже нічого не поробиш, крім як дати користувачеві знати, що вхід за відбитком недоступний і йому доведеться заново ввести пін-код.

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


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

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