Реализация графического ключа для входа в мобильное приложение
Графический ключ — паттерн из Android, в iOS встречается реже (нет системного аналога). Принцип тот же, что и у PIN: локальная разблокировка хранилища без отправки секрета на сервер. Но у паттерна есть специфические проблемы безопасности, которые надо понимать до начала реализации.
Проблема жирных следов
На экране телефона остаются следы от пальца. Паттерн из 9 точек восстанавливается визуально с расстояния 1–2 метра под правильным углом освещения. Исследование Авраама и коллег (2010) показало: большинство пользователей рисуют L-, Z- или S-образные паттерны — 12 наиболее популярных паттернов покрывают ~20% пользовательской базы. Это важный контекст для клиента при выборе метода аутентификации.
Кодирование паттерна
Стандартная сетка 3×3 — 9 точек с индексами 0–8. Паттерн — последовательность индексов. Минимальная длина — 4 точки (требование Android). Из 9 точек с минимальной длиной 4 возможных паттернов ~389 112 — существенно меньше, чем у 6-значного PIN (1 000 000 комбинаций).
Кодируем паттерн в строку индексов: [0,1,2,5,8] → "01258". Эту строку используем как input для деривации ключа — та же схема PBKDF2, что и для PIN. Паттерн в plaintext никогда не сохраняем.
Кастомная реализация View
На Android можно взять com.github.itsxtt:pattern-lock или аналогичные open-source библиотеки — базовую логику рисования линий. Но в enterprise-проектах мы пишем сами: полный контроль над визуалом, криптографической частью, и нет зависимости от неподдерживаемой библиотеки.
Jetpack Compose:
@Composable
fun PatternLockView(
onPatternComplete: (List<Int>) -> Unit
) {
val selectedDots = remember { mutableStateListOf<Int>() }
var currentPosition by remember { mutableStateOf(Offset.Zero) }
Canvas(
modifier = Modifier
.fillMaxSize()
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { offset -> /* найти ближайшую точку */ },
onDrag = { change, _ ->
currentPosition = change.position
// добавить точку если касание в радиусе
},
onDragEnd = {
if (selectedDots.size >= 4) onPatternComplete(selectedDots.toList())
selectedDots.clear()
}
)
}
) {
// рисуем точки и линии между selectedDots + линию до currentPosition
}
}
Ключевые детали: точку можно посетить только один раз; линия, пересекающая незатронутую точку, автоматически её добавляет (стандартное поведение Android Pattern Lock); минимальное расстояние касания до точки — ~24dp.
Скрываем паттерн через 500–800мс после завершения ввода — линии исчезают, точки остаются. Это предотвращает shoulder surfing.
iOS: кастомная реализация
В iOS нет системного Pattern Lock. Реализуем через SwiftUI Canvas + DragGesture. Логика аналогичная, визуал адаптируем под iOS Human Interface Guidelines. На iOS паттерн встречается реже — обычно это специфический запрос клиента (например, детские приложения или специализированные enterprise-инструменты).
Fallback и счётчик ошибок
После 5 неудачных попыток — требуем полный логин через credentials. Счётчик в Keychain (iOS) или EncryptedSharedPreferences (Android). После успешного логина сбрасываем счётчик и предлагаем перерисовать паттерн — важно предупредить пользователя, что старый паттерн сброшен.
Когда не стоит использовать
Паттерн — слабее PIN при равном числе символов. Для банковских приложений, медицинских данных или корпоративного доступа — рекомендуем PIN 6+ цифр с PBKDF2 или биометрию. Паттерн уместен в приложениях, где удобство важнее максимальной безопасности: трекеры, органайзеры, приложения для детей.
Сроки
Кастомная реализация PatternLock с правильной криптографической схемой на одной платформе — 5–8 рабочих дней. На обеих платформах — 10–14 дней (с адаптацией UX под каждую ОС).







