Перейти к содержимому
SafePass

Форматы шифрования SafePass, версия 1

Все целые числа в двоичных конвертах хранятся в порядке big-endian. Парсеры отклоняют неизвестные версии, усечённые заголовки, превышение размера и недействительные теги аутентификации.

Конверт ключа под мастер-паролем (SPWK)

magic[4] = "SPWK" | version[1] = 1 | salt[16] | nonce[12] | ciphertext[32] | tag[16]

Мастер-пароль кодируется в UTF-8. Argon2id v1.3 с параметрами m=65536 KiB, t=3, p=4 выводит 32-байтовый ключ шифрования ключа (KEK). AES-256-GCM шифрует случайный 32-байтовый ключ сейфа. AAD: "SPWK" || version || salt. Для каждого конверта генерируются новые соль и nonce.

Конверт ревизии записи (SPCR)

magic[4] = "SPCR" | version[1] = 1 | hkdf_salt[16] | nonce[12] | ciphertext[n] | tag[16]

Для каждой неизменяемой ревизии HKDF-SHA-256 выводит 32-байтовый ключ шифрования из ключа сейфа, случайной соли hkdf_salt и идентификаторов записи и ревизии с префиксами длины. Контекст: SafePass record key v1. AES-256-GCM шифрует сериализованную запись. В AAD входят метка формата и идентификаторы записи и ревизии с префиксами длины. Ревизия шифруется один раз. Для правки создаются новый идентификатор, соль и nonce.

Максимальный размер открытого содержимого записи — 1 МиБ. Ядро проверяет ограничение перед вызовом нативного криптографического API.

Локальный кэш

База SQLCipher клиента Flutter хранит те же зашифрованные конверты, что используются для синхронизации, а также зашифрованные при хранении локальные метаданные и сохраняемую очередь операций. Ключ SQLCipher — случайный секрет устройства в защищённом хранилище платформы. Браузер хранит конверты и метаданные синхронизации в IndexedDB и не сохраняет расшифрованный ключ сейфа.

Формат восстановления

Клиент генерирует 256 бит через системный CSPRNG и кодирует их в английскую мнемоническую фразу BIP-39 из 24 слов со стандартной 8-битной контрольной суммой. При вводе проверяются все слова и контрольная сумма. Список слов взят из репозитория Bitcoin BIPs и распространяется по лицензии MIT для BIP-39.

HKDF-SHA-256 выводит из энтропии мнемоники два разных ключа: seed подписи Ed25519 и ключ шифрования для восстановления. crypto_core подписывает и проверяет вызовы с разделением доменов. API привязывает подтверждение регистрации или ротации к идентификатору аккаунта, случайному пятиминутному вызову и хешу точных данных обновления восстановления или регистрации OPAQUE. Форматы и транзакции описаны в протоколе восстановления v1.

Конверт восстановления привязан к аккаунту и использует новую 128-битную соль и 96-битный nonce:

magic[4] = "SPRE" | version[1] = 1 | salt[16] | nonce[12] | ciphertext[32] | tag[16]

Информация для HKDF: "SafePass recovery wrapping key v1\0" || u16be(account_id_length) || account_id. AAD для AES-GCM: "SPRE" || version || salt || u16be(account_id_length) || account_id. API хранит этот 81-байтовый шифротекст вместе с открытым ключом восстановления. Другое устройство получает конверт и расшифровывает ключ сейфа локально. Для замены зарегистрированного ключа восстановления нужно доказательство владения прежним ключом. Подписанный сброс OPAQUE отзывает все сессии обновления в той же транзакции.

При восстановлении локального сейфа клиент раскрывает его ключ, шифрует его новым локальным мастер-паролем и создаёт новую фразу восстановления. Для подключённого аккаунта пользователь подтверждает фразу и загружает новый конверт, привязанный к аккаунту. Переход подписывается прежней фразой. Она остаётся в памяти на время восстановления и может быть введена заново при перезапуске приложения до обновления.

Любой, кто получит фразу и соответствующий конверт, сможет расшифровать ключ сейфа. Фразу запрещено писать в журнал, синхронизировать открытым текстом или автоматически сохранять в облаке.

Детерминированный межплатформенный вектор вывода ключа и подписи Ed25519 хранится в recovery-ed25519-v1.txt. Его проверяют тесты Rust, Dart, WASM/TypeScript и .NET. Опубликованная в нём фраза BIP-39 — намеренно раскрытые тестовые данные, которые нельзя использовать для аккаунта.

Документация