Безопасность
Как устроено хранилище и что можно обнаружить при его взломе.
Последнее обновление: 21 сентября 2026 г. · AssuredAuth, от CircleRoll LLC
Это публичное описание того, как AssuredAuth защищает ваши секреты, пароли и карты двухфакторной аутентификации. Оно написано таким образом, чтобы заинтересованный в безопасности читатель мог проверить каждое утверждение на соответствие приложению и опубликованным тестовым векторам (доступным любому рецензенту по запросу по адресу hello@assuredauth.com). Здесь говорится только то, что правда, и ничего больше: ничто здесь не претендует на защиту от атак, и ничего не обещается в процентах.
Вкратце: хранилище находится в одном месте — в вашем телефоне. Каждое значение в нем зашифровано ключом, который хранится в аппаратной системе безопасности телефона и не может быть экспортирован. У нас нет сервера хранилища, мы не храним копии и не храним ключи. Файл резервной копии покидает телефон только после того, как он будет зашифрован с помощью парольной фразы, которая никогда нигде не хранится.
Сообщить о проблеме: hello@assuredauth.com. См. раздел «Сообщение об уязвимости» в конце.
1. Что именно зашифровано и чем?
| Данные | На телефоне | Ключ |
|---|---|---|
| Секреты двухфакторной генерации (TOTP/HOTP/Steam seeds) | На телефонеKeychain (iOS) / Android — магазин, поддерживаемый Keystore | КлючКлюч платформы привязан к аппаратному обеспечению; строка в базе данных содержит только ссылку (secretRef), никогда не содержит начальное значение (seed). |
| Пароли (значение пароля, примечания) | На телефонеЗапечатанная строка: AES-256-GCM, случайное 96-битное значение nonce для каждого параметра. | КлючСлучайный 256-битный ключ данных, который, в свою очередь, заключен в аппаратный ключ, описанный в §2. |
| Карта (номер, срок действия, имя владельца карты, CVC-код) | На телефонеАналогично паролям; код CVC необязателен и сохраняется только в том случае, если вы его введете. | КлючТот же ключ данных |
| Метаданные (названия сервисов, имена пользователей, значки, порядок сортировки) | На телефонеБаза данных приложения (SwiftData / Room) | КлючЗащищено шифрованием файлов операционной системы; рассматривается как несекретный контент. |
| Файл резервной копии (.authbackup) | На телефонеГде бы вы его ни разместили: Файлы, iCloud Drive, папка appDataFolder в Google Drive, меню "Поделиться". | КлючКлюч, полученный из вашей парольной фразы (§3). Apple и Google получают запечатанный файл и ничего больше. |
| Приложение для Watch / Wear | На телефонеВстроенная в часы связка ключей / зашифрованные общие настройки | КлючКлючи платформы для часов передаются по каналу сопряженных устройств платформы. |
| Трафик через расширенные учетные записи пользователей (одноразовые коды по запросу) | На телефонеНе сохраняется; передается в виде зашифрованных сквозных зашифрованных сообщений. | КлючДля каждой пары ключей требуется пара X25519, закрытый ключ находится в связке ключей/хранилище ключей; ретранслятор видит только зашифрованный текст. |
В модуле диагностики нет метода, принимающего секретный ключ. Ни одно аналитическое событие не содержит поля хранилища. Ни одно значение из хранилища никогда не записывается в журнал ни в одной сборке.
2. Где находятся ключи, на каждой платформе.
iOS. Ключ данных заключен в оболочку ключа, сгенерированного внутри защищенного анклава (kSecAttrTokenIDSecureEnclave) с контролем доступа .privateKeyUsage + .userPresence, доступность …ThisDeviceOnly, в общей группе доступа Keychain приложения, чтобы расширение автозаполнения могло использовать тот же ключ. Закрытый ключ не может быть считан из анклава приложением, резервной копией или отладчиком; анклав соглашается использовать его только после присутствия пользователя (Face ID, Touch ID или пароль устройства). ThisDeviceOnly означает, что резервная копия телефона в iCloud или iTunes не содержит ключа; восстановленный телефон начинается с пустого хранилища и запрашивает файл резервной копии и парольную фразу. Телефон без пароля не может защитить ключ .userPresence (Enclave отказывается его создавать), и симулятор по своей сути следует тому же пути (его запрос присутствия — это предупреждение, на которое ни один тест не может ответить): там ключ данных представляет собой обычный элемент Keychain, доступный только для устройства — по-прежнему отсутствующий в резервных копиях, по-прежнему нечитаемый, пока телефон заблокирован, но без второго запроса — и экран безопасности сообщает именно об этом и предлагает установить пароль.
Android. Ключ данных упакован в AES-256-ключ AndroidKeyStore, сгенерированный в StrongBox, если устройство его имеет (в противном случае — TEE), с включенными флагами setUserAuthenticationRequired(true) и 30-секундным сроком действия аутентификации после ввода биометрических данных или учетных данных устройства, setUnlockedDeviceRequired(true) и setInvalidatedByBiometricEnrollment(true). Последний флаг является скорее корректным, чем определяющим: Android применяет его только к ключам, требующим биометрической аутентификации при каждом использовании, поэтому для этого 30-секундного ключа с разрешенными учетными данными устройства он ничего не меняет — несанкционированный доступ предотвращает то, что для регистрации требуется PIN-код, а снятие блокировки экрана делает ключ недействительным навсегда. Ключи Keystore не экспортируются и исключены из облачного резервного копирования Android. Домен, который приложение указывает в своих представлениях (webDomain), считается достоверным только из известного браузера; Из любого другого пакета он также должен быть одним из тех, которые система проверила для этого пакета (Digital Asset Links), поскольку любое приложение может претендовать на любой домен.
Что это означает для украденного файла базы данных. Файл SQLite и зашифрованные строки представляют собой зашифрованный текст. Ключ для их открытия хранится в защитном устройстве телефона, с которого был получен файл, и его нельзя скопировать вместе с файлом. В автономном режиме ничего нельзя угадать: ключ данных является случайным, а не получен из какого-либо выбора человека.
3. Файл резервной копии
Резервные копии — это единственное, что покидает телефон, поэтому в принципе только с них возможна атака в автономном режиме. Конструкция телефона делает такую атаку дорогостоящей и оставляет для неё как можно меньше ресурсов:
- Кодовая фраза должна содержать не менее 12 символов (таблица отклоняет меньшее количество символов и отображает индикатор уровня защиты).
- Подсказка для парольной фразы нигде не хранится — ни в файле, ни в приложении.
- Для каждого записываемого файла генерируется новый случайный 16-байтовый ключ-соль и 12-байтовый ключ-одноразовый ключ.
- Весь текст зашифрован с помощью AES-256-GCM, поэтому неверная парольная фраза или поврежденный файл отклоняются и никогда не расшифровываются в мусор.
- Заголовок (формат, версия, параметры KDF, соль) привязывается к тегу GCM в качестве дополнительных аутентифицированных данных, поэтому файл, коэффициент сложности которого был уменьшен, не откроется.
Файл имеет формат JSON и читается в любом текстовом редакторе; непрозрачным является только зашифрованный текст. Тот, кто найдет этот файл через пять лет, когда приложение будет удалено, сможет точно увидеть, как он был зашифрован, и открыть его с помощью OpenSSL или любой библиотеки Argon2.
Формат v1 (поставляется в версии 1.0, остается читаемым)
{
"format": "authbackup",
"version": 1,
"kdf": "PBKDF2-HMAC-SHA256",
"rounds": 600000,
"salt": "<base64, 16 bytes>",
"cipher": "AES-256-GCM",
"nonce": "<base64, 12 bytes>",
"ciphertext": "<base64>",
"tag": "<base64, 16 bytes>"
}
| Шаг | Байты |
|---|---|
key | PBKDF2-HMAC-SHA256(passphrase as UTF-8, salt, 600000 rounds, 32 bytes) |
aad | UTF-8("authbackup:1:600000:") ‖ salt (необработанные байты соли, а не base64) |
ciphertext, tag | AES-256-GCM(key, nonce, plaintext, aad); Тег размером 16 байт, хранится отдельно. |
plaintext | JSON в кодировке UTF-8, ключи отсортированы: {"accounts":[…],"exportedAt":"…Z","passwords":[…],"version":1} |
Примечание по совместимости: Android 1.0 использовал вариант v1 — kdf в нижнем регистре, без ключа тега (16-байтовый тег, добавляемый к зашифрованному тексту), без дополнительных данных. Обе платформы продолжают читать эти файлы (когда тег отсутствует: разделить последние 16 байт, открыть с пустым AAD; сравнить kdf без учета регистра) и обе теперь записывают только указанную выше структуру.
Формат v2 (текущий; используется по умолчанию для новых резервных копий)
{
"format": "authbackup",
"version": 2,
"kdf": "Argon2id",
"memoryKiB": 65536,
"passes": 3,
"lanes": 1,
"salt": "<base64, 16 bytes>",
"cipher": "AES-256-GCM",
"nonce": "<base64, 12 bytes>",
"ciphertext": "<base64>",
"tag": "<base64, 16 bytes>"
}
| Шаг | Байты |
|---|---|
key | Argon2id(version 0x13, passphrase as UTF-8, salt, m = 65536 KiB, t = 3, p = 1, tagLength 32, no secret, no associated data) |
aad | UTF-8("authbackup:2:argon2id:65536:3:1:") ‖ salt |
ciphertext, tag | AES-256-GCM(key, nonce, plaintext, aad); Тег размером 16 байт, хранится отдельно. |
plaintext | JSON в кодировке UTF-8, ключи отсортированы: {"accounts":[…],"cards":[…],"exportedAt":"…Z","passwords":[…],"version":2} |
Argon2id — это эталонная реализация на языке C (phc-winner-argon2, CC0), распространяемая на обеих платформах. 64 МиБ и 3 прохода занимают около секунды на современном телефоне, и каждая попытка угадывания требует столько же памяти и времени на любом оборудовании — свойство, которого нет у PBKDF2 при работе с графическими процессорами, и именно поэтому существует версия 2. Считыватель игнорирует ключи, которые ему неизвестны, поэтому считыватель версии 1, получив тело запроса из версии 2, просто никогда не увидит карты.
Доказательство
Три тестовых файла-вектора (backup v1, backup v2 и Argon2id) генерируются третьей реализацией, независимой от обоих приложений (Node.js OpenSSL для PBKDF2 и AES-GCM, WebAssembly Argon2id, проверяемый во время генерации на соответствие опубликованным ответам эталонной реализации). Каждый файл содержит парольную фразу, соль, nonce, открытый текст, производный ключ, AAD, ожидаемый зашифрованный текст и тег, а также полный файл. Модульные тесты на iOS и Android считывают одни и те же файлы и проверяют одни и те же байты — таким образом, резервная копия, запечатанная на одной платформе, доказывается, а не предполагается, что она откроется на другой, и совпадение всех трех результатов не означает наличие двух копий одной и той же ошибки. Вспомогательный инструмент открывает файл, фактически записанный телефоном с использованием той же третьей реализации, и проверяет заголовок на соответствие правилам, указанным выше (ключи точно соответствуют формату, поэтому подсказки или имена отсутствуют; 16-байтовая соль, 12-байтовый nonce, 16-байтовый тег хранятся отдельно; версия 2 занимает ровно 64 МиБ / 3 / 1; неправильная парольная фраза отклонена); автоматизированный запуск iOS записывает такой файл в симулятор и открывает его таким образом, а затем выполняет поиск в контейнере приложений симулятора, контейнере групп приложений и базе данных связки ключей каждого заданного пароля и номера карты в кодировках UTF-8 и UTF-16 (0 совпадений).
4. Модель угроз
| Ситуация | То, что у них есть | Что они получают |
|---|---|---|
| Потерянный или украденный телефон, заблокирован | То, что у них естьУстройство, файл базы данных, запечатанные строки | Что они получаютЗашифрованный текст. Ключ шифрования находится в защищенном анклаве/StrongBox и разблокируется только после снятия блокировки устройства и ввода новых биометрических данных или пароля. Удаленная очистка телефона (функция «Найти мой телефон» / «Найти устройство») уничтожает вместе с ним и ключевой материал. |
| Украденный файл резервной копии | То, что у них естьФайл .authbackup из облачного аккаунта, электронной почты, ноутбука. | Что они получаютКод AES-256-GCM обрабатывается ключом, полученным из парольной фразы длиной не менее 12 символов, с солью для каждого файла и Argon2id с 3 проходами по 64 МиБ (v2) или PBKDF2 с 600 000 раундов (v1). В открытом виде отсутствуют подсказки, имена пользователей и списки учетных записей. Каждая попытка перехвата стоит полной суммы генерации пароля; нет никаких коротких путей. Надежная парольная фраза — это вся защита, и приложение это подтверждает. |
| Украденное устройство с разблокированным экраном. | То, что у них естьТелефон в руке, разблокированный. | Что они получаютСписок кодов. Хранилище (пароли, карты) находится за блокировкой приложения и повторно блокируется по таймеру автоматической блокировки (немедленно / 1 мин / 5 мин); расшифрованное значение существует в памяти только пока отображается на экране. Автозаполнение и экспорт в открытом текстовом виде запрашивают новые биометрические данные, даже когда приложение открыто. Включите блокировку приложения с параметром «Немедленно», если вас это беспокоит. |
| Вредоносное приложение на том же телефоне | То, что у них естьПесочница другого приложения, буфер обмена, специальные возможности или приемы наложения. | Что они получаютСистема не может читать наши файлы (песочница платформы) или использовать наш ключ (ключ привязан к нашему идентификатору подписи и к присутствию пользователя). Копии удаляются из буфера обмена через 30 секунд; на Android копия помечается как конфиденциальная и исключается из истории буфера обмена. На Android экраны хранилища устанавливают флаг FLAG_SECURE (нет скриншотов, нет записи экрана, пустое поле в переключателе); на iOS миниатюра переключателя становится пустой. Автозаполнение на Android предоставляется только приложениям, которые система проверила на соответствие сохраненному домену (Digital Asset Links), поэтому похожее приложение не предлагает ваш пароль. |
| Фишинговая страница | То, что у них естьСайт, который выглядит так же, как тот, который вы сохранили. | Что они получаютНичего не заполняется автоматически. Сопоставление осуществляется по регистрируемому домену (публичный список суффиксов, включенный в приложение и обновляемый им): login.example.com и example.com совпадают, example.com.evil.net и examp1e.com — нет. Встроенный браузер выполняет сопоставление таким же образом и заполняет данные только в том фрейме, который запросил ввод, перепроверяя его на соответствие собственному источнику в момент заполнения; хост без собственного регистрируемого домена — IP-адрес, простое имя в локальной сети — совпадает точно, и никаких отклонений не требуется. Автозаполнение iOS добавляет проверку на связанные домены от Apple. Пароль, который нужно вставлять вручную в похожий пароль, по-прежнему представляет риск; приложение не может его устранить, а лишь не помогает. |
| Наши собственные серверы | То, что у них есть— | Что они получаютСервера хранилища нет. У AssuredAuth нет учетных записей, входа в систему и копии вашего хранилища, поэтому на нашей стороне нет ничего вашего, что можно было бы украсть и взломать. На стороне сервера работают две вещи, ни одна из которых не касается хранилища: почтовый ящик-ретранслятор для расширения рабочего стола, который хранит зашифрованные сквозным шифрованием конверты (одноразовые коды, удаляемые после прочтения; у ретранслятора никогда нет ключа), и серверы выхода дополнительного частного соединения, которые передают трафик и не ведут журналы доступа. |
| Повестка в суд или запрос государственных органов | То, что у них естьПравовой процесс, адресованный нам. | Что они получаютНам нечего передать: у нас нет хранилища, ключа, парольной фразы и резервной копии. Нас могут заставить это сказать; нас нельзя заставить предоставить то, чего не существует. |
5. Буфер обмена, скриншоты и память.
- Функция копирования помещает значение в буфер обмена на 30 секунд, а затем очищает его, если оно по-прежнему принадлежит нам. На Android фрагмент помечается как EXTRA_IS_SENSITIVE, поэтому система скрывает его из предварительного просмотра и истории буфера обмена; на iOS элемент буфера обмена находится только локально (не передается на другие устройства через функцию Handoff).
- На Android экраны хранилища (Пароли, Пароль, Карты, Карта, листы с кодовыми фразами, экспорт в открытом текстовом виде) устанавливают флаг FLAG_SECURE. В iOS аналогичного флага нет; приложение по умолчанию скрывает свою миниатюру в переключателе приложений, а значения отображаются за переключателем в виде глаза.
- Расшифрованные значения хранятся в памяти только пока отображается экран, и удаляются, когда экран исчезает или приложение блокируется. Ключ данных расшифровывается для каждого запроса и не кэшируется при блокировке.
- Экспорт в текстовом формате существует (так что вы всегда можете уйти), но он осуществляется через подтверждающий лист и требует повторной биометрической аутентификации, и на экране также отображается флаг FLAG_SECURE.
6. Автозаполнение: что заполняется, а что нет.
| Где | Пароли | Карты |
|---|---|---|
| iOS: Safari и приложения (расширение автозаполнения) | ПаролиДа, после повторной активации Face ID / Touch ID / пароля в расширении (LAContext) и только для регистрируемого домена, для которого была сохранена запись, с проверкой Apple связанных доменов для приложений. | КартыНет. Автозаполнение карточек в Safari — это исключительно разработка Apple; на экране карточки написано «Заполняется в браузере AssuredAuth и с помощью Copy» и больше ничего не обещается. |
| Android: приложения и Chrome (AutofillService) | ПаролиДа, аутентификация для каждого набора данных: система отображает BiometricPrompt перед заполнением; пакет приложения считается соответствующим только в том случае, если Digital Asset Links связывает его с сохраненным доменом. | КартыДа, подсказки creditCardNumber, creditCardExpiration* и creditCardSecurityCode относятся к одному и тому же биометрическому шлюзу. |
| Браузер AssuredAuth (на обеих платформах) | ПаролиДа, по регистрируемому домену — хосту, у которого нет ни одного из своих собственных доменов, точно совпадающих по параметрам, — в запрошенном формате, биометрические данные перед заполнением. | КартыДа, путем обнаружения в полевых условиях, биометрическая идентификация перед заполнением |
| Кнопки копирования | ПаролиЗа блокировкой приложения — 30-секундный буфер обмена. | КартыНомер, срок действия, CVC (те же правила) |
На похожем устройстве ничего не заполняется. Ничего не заполняется без биометрических данных.
7. То, чего никогда не происходит.
Нет сервера хранилища. Нет аналитики, которая бы содержала поле хранилища. Нет экспорта в открытом текстовом виде без листа подтверждения и новых биометрических данных. Нет стороннего SDK, загружаемого на экран хранилища. Нет скриншота экрана хранилища. Нет подсказки для парольной фразы. Нет логирования значений ни в одной сборке.
8. От чего мы не можем защититься
Кодовая фраза, достаточно слабая, чтобы её угадать. Телефон, операционная система которого скомпрометирована на корневом уровне (взломанное или рутированное устройство может обмануть приложение относительно всего вышеперечисленного). Человек, за которым следят во время набора текста. Вредоносное ПО на рабочем столе, куда вы вставляете текст. Мы говорим это, чтобы приведенный выше список был понятен как то, что дизайн может удалить.
9. Статус проверки
Данная разработка была протестирована с использованием опубликованных известных решений на обеих платформах, а также с учетом собственных кроссплатформенных требований. Перед размещением объявления о хранилище в магазине планируется внешняя проверка; до ее проведения в этом документе будет указана соответствующая информация.
10. Где всё происходит
Никакие данные из хранилища не покидают телефон, но некоторые программы, находящиеся в нём, работают на компьютерах других пользователей. Вот список этих программ и их содержимое; сами потоки данных описаны в политике конфиденциальности по адресу https://assuredauth.com/privacy.
- Google Cloud / Firebase — почтовый ящик-ретранслятор для расширения для настольных компьютеров (зашифрованные сквозным шифрованием конверты, удаляются после прочтения), push-уведомлений, состояния покупки и сообщений, отправляемых через форму обратной связи.
- Vercel — assuredauth.com, его страницы и небольшие функции, стоящие за ними.
- Vultr — это серверы выхода частного соединения. Трафик проходит через них; ничего не сохраняется.
- Apple и Google Play — покупки и подписки в их собственных магазинах.
- PostHog — аналитика продукта: подсчет использований, не привязанный к конкретным данным пользователя.
- RevenueCat — состояние подписки: активна или истекла, и на каком тарифном плане она оформлена.
Каждый продукт содержит только то, что указано в его описании, и ничего из хранилища. Список меняется вместе с продуктом, и этот раздел меняется вместе с ним.
Сообщение о недостатке
Если вы считаете, что обнаружили уязвимость в чем-либо из описанного здесь, напишите на hello@assuredauth.com с темой "Безопасность". Пожалуйста, укажите платформу, версию приложения и шаги; достаточно подтверждения с использованием ваших собственных данных, и мы просим вас не тестировать на данных других людей. Мы не предоставляем ключ PGP; если отчет не должен передаваться в открытом виде по электронной почте, сообщите об этом в первом сообщении, и мы найдем другой способ.
Взамен мы обещаем: подтверждение получения запроса в течение трех рабочих дней, информирование о ходе работы, исправление неточностей до публикации результатов и указание вашего авторства, если вы этого желаете. Мы не будем предпринимать юридических действий в отношении исследований, проведенных добросовестно в рамках описанной ниже области.
В сферу действия входят: приложения для iOS и Android, приложения для Apple Watch, расширение для настольных компьютеров, assuredauth.com и его API. Вне сферы действия: сами App Store и Google Play, перечисленные выше поставщики услуг, социальная инженерия и атаки типа «отказ в обслуживании».
В настоящее время вознаграждение не предусмотрено. На сообщение дается ответ в виде устранения проблемы и благодарности, а не денежного вознаграждения.