Для защиты от несанкционированного доступа к своему Nexus 4 я использую стандартную блокировку экрана с паролем. Но пароль приходится постоянно вводить вручную, а это немного напрягает. Блокировка с помощью графического ключа меня не впечатлила, так же как и PIN-код (слишком мало возможных комбинаций). Хотелось сильную защиту, с сохранением скорости разблокировки. Именно по этой причине я решил присмотреться к технологии NFC.
Готовые решения
Единственным готовым решением, которое я нашёл, для реализации разблокировки девайса с помощью метки, является программа NFCSecure. Она платная, а бесплатной версии нет. Посмотрев видео на его странице в Google Play, я сразу для себя решил, что ЭТО не стоит своих денег. Всё реализовано простейшим способом:
- Пользователь включает экран устройства и видит стандартный блокировщик экрана.
- Он его разблокирует.
- Появляется Activity NFCSecure, которое делает вид, что надёжно блокирует доступ к устройству (при нажатии на иконку разблокировки страница почему-то мигает, что не вселяет доверия).
- Прикладываем метку и окно закрывается.
Даже, если предположить, что это окно блокирует любые попытки физически добраться до стандартного лаунчера (сенсорные кнопки, физические кнопки, жест по статусной строке), оно ни в коем случае не реализует всю ту защиту, которую предоставляет стандартный экран блокировки. Стандартная блокировка гораздо более функциональна и блокирует даже доступ к девайсу как к накопителю, т.е. пока не разблокируете экран — доступа к файлам не получите.
В общем это приложение достаточно дырявое и защиту сравнимую со стандартным экраном блокировки оно не предоставляет.
Сделаем свою реализацию
Самым идеальным вариантом для меня была интеграция со стандартным экраном блокировки. Хотелось, чтобы можно было прямо из него сканировать метки и авторизоваться, а если метки нет, то можно было бы использовать пароль как резервный вариант.
Перед тем как начать делать свою реализацию я определил две причины, по которым автор NFCSecure сделал своё приложение именно так, как оно работает сейчас, а не как задумал я. Опишем эти проблемы и пути их решения в разработанном мною приложении.
Сканер меток работает не всегда
А точнее он не работает как минимум в двух случаях, которые нас интересуют:
- Когда экран выключен.
- Когда мы находимся на экране блокировки.
Я предполагаю, что в этих случаях сканер не работает по соображениям безопасности. После сканирования метки запускается наиболее подходящее приложение, и не известно что это приложение захочет сделать. Для примера, Google Wallet (судя по видео) требует пин-код и выбор карты перед тем как приложить устройство к терминалу.
Правила работы сканера находятся в системном приложении NfcNci.apk. Там то и находится константа которая определяет в каком состоянии должен быть девайс, чтобы работало сканирование (можно почитать здесь). Естественно эту константу нужно изменить. Многие разработчики выкладывают на xda-developers уже готовые модифицированные файлы NfcNci.apk под нужный вам девайс, поэтому я просто взял готовый файл и заменил им приложение на моём устройстве.
Константе можно установить как минимум одно из двух значений, которые позволят сканеру считывать метки при заблокированном экране:
- Сканер работает при выключенном экране.
- Сканер работает при включенном экране и даже с активной блокировкой.
Первое решение по словам форумчан жрёт около 35% батареи за 3 часа при том, что телефон просто лежал без дела. Поэтому лучше выбирать второй вариант, так как он вообще не тратит заряд + предотвратит случайную разблокировку и включение экрана, если метка будет находиться очень близко.
Конечно данное решение резко уменьшает порог вхождения обычных пользователей, так как теперь потребуется рутованный девайс и некоторые телодвижения, чтобы заменить оригинальное приложение. Но так как приложение не ориентированно на большие массы, то с этим можно смириться.
Нет простой возможности разблокировки
Опять же, по соображениям безопасности приложение на устройстве не может просто взять и разблокировать экран защищённый каким-либо методом (пароль, пин, паттерн и др.). На данный момент существует 2 стандартных решения, которые позволяют разблокировать экран:
- Класс KeyguardManager.KeyguardLock.
- Флаги окна FLAG_DISMISS_KEYGUARD и FLAG_SHOW_WHEN_LOCKED.
KeyguardManager.KeyguardLock
Этот класс содержит 2 метода: disableKeyguard() и reenableKeyguard(). Первый метод разблокирует только не защищёный экран. Если экран защищён каким-либо методом, то вызов будет проигнорирован. Метод reenableKeyguard() необходимо вызвать для повторной блокировки экрана, иначе блокировщик не будет запускаться после его выключения.
Минусы данного решения: нельзя разблокировать экран защищённый, допустим, паролем да ещё и надо по какому-то событию вызывать перезапуск экрана блокировки. Вдобавок данный класс является устаревшим начиная с API 13, так что на него не стоит надеяться.
Флаги окна
Флаги нужно устанавливать на родительское окно Activity. FLAG_DISMISS_KEYGUARD разблокирует экран только в том случае, если он не защищён. После закрытия окна блокировщик не будет восстанавливаться пока не выключится экран. FLAG_SHOW_WHEN_LOCKED лишь спрячет блокировщик (даже защищённый) и после закрытия окна он сразу же перейдёт на передний план.
То есть, опять же, эти флаги не смогут разблокировать защищённый экран. Максимум что можно сделать — показать своё окно поверх него.
В данном случае, чем меньше давать возможностей разработчику, тем больше повышается безопасность самой ОС. Возможности флагов, особенно FLAG_SHOW_WHEN_LOCKED, позволяют отображать своё приложение поверх блокировщика не нарушая безопасности системы. Но, к сожалению, нас это не устраивает.
Пишем обходные пути
Так как Android API не предоставляет нам красивых решений для разблокировки, то придётся писать костыли обходные пути.
В итоге было написано базовое приложение реализующее 3 метода разблокировки. Решил назвать его NFC Unlocker (ссылки на Google Play и исходники в конце поста). Реализованные обходные пути могут быть не стабильны, но это и так понятно исходя из их названия. Все эти методы требуют, чтобы пользователь вводил пароль в настройках приложения, а не в системе. Это сделано для того, чтобы мы могли восстанавливать/вводить (в зависимости от метода) пароль вместо пользователя.
После чтения метки ОС должна запустить наиболее подходящее Activity. Поэтому воспользоваться BroadcastReceiver’ом не получится. Далее я опишу эти методы.
Установка флага для окна Activity
И всё-таки флаг нам может помочь в реализации задуманного. Данное решение наиболее чистое, так как не требует рута и использует флаги окна, которые вполне себе разрешены. Для того, чтобы флаги сработали придётся очистить пароль методом DevicePolicyManager.resetPassword(). Для этого нам потребуются права администратора, которые приложение запросит у пользователя на странице настроек.
Алгоритм разблокировки таков:
- Пользователь сканирует метку, запускается наше Activity.
- Чистим пароль:
((DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE)).resetPassword("", 0);
- Ставим флаг окну нашего Activity:
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
- Завершаем Activity в методе onAttachedToWindow, так как именно в нём к окну уже применён наш флаг.
- В методе onDestroy восстанавливаем пароль пользователя.
Использование KeyguardLock
Следующий метод использует устаревшее API, но тем не менее успешно выполняет свою задачу на Android 4.3. Так же как и в предыдущем методе нам придётся очистить пароль, чтобы разблокировка сработала.
Здесь алгоритм сложнее и с первого взгляда достаточно не стабилен (на практике всё гораздо лучше):
- Пользователь сканирует метку, запускается наше Activity.
- Чистим пароль:
((DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE)).resetPassword("", 0).
- Разблокируем экран:
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Activity.KEYGUARD_SERVICE);
KeyguardLock keyguardLock = keyguardManager.newKeyguardLock("nfcunlocker");
keyguardLock.disableKeyguard();
- Запускаем сервис, который в фоновом режиме создаст BroadcastReceiver, который будет принимать событие выключения экрана:
ScreenReceiver screenReceiver = new ScreenReceiver();
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
registerReceiver(mReceiver, filter);
- Как только получено событие оповещающее о том, что экран выключен — включаем блокировку и восстанавливаем пароль.
Данный метод работает достаточно стабильно, но пользуется устаревшим API.
Ввод пароля через shell
Этот способ я придумал первым так как он достаточно прост, но требует root. В Андроиде с shell-а можно вызвать команду «input», которая позволяет вводить текст и эмулировать нажатия клавиш. Для ввода текста используется такой синтаксис:
input text "type your text here"
Эмуляция нажатий клавиш производится с помощью такой команды:
input keyevent KEYCODE
Список кодов клавиш можно найти здесь.
Алгоритм разблокировки у данного метода очень прост:
- Пользователь сканирует метку, запускается наше Activity.
- Вводим пароль с помощью shell команды «input», и с её же помощью посылаем код клавиши Enter.
Немного о самих метках
Про алгоритм выбора Activity при сканировании метки можно почитать здесь. Вкратце можно сказать, что ОС выбирает Activity на основании содержимого метки. Для гарантированного запуска нашего Activity нужно воспользоваться AAR (Android Application Records), что обозначает просто запись названия пакета приложения на метку. С помощью данного способа можно было бы гарантированно запускать разблокировку, но у меня не было под рукой меток, которые поддерживают стандарт NDEF. Поэтому я идентифицирую их по уникальному идентификатору.
Итог
А теперь подытожим плюсы и минусы интеграции с родным блокировщиком.
Плюсы:
- Не нарушаем защиту ОС.
- Разблокируем устройство просто приложив метку к устройству.
- Если метки нет рядом, то можно ввести пароль вручную.
Минусы:
- Высокий порог вхождения для пользователей. Нужен рут и модифицированный NfcNci.apk.
- Методы разблокировки могут работать не стабильно. Нужно выбрать подходящий.
По ссылке на Github есть ссылки на модифицированные файлы NfcNci.apk для некоторых популярных смартфонов.
Для демонстрации настройки и работы приложения я записал видео:
Ссылки:
Google Play
Github
P.S. За качество кода прошу не пинать. Я не Java-разработчик.
Автор: TheSteelRat