Как tapeshelf шифрует видео и почему не хранит ключи

Приватное видео на tapeshelf видят владелец и те, кому он сам его дал. Расскажем, как приложение за этим следит и где его защита заканчивается.
От загрузки до потока
Браузер загружает файл прямо в закрытый бакет S3-совместимого хранилища, частями по 16 МБ. Если связь оборвётся, загрузка продолжится с того же места. Через наши API-серверы файл не проходит.
Фоновый обработчик (воркер) проверяет файл через ffprobe и убирает из него метаданные: место и время съёмки, камеру, автора, комментарии. Файл пересобирается без перекодирования, так что картинка и звук не меняются. Скачивают уже этот очищенный файл. У видео, загруженных до 18 октября 2026 года, метаданные остались.
Видео в H.264 до 1080p, если оно проходит ещё несколько проверок, достаточно переупаковать, это секунды. Остальное перекодируется в H.264 до 1080p. Потом ffmpeg режет результат на шестисекундные сегменты HLS. Safari играет их сам, остальные браузеры через hls.js.
Шифруем сегменты
ffmpeg умеет шифровать HLS и сам, но ключ ему нужен файлом на диске. Мы не хотели, чтобы ключ попадал на диск, поэтому сегменты шифрует воркер:
- каждый сегмент шифруется AES-128 в режиме CBC, как описано в стандарте HLS;
- ключ — 16 байт, у каждого видео свой, и существует он только в памяти воркера, пока тот работает;
- вектор инициализации — порядковый номер сегмента: его плеер HLS подставляет сам, если плейлист не указывает другой.
В закрытый бакет уходят только зашифрованные сегменты и их плейлист. Временные файлы воркер удаляет, как только задача закончена.
Ключи, которых нет
После этого ключ нигде не хранится. Вместо случайного ключа, который пришлось бы где-то держать и стеречь, tapeshelf каждый раз вычисляет его заново:
ключ видео = HKDF-SHA256(мастер-ключ, "video:" + id видео + версия) → 16 байт
Версия растёт при монтаже, и у отредактированного видео ключ новый.
Мастер-ключ — один секрет в конфигурации сервера. Его нет ни в базе данных, ни в хранилище, ни в бэкапах. В копии базы нет ни одного ключа видео, в бакете лежат только зашифрованные сегменты, так что понадобится ещё и конфигурация сервера.
Потеряете мастер-ключ, и ни одно видео больше не воспроизведётся, бэкап базы тут не поможет. Особенно это важно, если вы держите tapeshelf на своём сервере.
Три запроса при просмотре
- Начало просмотра. API проверяет, можно ли вам смотреть: вы автор, видео стоит на вашей полке или оно публичное. Тогда API отдаёт адрес плейлиста с подписанным токеном на час. В токене записаны видео и вы (или никто, если вы не вошли).
- Плейлист. API снова проверяет токен и права и переписывает плейлист на лету. Каждый сегмент превращается в подписанную ссылку на хранилище, а строка с ключом указывает обратно на API.
- Ключ. Токен должен быть выписан на это видео. Для приватного видео ещё и сессия, под которой вы вошли, должна принадлежать тому, на кого выписан токен. Только тогда API вычисляет ключ и отдаёт 16 байт.
У временной ссылки нет аккаунта для проверки, поэтому на её странице токен выписан на саму ссылку. Каждый запрос проверяет, что ссылка действует и видео в ней есть, а дольше ссылки токен не живёт. Видео, полученные гостем в обмене, так же играют на странице сделки в течение недели.
Решение о доступе кэшируется на минуту, чтобы двухчасовой фильм не дёргал базу на каждом запросе. Удалённое видео в течение этой минуты ещё можно запустить, а истёкшая или отозванная временная ссылка перестаёт работать сразу. В обоих случаях уже играющий плеер держит у себя ключ и ссылки на сегменты и может играть дальше.
Ссылки на сегменты тоже подписаны и истекают через час или позже. Плеер загружает плейлист один раз, и короткие ссылки ломали бы даже обычную паузу. Если ссылка истекла, плеер тихо запрашивает просмотр заново и продолжает с того же места. А без ключа сегмент всё равно бесполезен.
Почему нет файла, который можно унести
Плеер никогда не получает ссылку на сам файл видео. У него есть плейлист, привязанный к токену, зашифрованные куски и ключ за проверкой. Скопируйте адрес плейлиста приватного видео в браузер друга, и ничего не заиграет: ключ требует вашей сессии.
Скачивание — отдельное действие. Вошедшие скачивают то, что им можно смотреть. Без аккаунта скачать можно только по временной ссылке, которую вам прислали, или то, что вы получили как гость в обмене, в течение недели. Ссылка на скачивание живёт две минуты.
С картинками иначе. Обложки, 16 кадров из каждого видео и собранное из них покадровое превью лежат в публичном бакете под длинными случайными именами, и список файлов получить нельзя. Превью приватного видео видно намеренно: по нему посетитель понимает, что он просит.
От чего это защищает
- Тот, у кого нет доступа, не получает ни плейлиста, ни сегмента, ни ключа.
- Утёкшая ссылка на сегмент — зашифрованный шум. Утёкшая ссылка на плейлист приватного видео не работает без сессии того, кому её выдали, и в любом случае истекает через час.
- Украденная копия базы не раскрывает ни одного ключа.
От чего не защищает
- Кто может смотреть, тот может и записать. Человек с доступом может записать экран или сохранить поток подходящими инструментами. Ключ ему выдан законно. DRM в tapeshelf нет. Настоящий DRM вроде Widevine или FairPlay зависит от коммерческих лицензионных серверов и всё равно бессилен против камеры, направленной на экран.
- Те, с кем вы поделились. Если вы поделились видео или обменялись им, у человека остаётся постоянная копия, и он может скачать файл. Скачать может и любой, кто открыл вашу временную ссылку, пока она действует. Что будет с файлом дальше, от нас уже не зависит. Делитесь с теми, кому доверяете.
- Сам сервер. Это не сквозное шифрование. Ключи вычисляет сервер, поэтому тот, кто им управляет, может воспроизвести любое видео. На tapeshelf.com это мы, и приватные видео мы открываем, когда это нужно для работы сервиса и модерации. На своём сервере это его администратор.
Коротко всё это собрано на странице о безопасности. Если хочется проверить самому, код открыт, и его можно будет запустить у себя.