Одна из частых уязвимостей мобильных криптокошельков — хранение PIN-кода в UserDefaults или в виде обычной строки. Мы видели проекты, где PIN сравнивался через == с числом, полученным из SharedPreferences. Это прямой путь к краже средств.
Без ограничения попыток злоумышленник может перебрать все 10000 комбинаций за пару часов на современном смартфоне. Результат — потеря контроля над кошельком. Мы решаем эту задачу за 1–3 дня на любом стеке.
За 5 лет мы внедрили PIN-защиту в 15+ криптокошельках, каждый раз адаптируя под требования App Store Review и Google Play. В этой статье разберём, как правильно хэшировать PIN, организовать блокировку и сделать кастомный numpad, который не даст утечь через автозаполнение. Если вы хотите избежать аудита по безопасности с high-критическими находками, внедрение под ключ — ваш вариант. Мы предлагаем готовое решение с исходным кодом и документацией. Свяжитесь с нами — и за 2 дня получите защищённый PIN-ввод. Гарантируем совместимость с iOS 15+ и Android 8+.
Проблемы, которые решаем
- Хранение PIN без хэша. Часто разработчики используют MD5 или даже plaintext. Это первая же находка при пентесте.
- Отсутствие блокировки. Без задержек и полной блокировки злоумышленник перебирает PIN за минуты.
- Системная клавиатура. Она не даёт визуального контроля ввода и может предлагать автозаполнение из iCloud Keychain — риск утечки PIN.
Как мы реализуем хэширование PIN
PIN никогда не хранится как есть. Минимальный приемлемый вариант — PBKDF2-HMAC-SHA256 с уникальной солью (32 байта из CSPRNG) и количеством итераций от 100 000. Лучше — bcrypt или Argon2id, но последний требует стороннюю библиотеку на iOS.
На iOS: PBKDF2 через CCKeyDerivationPBKDF из CommonCrypto:
func deriveKey(from pin: String, salt: Data, iterations: UInt32 = 200_000) -> Data { var derivedKey = Data(count: 32) let pinData = pin.data(using: .utf8)! derivedKey.withUnsafeMutableBytes { derivedPtr in pinData.withUnsafeBytes { pinPtr in salt.withUnsafeBytes { saltPtr in CCKeyDerivationPBKDF( CCPBKDFAlgorithm(kCCPBKDF2), pinPtr.baseAddress, pinData.count, saltPtr.baseAddress, salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), iterations, derivedPtr.baseAddress, 32 ) } } } return derivedKey } Соль + хэш хранятся в iOS Keychain с kSecAttrAccessibleWhenUnlockedThisDeviceOnly. PIN из памяти обнуляется сразу после деривации.
Сравнение: PBKDF2 с 200 000 итераций в 10 000 раз медленнее обычного SHA256, что делает атаку перебором неэффективной.
"Keychain items persist across app deletions when using accessibility type
kSecAttrAccessibleWhenUnlockedThisDeviceOnly." — Apple Keychain Documentation.
Пошаговое руководство реализации
- Генерация соли (32 байта, CSPRNG).
- Хэширование PIN через PBKDF2 (200 000 итераций).
- Сохранение хэша и соли в Keychain / EncryptedSharedPreferences.
- Настройка счётчика попыток (хранить там же).
- Разработка кастомного numpad с визуальной обратной связью.
- Опционально: интеграция с биометрией (Face ID / Touch ID).
- Тестирование граничных случаев: переустановка, сброс данных, многопоточный ввод.
Почему нужно ограничивать попытки ввода
Без ограничения злоумышленник может перебирать PIN бесконечно. После 3 неудачных попыток — задержка 30 секунд. После 5 — минута. После 10 — полная блокировка кошелька с требованием восстановления через seed-фразу. Счётчик попыток хранится в Keychain (не UserDefaults — его можно сбросить удалением данных приложения без root).
Атаку через удаление и переустановку приложения частично нейтрализуем: на iOS Keychain с kSecAttrAccessibleWhenUnlockedThisDeviceOnly переживает переустановку. На Android — блокировку храним через EncryptedSharedPreferences в отдельном contentProvider или бэкенд.
Кастомный numpad или системная клавиатура: что безопаснее
Системная цифровая клавиатура удобна, но не даёт визуального контроля — не видно, сколько цифр введено. Кастомный numpad (6 кружков + 10 цифр + backspace) — стандарт для криптокошельков. Он исключает автозаполнение (textContentType = .none), предотвращая утечку PIN через менеджеры паролей.
| Платформа | Хранилище | Поведение при переустановке |
|---|---|---|
| iOS | Keychain с kSecAttrAccessibleWhenUnlockedThisDeviceOnly |
Данные сохраняются |
| Android | KeyStore / EncryptedSharedPreferences | KeyStore уничтожается, нужен бэкенд |
Сравнение методов хэширования
| Метод | Итераций | Безопасность | Скорость (отн.) |
|---|---|---|---|
| PBKDF2-HMAC-SHA256 | 100k–200k | Хорошая | 1x |
| bcrypt | cost=10 | Лучше | 0.5x |
| Argon2id | m=19MB, t=2, p=1 | Лучшая | 0.2x |
Процесс работы
- Анализ требований и аудит текущей реализации безопасности.
- Проектирование схемы: хэширование, блокировка, numpad.
- Реализация на Swift / Kotlin / Flutter.
- Тестирование на уязвимости (перебор, сброс счётчика, переустановка).
- Деплой и документация.
Сроки и стоимость
Сроки: 1–3 дня. Стоимость разработки — от $270–390. Экономия от предотвращения утечки данных — до $9k–13k. (стоимость инцидента при краже средств).
Закажите разработку под ключ — получите готовый код, защищённый от перебора и утечек. Свяжитесь с нами для консультации.







