Подробная техническая документация по криптографическим
Сквозное шифрование при передаче файлов
PDF Pro Whitepaper WP-001 — Архитектура and Безопасность
В этом техническом документе описана архитектура системы безопасной передачи PDF Pro — механизма передачи файлов со сквозным шифрованием, при котором файлы шифруются на стороне клиента с помощью AES-256-GCM до загрузки. Сервер хранит только непрозрачный шифротекст и ни в какой момент не имеет возможности расшифровать, осмотреть или прочитать содержимое файлов. Система поддерживает два режима ключей: автоматически сгенерированный случайный ключ (передаваемый через URL-фрагмент, который никогда не отправляется на сервер) или ключ, производный от парольной фразы с использованием PBKDF2. В обоих режимах ключ шифрования существует только в браузерах отправителя и получателя. В этом документе детально описаны криптографические примитивы, поток данных, модель угроз и честно раскрытые ограничения системы. Замечание о терминологии: система не использует доказательства с нулевым разглашением (ZKP). Когда мы говорим, что сервер обладает «нулевым знанием» о ваших файлах, мы имеем в виду, что — как следствие шифрования на стороне клиента — сервер всегда хранит только шифротекст, который не может расшифровать.
Система безопасной передачи следует строгому разделению клиента и сервера, при котором все криптографические операции выполняются исключительно в браузере:
Сервер намеренно спроектирован как «тупой канал» для зашифрованных данных. Он не имеет сведений о ключе шифрования, парольной фразе или содержимом файла. Это обеспечивается архитектурно, а не только политикой.
Принцип проектирования: сервер должен быть компрометируемым без компрометации пользовательских данных. Даже при полном доступе к базе данных и выполнении серверного кода злоумышленник не может расшифровать переданные файлы.
Всё шифрование файлов использует алгоритм аутентифицированного шифрования AES-256-GCM, доступный через нативный Web Crypto API браузера. Это обеспечивает как конфиденциальность (данные невозможно прочитать), так и целостность (данные невозможно изменить незаметно).
| Параметр | Значение | Обоснование |
|---|---|---|
| Алгоритм | AES-256-GCM | Одобрен NIST, аутентифицированное шифрование со связанными данными (AEAD) |
| Размер ключа | 256 бит | Максимальная длина ключа AES; обеспечивает 128-битный уровень защиты от перебора |
| Размер IV | 96 бит (12 байт) | Рекомендованная NIST длина IV для режима GCM |
| Размер тега | 128 бит (16 байт) | Полноразмерный тег аутентификации для максимальной защиты целостности |
| Генерация IV | crypto.getRandomValues() | Криптографически стойкий генератор случайных чисел |
// Упрощённый поток шифрования (Web Crypto API) const iv = crypto.getRandomValues(new Uint8Array(12)); const salt = crypto.getRandomValues(new Uint8Array(16)); // Производный ключ из парольной фразы (см. Раздел 1.4) const key = await deriveKey(passphrase, salt); // Шифрование файла const ciphertext = await crypto.subtle.encrypt( { name: "AES-GCM", iv: iv }, key, fileArrayBuffer ); // Пакет: [salt (16Б)] + [iv (12Б)] + [ciphertext + tag] const blob = concatenate(salt, iv, ciphertext);
Итоговый зашифрованный blob представляет собой объединение трёх компонентов: 16-байтовой соли (используемой для деривации ключа), 12-байтового IV (используемого для AES-GCM) и шифротекста с присоединённым 16-байтовым тегом аутентификации GCM. Именно этот blob получает и хранит сервер.
Безопасная передача поддерживает два взаимоисключающих режима ключей. Режим выбирается отправителем в момент создания передачи.
В режиме по умолчанию парольная фраза не используется. Браузер напрямую генерирует криптографически случайный 256-битный ключ AES:
crypto.getRandomValues() и crypto.subtle.generateKey().#).Когда отправитель решает задать парольную фразу, ключ выводится из этой парольной фразы с помощью PBKDF2:
| Параметр | Значение | Обоснование |
|---|---|---|
| Алгоритм | PBKDF2 | Рекомендован NIST SP 800-132; широко аудируется |
| Хеш-функция | SHA-256 | Стандартный криптографический хеш; 256-битный вывод |
| Итерации | 600 000 | Соответствует рекомендации OWASP 2023 для PBKDF2-SHA256 |
| Размер соли | 128 бит (16 байт) | Уникальна для каждой передачи; предотвращает атаки по радужным таблицам |
| Длина выходного ключа | 256 бит | Соответствует требованию ключа AES-256 |
async function deriveKey(passphrase, salt) { // Импорт парольной фразы как сырого материала ключа const keyMaterial = await crypto.subtle.importKey( "raw", new TextEncoder().encode(passphrase), { name: "PBKDF2" }, false, ["deriveKey"] ); // Деривация ключа AES-256-GCM return crypto.subtle.deriveKey( { name: "PBKDF2", salt: salt, iterations: 600000, hash: "SHA-256" }, keyMaterial, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt"] ); }
Замечание по безопасности: в режиме B безопасность зависит от энтропии парольной фразы. Рекомендуем парольные фразы из 12+ символов, сочетающих заглавные и строчные буквы, цифры и символы.
Полный жизненный цикл безопасной передачи следует следующему пути:
crypto.getRandomValues().#).crypto.getRandomValues().| Элемент данных | Сервер имеет доступ? | Подробности |
|---|---|---|
| Зашифрованный blob (соль + IV + шифротекст + тег) | Да | Непрозрачные двоичные данные; сервер не может интерпретировать содержимое |
| Идентификатор передачи | Да | Идентификаторы передачи — это значения UUID v4, генерируемые функцией PostgreSQL gen_random_uuid(), что обеспечивает 122 бита криптографической случайности из CSPRNG сервера |
| Исходное имя файла | Да | Хранится в метаданных для отображения получателю |
| Исходный размер файла | Да | Хранится в метаданных для отображения |
| Метка времени истечения | Да | Используется для автоудаления |
| Счётчик / лимит загрузок | Да | Используется для самоуничтожения после прочтения и применения лимита загрузок |
| Идентификатор отправителя (если авторизован) | Да | Привязывает передачу к учётной записи для управления |
| Парольная фраза | Нет — никогда | Никогда не отправляется на сервер; никогда не покидает браузер |
| Производный ключ шифрования | Нет — никогда | Существует только в памяти браузера во время шифрования/расшифрования |
| Содержимое файла в открытом виде | Нет — никогда | До сервера доходит только зашифрованный шифротекст |
| Количество итераций PBKDF2 | Нет | Жёстко задано в клиенте; не передаётся на сервер |
Все безопасные передачи эфемерны по своему замыслу. Опция постоянного хранения отсутствует.
| Опция | Поведение | Применение |
|---|---|---|
| Самоуничтожение после прочтения | Зашифрованный blob удаляется сразу после первой успешной загрузки | На стороне сервера: blob удаляется из хранилища после завершения потока загрузки |
| 1 час | Автоудаление через 1 час независимо от статуса загрузок | На стороне сервера: запланированная задача очистки + проверка истечения при доступе |
| 24 часа | Автоудаление через 24 часа | Так же, как выше |
| 7 дней | Максимальный срок хранения; автоудаление через 7 дней | Так же, как выше |
Когда передача истекает или уничтожается после прочтения, зашифрованный blob окончательно удаляется из Supabase Storage. Запись метаданных сохраняется в течение 30 дней в состоянии «мягкого удаления» (для расследования злоупотреблений), после чего окончательно удаляется. Запись метаданных не содержит ключа шифрования, парольной фразы или какой-либо информации, которую можно было бы использовать для восстановления файла.
| Угроза | Вектор атаки | Меры защиты |
|---|---|---|
| Компрометация сервера | Злоумышленник получает полный доступ к базе данных и хранилищу | Все сохранённые данные — это шифротекст AES-256-GCM. Злоумышленник получает только непрозрачные blob-объекты. Без парольной фразы перебор 256-битного AES вычислительно невозможен. |
| Перехват в сети (MITM) | Злоумышленник перехватывает данные при передаче | Весь трафик использует TLS 1.3. Даже если TLS будет взломан, злоумышленник получит только зашифрованные blob-объекты (то же, что и при компрометации сервера). |
| Слабая парольная фраза | Злоумышленник подбирает короткую или распространённую парольную фразу | PBKDF2 с 600 тыс. итераций делает каждую попытку вычислительно затратной. Парольная фраза из 4 символов всё равно потребует значительных вычислений. Интерфейс применяет минимальную длину парольной фразы и показывает индикатор её надёжности. |
| Перехват парольной фразы | Злоумышленник перехватывает парольную фразу, передаваемую между отправителем и получателем | Парольная фраза передаётся внеполосно (не через нашу систему). Рекомендуем передавать её через канал, отличный от URL передачи. Это ответственность пользователя. |
| Подмена клиентского кода | Злоумышленник изменяет JavaScript, доставляемый пользователю | Все ресурсы доставляются по HTTPS через CDN Vercel. Хеши Subresource Integrity (SRI) защищают от подмены на уровне CDN. Пользователи могут проверить исходный код в DevTools браузера. |
| Извлечение из памяти | Злоумышленник извлекает ключ шифрования из памяти браузера | Ключи Web Crypto API помечаются как неизвлекаемые там, где это возможно. Производные ключи существуют в памяти только во время операции шифрования/расшифрования. Изоляция памяти браузера обеспечивает защиту на уровне ОС. |
| Атака повторного воспроизведения | Злоумышленник повторно использует перехваченный зашифрованный blob | Каждая передача имеет уникальный идентификатор и защищена лимитами загрузок и сроком действия. Передачи с самоуничтожением после прочтения удаляются после первого доступа. |
Честные ограничения: ни одна система безопасности не идеальна. Мы за прозрачное раскрытие известных ограничений.
Privacy-First Cryptographic Document подписание
PDF Pro Whitepaper WP-002 — Архитектура and Безопасность
В этом техническом документе описана архитектура системы приватной подписи от PDF Pro — механизма криптографической подписи документов, при котором PDF-документ никогда не покидает браузер подписанта. Система использует ECDSA с кривой P-256 и хеширование SHA-256 для создания отделённых цифровых подписей. На сервер передаются только хеш документа, криптографическая подпись и публичный ключ. Приватные ключи генерируются, шифруются и хранятся исключительно в браузере пользователя в IndexedDB, защищённом деривацией ключа PBKDF2 (600 000 итераций) и шифрованием AES-256-GCM. В этом документе детально описаны полная архитектура подписания и проверки, модель управления ключами, схема подписанной полезной нагрузки, дизайн аудиторского следа, модель угроз и честно раскрытые ограничения.
Система приватной подписи спроектирована вокруг фундаментального ограничения: PDF-документ никогда не должен передаваться на сервер. Это обеспечивается архитектурно через модель отделённой подписи.
Основная гарантия: сервер никогда не видит, не получает и не обрабатывает PDF-документ. Единственные связанные с документом данные, которые сервер когда-либо получает, — это хеш SHA-256 — значение фиксированного размера в 256 бит, из которого исходный документ восстановить невозможно.
| Параметр | Значение | Обоснование |
|---|---|---|
| Алгоритм подписи | ECDSA (алгоритм цифровой подписи на эллиптических кривых) | NIST FIPS 186-4; компактные подписи; высокая безопасность на бит ключа |
| Кривая | P-256 (secp256r1 / prime256v1) | Одобрена NIST; 128-битный уровень безопасности; широкая поддержка в Web Crypto API |
| Хеш-функция | SHA-256 | NIST FIPS 180-4; 256-битный дайджест; устойчива к коллизиям |
| Размер ключа | 256-битный приватный ключ, 512-битный публичный ключ (несжатый) | Стандарт для P-256; эквивалентно ~3072-битному RSA |
| Размер подписи | 64 байта (r: 32 байта, s: 32 байта) | Компактный; подходит для хранения и передачи |
| Формат подписи | IEEE P1363 (сырое r || s) | Нативный вывод Web Crypto API; для хранения кодируется в base64url |
Кодирование подписи: ECDSA с P-256 в Web Crypto API создаёт сырую 64-байтовую подпись, состоящую из двух 32-байтовых целых чисел (r || s) в формате big-endian фиксированной ширины. Этот сырой вывод кодируется в base64url для хранения. Это НЕ DER-кодирование — это формат IEEE P1363, который Web Crypto API создаёт нативно.
// 1. Хеширование PDF-документа (на стороне клиента) const fileBuffer = await file.arrayBuffer(); const hashBuffer = await crypto.subtle.digest("SHA-256", fileBuffer); const hashHex = Array.from(new Uint8Array(hashBuffer)) .map(b => b.toString(16).padStart(2, '0')).join(''); // 2. Подписание хеша приватным ключом (на стороне клиента) const signature = await crypto.subtle.sign( { name: "ECDSA", hash: "SHA-256" }, privateKey, // CryptoKey из IndexedDB (расшифрованный) hashBuffer ); // 3. Отправка на сервер: хеш + подпись + публичный ключ (НЕ сам PDF) await submitSignature({ documentHash: hashHex, signature: base64Encode(signature), publicKey: exportedPublicKeyJWK });
| Тип ключа | Контекст | Жизненный цикл | Хранение |
|---|---|---|---|
| Эфемерный | Гостевые пользователи (не вошедшие в систему) | Генерируется на каждую сессию; уничтожается при закрытии вкладки | Только в памяти (объект CryptoKey); никогда не сохраняется |
| Постоянный | Авторизованные пользователи (вошедшие в систему) | Генерируется один раз; сохраняется между сессиями; может быть отозван | IndexedDB (зашифрован с помощью PBKDF2 + AES-256-GCM) |
// Генерация пары ключей ECDSA P-256 (Web Crypto API) const keyPair = await crypto.subtle.generateKey( { name: "ECDSA", namedCurve: "P-256" }, true, // extractable (нужно для шифрования + хранения) ["sign", "verify"] ); // Экспорт публичного ключа в формате JWK для регистрации на сервере const publicKeyJWK = await crypto.subtle.exportKey("jwk", keyPair.publicKey); // Экспорт приватного ключа в формате JWK для зашифрованного хранения const privateKeyJWK = await crypto.subtle.exportKey("jwk", keyPair.privateKey);
Формат публичного ключа: публичные ключи экспортируются и хранятся в формате JWK (JSON Web Key). Отпечаток ключа вычисляется как SHA-256 от канонического JWK, содержащего только публичные поля {crv, kty, x, y} с ключами, отсортированными по алфавиту.
Постоянные приватные ключи никогда не хранятся в открытом виде. Перед записью в IndexedDB приватный ключ (экспортированный в виде JWK JSON) шифруется по той же схеме, что и в системе безопасной передачи:
| Параметр | Значение |
|---|---|
| Деривация ключа | PBKDF2-SHA256, 600 000 итераций |
| Соль | Случайные 16 байт (на каждый ключ) |
| Шифрование | AES-256-GCM |
| IV | Случайные 12 байт (на каждое шифрование) |
| Вход | JWK приватного ключа (JSON-строка в UTF-8) |
| Выход, сохраняемый в IndexedDB | { salt, iv, ciphertext, publicKeyJWK, keyId, createdAt } |
// Структура записи IndexedDB для зашифрованного ключа подписи { "keyId": "uuid-v4-unique-identifier", "publicKey": { /* формат JWK, незашифрованный */ }, "encryptedPrivateKey": { "salt": "base64-encoded-16-bytes", "iv": "base64-encoded-12-bytes", "ciphertext": "base64-encoded-aes-gcm-ciphertext" }, "algorithm": "ECDSA", "curve": "P-256", "createdAt": "2026-04-16T00:00:00.000Z", "userId": "supabase-auth-user-id" }
Восстановление ключа: если пользователь забывает свою парольную фразу подписи, приватный ключ восстановить невозможно. У нас нет парольной фразы, производного ключа или какого-либо механизма обхода защиты PBKDF2 + AES-GCM. Пользователям следует экспортировать резервные копии ключей.
PDF Pro использует модель отделённой подписи, что означает, что подпись хранится отдельно от документа. Это ключевой механизм конфиденциальности: документ никогда не покидает устройство подписанта.
Криптографическая гарантия: SHA-256 — это односторонняя хеш-функция. Имея только хеш e3b0c44298fc1c14..., она обеспечивает сильную криптографическую гарантию (при условиях безопасности ECDSA и SHA-256), что исходный документ не может быть восстановлен. Хеш не раскрывает ничего о содержимом, длине или структуре документа, кроме подтверждения идентичности при повторном хешировании.
Следующая JSON-схема определяет полную запись подписи, хранимую на сервере:
{
"schemaVersion": "1.0",
"signatureId": "uuid-v4",
"documentHash": "sha256-hex-64-chars",
"hashAlgorithm": "SHA-256",
"signature": "base64-encoded-ecdsa-signature",
"signatureAlgorithm": "ECDSA",
"curve": "P-256",
"publicKey": {
"kty": "EC",
"crv": "P-256",
"x": "base64url-encoded-x-coordinate",
"y": "base64url-encoded-y-coordinate"
},
"signer": {
"identityLevel": "authenticated | self-asserted",
"displayName": "string or null",
"email": "string or null",
"userId": "supabase-uid or null"
},
"timestamp": "ISO-8601-UTC",
"metadata": {
"fileName": "original-file-name.pdf",
"fileSize": 123456,
"pageCount": 12,
"clientVersion": "2.0.0",
"userAgent": "browser-user-agent-string"
},
"auditChain": {
"previousEventHash": "sha256-of-previous-audit-event or null",
"eventHash": "sha256-of-this-record"
}
}Проверка подписи — двухэтапный процесс: криптографическая проверка на стороне клиента с последующей серверной перекрёстной сверкой.
const isValid = await crypto.subtle.verify( { name: "ECDSA", hash: "SHA-256" }, importedPublicKey, signatureBuffer, hashBuffer );
isValid === true, подпись криптографически действительна: документ не был изменён с момента подписания, и подпись создана владельцем соответствующего приватного ключа.| Результат | Значение |
|---|---|
| Действительна (авторизована) | Подпись криптографически действительна И публичный ключ принадлежит зарегистрированному, авторизованному пользователю PDF Pro. |
| Действительна (самозаявлена) | Подпись криптографически действительна, но идентичность подписанта самозаявлена (гостевой пользователь или непроверенное имя). |
| Недействительна | Криптографическая проверка не пройдена. Документ был изменён после подписания или подпись повреждена. |
| Отозвана | Подпись была действительной, но была явно отозвана подписантом. |
| Подпись не найдена | Запись подписи для этого хеша документа не существует. |
Каждое событие подписания и проверки регистрируется в аудиторском следе с защитой от подделки. События связаны хеш-цепочкой: каждое событие содержит хеш SHA-256 предыдущего события, образуя цепочку только для добавления, аналогичную блокчейну.
| Тип события | Триггер | Записываемые данные |
|---|---|---|
KEY_REGISTERED | Пользователь регистрирует новый публичный ключ | JWK публичного ключа, идентификатор пользователя, метка времени |
DOCUMENT_SIGNED | Пользователь подписывает документ | Хеш документа, подпись, публичный ключ, идентичность подписанта, метка времени |
SIGNATURE_VERIFIED | Любой пользователь проверяет подпись | Хеш документа, результат проверки, информация о проверяющем (если авторизован), метка времени |
SIGNATURE_REVOKED | Подписант отзывает подпись | Идентификатор подписи, причина отзыва, метка времени |
KEY_REVOKED | Пользователь отзывает публичный ключ | Идентификатор публичного ключа, причина отзыва, метка времени |
// Каждое событие аудита включает: { "eventId": "uuid-v4", "eventType": "DOCUMENT_SIGNED", "timestamp": "ISO-8601-UTC", "data": { /* полезная нагрузка, специфичная для события */ }, "previousEventHash": "sha256-of-previous-event-json", "eventHash": "sha256-of-this-event-json-without-eventHash" } // Обнаружение подделки: для проверки цепочки вычислите: // SHA-256(JSON.stringify(событие без поля eventHash)) // и убедитесь, что значение совпадает с eventHash. // Затем убедитесь, что previousEventHash совпадает с eventHash предыдущего события.
Если какое-либо событие в цепочке изменено, все последующие хеш-связи нарушатся, что делает подделку сразу обнаруживаемой. Это обеспечивает надёжную гарантию целостности аудиторского следа.
Важное ограничение: хеш-цепочка делает подделку обнаруживаемой в пределах зарегистрированной последовательности событий. Однако администратор базы данных с прямым доступом теоретически мог бы удалить и перестроить цепочку. Более сильные гарантии потребовали бы внешней метки времени или подтверждения третьей стороной, что в этой версии не реализовано.
| Уровень | Требования | Свойства доверия | Сценарий использования |
|---|---|---|---|
| Авторизованный | Вошедший в систему пользователь PDF Pro с проверенным email; постоянная пара ключей, зарегистрированная за учётной записью | Email подтверждён через Supabase Auth; публичный ключ привязан к авторизованной учётной записи; аудиторский след связан с идентификатором пользователя | Деловые документы, договоры, формальные соглашения |
| Самозаявленный | Гостевой пользователь или авторизованный пользователь с самостоятельно введённым именем; эфемерная или постоянная пара ключей | Криптографическая целостность гарантирована; идентичность подписанта самозаявлена и независимо не проверена | Быстрое подписание, личные документы, неформальные соглашения |
Замечание: в интерфейсе продукта «self_asserted» может отображаться как «Самозаявленная идентичность» или «Гостевое подписание». «authenticated» может отображаться как «Авторизованный аккаунт». Это отображаемые метки для одних и тех же базовых уровней идентификации.
Привязка идентичности: «авторизованная» подпись означает, что публичный ключ зарегистрирован за учётной записью PDF Pro с проверенным email. Это НЕ означает, что реальная идентичность подписанта проверена через государственный документ, биометрию или личную верификацию. Мы не выполняем проверки KYC (Know Your Customer).
| Угроза | Вектор атаки | Меры защиты |
|---|---|---|
| Атака повторного воспроизведения | Злоумышленник копирует действительную подпись и применяет её к другому документу | Подпись привязана к SHA-256 хешу конкретного документа. У другого документа будет другой хеш, и проверка ECDSA не пройдёт. |
| Подделка | Злоумышленник создаёт действительную подпись без приватного ключа | Безопасность ECDSA P-256 основана на проблеме дискретного логарифма на эллиптических кривых (ECDLP). Подделка подписи без приватного ключа вычислительно невозможна (128-битный уровень безопасности). |
| Подмена ключа | Злоумышленник регистрирует свой публичный ключ и заявляет, что подпись поставлена кем-то другим | Авторизованные подписи привязывают публичный ключ к проверенному email. Аудиторский след фиксирует, какой ключ подписал какой документ. События регистрации ключей связаны хеш-цепочкой. |
| Подмена документа | Злоумышленник изменяет подписанный PDF и заявляет, что подпись остаётся действительной | Любое изменение PDF меняет его SHA-256 хеш. Существующая подпись не пройдёт проверку по новому хешу. Поиск коллизии (другой документ с тем же хешем) требует ~2^128 операций. |
| Кража приватного ключа | Злоумышленник извлекает зашифрованный приватный ключ из IndexedDB | Приватный ключ зашифрован с помощью AES-256-GCM на ключе, полученном через PBKDF2 (600K итераций). Без парольной фразы расшифровать ключ вычислительно невозможно. |
| Компрометация сервера | Злоумышленник получает полный доступ к серверу | На сервере находятся только публичные ключи и записи подписей. Приватные ключи никогда не достигают сервера. Злоумышленник не может подделывать новые подписи. Он может удалить или изменить существующие записи, но разрывы хеш-цепочки будут обнаружены. |
| Подделка аудиторского следа | Злоумышленник изменяет события аудиторского следа на сервере | События связаны хеш-цепочкой: изменение любого события разрывает цепочку, начиная с этой точки. Независимые средства проверки могут обнаружить разрывы цепочки. |
Чем подписи PDF Pro НЕ являются:
Наше обязательство: мы строим безопасность через архитектуру, а не через маркетинг. Эти технические документы описывают, как именно работают наши системы, включая их ограничения. Мы считаем, что заботящиеся о безопасности пользователи заслуживают полной технической прозрачности. Если у вас есть вопросы по какому-либо аспекту нашей архитектуры, свяжитесь с нами по адресу info@webdesign9.com.