Эй, вы все!

Это часть 2 службы передачи файлов Bondesque, где вы можете отправить кому-нибудь файл, и его можно будет загрузить только один раз.

Просто краткий обзор проекта.

В качестве интересного эксперимента мы пытаемся создать ориентированное на конфиденциальность веб-приложение для одноразовой передачи файлов, получившее название Записать после прочтения. См. Часть 1 о начальной архитектуре.

Поскольку конфиденциальность и безопасность являются центральным пунктом всего, что нам нужно, чтобы оснастить наше маленькое приложение шифрованием. Первая часть этого руководства была всего лишь прототипом.

Как гласит старая поговорка:Если что-то не стоит шифрования, оно просто бесполезно.

Вы просто не можете назвать его MVP, если в нем нет шифрования, хотя наш прототип вроде бы работал.

Поэтому, когда вы думаете о безопасности, вы должны думать о плане атаки. Перекладывая ответственность с базы данных (хранилища) на шифрование, вы устраняете риск утечки данных. Действительно, в этой настройке вам нужен только проверенный исходный код для внешнего интерфейса и все.

Это единственная критическая часть этого проекта.

Внедрение шифрования

Мы решили реализовать шифрование в браузере, а это означает, что весь aes256 будет работать на стороне клиента. Расшифровка также будет выполняться в браузере, а это означает, что и серверная часть, и база данных будут просто средством для пересылки и хранения данных. Настоящая магия происходит в браузере.

Прежде всего, мы хотим немного изменить UX.

Нам нужен многоразовый модальный компонент ввода пароля. Мы собираемся использовать его как в пользовательском интерфейсе загрузки, так и загрузки.

Вот src/components/PasswordModal.js

Мы используем хранилище избыточности для сохранения пароля (просто для удовольствия мы также используем обратные вызовы).

Итак, в onClick мы обновляем пароль, когда пользователь вводит пароль в текстовое поле. В onSubmit мы также передаем пароль вызывающей стороне, если определена функция щелчка.

В drag-n-drop.js мы настраиваем события после того, как пользователь предоставит файл для загрузки:

Это представлено тремя функциями:

Эта функция сохраняет информацию о файле, предоставленном пользователем, и запускает модальный пароль.

Этот фрагмент кода отвечает за модальное окно:

Мы также определяем onPasswordInput следующим образом:

Таким образом, когда пользователь вводит пароль, модальное окно завершается, а затем код создает хэш пароля (await hashSha256(p)) и после этого вызывает handleFilesFinal.

hashSha256() определен в src/utils/hex.js.

Он использует дайджест библиотеки Crypto Subtle SHA-256, который возвращает хэш пароля. А затем он преобразуется в массив целых чисел, который является обязательным типом для остального кода.

Теперь мы подошли к последней функции, которая метко названа handleFilesFinal:

Он имеет небольшие изменения по сравнению с предыдущей версией.

Сначала мы используем функцию EncryptFile, которую мы определили в src/components/EncryptFile.js:

Экспорт по умолчанию — это функция, которая создает IV (вектор инициализации) — случайный вектор соли, который используется в алгоритме AES256 для значительного улучшения его случайности. Нам нужно сохранить его на потом, так как он также будет использоваться для расшифровки. Это часть метаданных файла.

Затем мы используем FileReader, чтобы браузер загрузил файл… в память. А теперь сложная часть. Поскольку в 2022 году потоки не очень хорошо поддерживаются в браузерах, нам нужно припарковать файл в памяти браузера. Но это нормально, поскольку мы установили ограничение на загрузку в 1 МБ: это экономит место в хранилище, и я думаю, что этого достаточно для наших целей. В конце концов, если у вас есть необходимость отправить, например, зашифрованные видеоклипы, вы обязательно воспользуетесь другими способами.

Итак, файл зашифрован с помощью шифрования Crypto Subtle. Функция fileEncrypt является обещанной оболочкой вокруг этой логики. Итак, наконец, мы получаем зашифрованный двоичный буфер.

Затем мы используем FileAPI для создания Blob, необходимого для formData, и отправляем эти данные вместе с именем файла и IV.

Итак, на стороне API мы вносим небольшие изменения для учета новых метаданных:

То же самое касается маршрута /get_info, который отправляет метаданные во внешний интерфейс:

Итак, теперь мы можем отправить зашифрованный файл на сервер.

В Pt1 после загрузки файла мы получили прямую ссылку на API, по которой можно скачать файл.

Однако на этот раз мы не можем использовать прямой API, потому что данные, которые будут загружены, бесполезны: они зашифрованы.

Поэтому вместо этого нам теперь нужно создать новый маршрут во внешнем интерфейсе: Download.

Вот как это выглядит:

Мы используем тот же модальный пароль, но с другим заголовком, и мы связываем его с функцией onPasswordInput.

Сначала мы используем хук useSearchParams, чтобы получить параметр «?id=…xxx» для идентификатора файла.

Мы useEffect получаем метаданные для этого идентификатора файла, и если все в порядке, мы продолжаем. Если нет, пользователю отображается ошибка.

Когда пользователь вводит пароль, вызывается onPasswordInput:

Эта функция скрывает модальное окно пароля и пытается расшифровать файл. Если это не удается (что обычно означает, что пароль неверен), отображается ошибка.

В противном случае он использует saveAs из библиотеки «file-saver», чтобы сохранить файл, как если бы он был загружен.

Но где его на самом деле скачать?

Взгляните на DectyptFile.js:

Вот оно. Фактическая загрузка осуществляется через выборку!

Затем полученный большой двоичный объект, IV из метаданных и предоставленный пользователем ключ отправляются в decryptFile, который использует Crypto Subtle Decrypt для расшифровки большого двоичного объекта.

Опять сталкиваемся с той же проблемой: файл сначала скачивается в память, а потом расшифровывается и сохраняется в закачки.

В API загрузки нет существенных изменений, потому что шифрование или нет, это все еще просто драйвер хранилища.

Итак, в этой части мы изменили проект, ввели модальные окна и значительно улучшили безопасность с помощью шифрования AES256 на стороне клиента. Таким образом, мы можем назвать это MVP!

Поздравляю, ребята!

В следующей части мы попытаемся развернуть приложение в облаке, чтобы использовать уровень бесплатного пользования Azure и сделать его доступным из Интернета по общедоступной ссылке.

Исходный код находится @ https://github.com/0xd0a/burn-after-reading-download/tree/password-protect