Содержание статьи +
- TL;DR
- Почему это важно
- Какую одну проблему решает доставка ключей
- Поток получения лицензии, шаг за шагом
- Три диалекта одного рукопожатия
- Лицензионный прокси: где живут права и почему не в плеере
- Где живёт каждый секрет – и кто не должен видеть его никогда
- Сессионная против постоянной лицензии: онлайн и офлайн
- Ротация ключей для прямых эфиров
- Частая ошибка: лимит без проверки и секрет в клиенте
- Где здесь Фора Софт
- Ключевые выводы
- Что почитать дальше
TL;DR
Зашифрованное видео бесполезно без ключа, и весь смысл защиты контента в том, чтобы доставить этот ключ нужному устройству на время одного воспроизведения – и больше никуда. Делает это лицензионный сервер: плеер собирает запрос лицензии через браузерный стандарт Encrypted Media Extensions (EME, рекомендация W3C), сервер проверяет права и возвращает ключ, упакованный так, что распаковать его может только доверенный модуль расшифровки внутри устройства. У трёх систем DRM свои диалекты этого обмена – у Widevine запрос лицензии, у PlayReady challenge, у FairPlay пара SPC/CKC, – но форма одна: запрос → проверка → ключ. Эта статья простыми словами разбирает поток получения лицензии, роль лицензионного прокси и сервиса прав, ротацию ключей для прямых эфиров, разницу между сессионной и постоянной лицензией и – главное – где живёт каждый секрет и кто не должен видеть его никогда.
Почему это важно
Если вы основатель, продакт-менеджер или впервые отвечаете за стриминг как CTO, доставка ключей – тот слой, где защита контента либо работает, либо тихо вас подводит: ошибка здесь либо роняет старт воспроизведения у всей аудитории, либо отдаёт ваш контент-ключ туда, откуда его скопируют. Сам сервер лицензий вы, скорее всего, не пишете – его даёт multi-DRM-вендор или облако, – но спроектировать вокруг него проверку прав, выдержать пик премьеры и не дать ключу утечь обязаны вы. Прочитав статью, вы сможете объяснить, как плеер получает ключ, нарисовать границу между вашим сервисом прав и сервером лицензий и назвать вслух три места, где контент-ключ появляться не должен никогда. Это прямое продолжение CENC, CTR и CBCS: там мы зашифровали байты один раз; здесь доставляем ключ, который их открывает.
Какую одну проблему решает доставка ключей
Начнём с задачи, потому что вся конструкция понятна только из неё. В статье CENC, CTR и CBCS мы зашифровали каталог симметричным ключом – контент-ключом (content encryption key, CEK): одно секретное 128-битное число, которым байты и запираются, и отпираются. Зашифрованные сегменты лежат на CDN открыто для скачивания; украсть их может кто угодно. Бесполезны они ровно до тех пор, пока у вора нет ключа. Значит, вся защита сводится к одному вопросу: как передать контент-ключ миллиону легальных устройств и ни одному пиратскому.
Наивных решений два, и оба ломаются. Положить ключ рядом с видео на CDN – всё равно что оставить ключ в замке. Зашить ключ в приложение плеера – чуть лучше, но любой, кто вскроет приложение, достанет ключ и расшифрует весь каталог. Защита требует, чтобы ключ доставлялся вживую, по одному воспроизведению, индивидуально каждому устройству, и приходил завёрнутым так, что развернуть его умеет только доверенное «железо» именно этого устройства. Систему, которая это делает, и называют лицензионным сервером (license server), а её ответ – лицензией: маленьким сообщением, несущим контент-ключ, запертый под конкретное устройство, плюс правила, на что это устройство имеет право.
Держите в голове разделение из статьи про CENC: слой шифрования общий, слой лицензии – свой для каждой системы. Зашифрованные байты одни на всех (одна упаковка по cbcs). А вот доставка ключа у Widevine, PlayReady и FairPlay устроена по-разному, и именно ей посвящена эта статья.
Поток получения лицензии, шаг за шагом
В браузере весь обмен идёт через один стандарт – Encrypted Media Extensions (EME), рекомендацию W3C, которая даёт плееру договариваться с системой защиты, не зная её внутренностей. Разберём поток по шагам; он почти дословно повторяется и в нативных приложениях, и на смарт-ТВ.
Во-первых, плеер начинает грузить защищённый поток и натыкается на пометку «здесь зашифровано». Браузер поднимает событие encrypted, передавая плееру initialization data – небольшой блок, который, помимо прочего, называет идентификатор нужного ключа.
Во-вторых, плеер просит у браузера объект MediaKeys (через requestMediaKeySystemAccess) и открывает сессию ключей (MediaKeySession) – она представляет «жизнь одной лицензии и её ключей». На этой сессии плеер вызывает generateRequest(), передавая модулю расшифровки ту самую init data.
В-третьих, модуль расшифровки – Content Decryption Module (CDM), доверенный компонент устройства, который и хранит, и применяет ключи, – собирает запрос лицензии. Это не просто «дай ключ по такому-то ID»: запрос подписан личностью устройства, так что сервер может убедиться, что говорит с настоящим, непровзломанным «железом». Браузер отдаёт этот запрос плееру через событие message.
В-четвёртых, и это ключевой момент архитектуры: запрос лицензии передаёт ваша JavaScript-страница, а не браузер напрямую. Плеер берёт байты запроса и шлёт их на URL сервера лицензий обычным сетевым вызовом (fetch/XHR). Раз транспорт – ваш код, вы можете приложить к нему что угодно: токен входа, идентификатор подписки, маркер сеанса. Это и есть крючок, на котором держится вся проверка прав, – к нему вернёмся.
В-пятых, сервер лицензий проверяет запрос – личность устройства, целостность, права – и, если всё чисто, достаёт контент-ключ из защищённого хранилища, заворачивает его под этот конкретный CDM и возвращает лицензию. Плеер передаёт её обратно модулю через update(). Теперь, и только теперь, ключ оказывается внутри CDM, расшифровка идёт прямо перед декодированием, и видео играет. Поднимается событие keystatuseschange – «ключ годен».
Главное, что стоит вынести: наружу, в ваш код и по сети, контент-ключ в открытом виде не выходит ни на одном шаге. Запрос подписан устройством; лицензия зашифрована под устройство; распаковка происходит внутри CDM. Ваше приложение перекладывает запечатанные конверты и не видит, что внутри.
Вот тот же поток минимальным кодом в браузере – обратите внимание, что код приложения только перевозит байты, никогда их не читая (значения вымышлены):
// Плеер увидел зашифрованный поток
video.addEventListener('encrypted', async (event) => {
const session = mediaKeys.createSession('temporary'); // тип сессии — см. ниже
session.addEventListener('message', async (e) => {
// e.message — запрос лицензии, собранный CDM. Внутрь мы не смотрим.
const res = await fetch('https://license.example.com/widevine', {
method: 'POST',
headers: { 'Authorization': 'Bearer eyJfake.entitlement.token' }, // крючок прав
body: e.message
});
const license = await res.arrayBuffer();
await session.update(license); // ключ уходит внутрь CDM, не в наш код
});
await session.generateRequest(event.initDataType, event.initData);
});Три диалекта одного рукопожатия
Форма обмена – запрос, проверка, завёрнутый ключ – у всех трёх систем одна. Различаются названия и форматы сообщений. Знать их полезно, потому что они встретятся вам в дашбордах вендоров и логах. (Карта «какая система на каком устройстве» – тема статьи Три системы DRM; здесь берём её как данность.)
Google Widevine. Модуль расшифровки собирает зашифрованный, подписанный устройством запрос лицензии и шлёт его на сервер лицензий Widevine; тот возвращает лицензию с ключом. Личность устройства закладывается на заводе: корень доверия – keybox, а провижининг-сервер Google выдаёт устройству сертификат. На устройствах уровня L1 ключи и расшифрованные кадры живут только в аппаратной доверенной среде (TEE) и не видны основному процессору – поэтому ключ Widevine на хорошем устройстве не покидает «железо» вообще.
Microsoft PlayReady. Клиент формирует license challenge (XML-сообщение), приложенный к нему идентификатор ключа (KID) называет нужный ключ; сервер лицензий генерирует контент-ключ под этот KID и возвращает лицензию, привязанную к устройству. У каждого клиента – уникальное доказательство, аутентифицирующее его перед сервером.
Apple FairPlay. Самый узнаваемый диалект. Устройство создаёт Server Playback Context (SPC) – зашифрованный запрос, который сумело собрать только это устройство и только для этой сессии. SPC уходит на сервер лицензий, где модуль Key Security Module (KSM) расшифровывает его, сверяет хеш сертификата с сертификатом приложения (Application Certificate) от Apple, достаёт контент-ключ из хранилища и заворачивает его в Content Key Context (CKC) – ответ, который и возвращается приложению. В мире HLS этот обмен ещё и размечается в манифесте: тег EXT-X-KEY (формат HLS, RFC 8216) несёт METHOD=SAMPLE-AES, KEYFORMAT="com.apple.streamingkeydelivery" и URI со схемой skd://, указывающий плееру, куда идти за ключом.
| Система | Что шлёт клиент | Модуль на сервере | Что возвращается | Какие устройства |
|---|---|---|---|---|
| Widevine | Запрос лицензии (подписан устройством) | Сервер лицензий Widevine | Лицензия с ключом | Android, Chrome, многие ТВ |
| PlayReady | License challenge (XML) + KID | Сервер лицензий PlayReady | Лицензия под устройство | Windows, Xbox, многие ТВ |
| FairPlay | SPC (Server Playback Context) | KSM (Key Security Module) | CKC (Content Key Context) | Только устройства Apple |
Разные слова, одно рукопожатие: устройство доказывает, кто оно; сервер выдаёт ключ, который сможет развернуть только это устройство. Чтобы один воркфлоу обслуживал все три (а это базовый современный паттерн), нужен multi-DRM-сервис, говорящий на всех трёх диалектах из одной упаковки, – он разобран в Multi-DRM: один воркфлоу, любое устройство.
Лицензионный прокси: где живут права и почему не в плеере
Вернёмся к четвёртому шагу – тому, где запрос лицензии везёт ваше приложение. Это не деталь реализации, а главная точка контроля во всей защите. Сервер лицензий умеет выдать ключ; решать, кому его выдавать, – не его работа. Решает это ваша бизнес-логика: активна ли подписка, входит ли тайтл в план, не превышен ли лимит одновременных потоков, разрешён ли регион. Эта логика называется сервисом прав (entitlement service), и жить она обязана на сервере.
Чистый и проверенный паттерн – лицензионный прокси: для плеера он выглядит как сервер лицензий, но на деле стоит перед настоящим. Плеер шлёт запрос лицензии на прокси без приложенного секрета; прокси проверяет права (по токену входа, куке или заголовку), выпускает свежий короткоживущий токен прав и пересылает запрос вместе с токеном уже настоящему серверу лицензий. Тот выдаёт ключ, только если токен верен.
Почему так, а не «токен прямо из плеера на сервер лицензий»? Из-за того, где обязаны жить секреты. Ключ связи между вашим сервисом прав и сервисом лицензий нельзя класть в клиент: утечёт – и злоумышленник начнёт штамповать валидные токены прав от вашего имени и тянуть ключи на любой контент. Поэтому логика прав исполняется на сервере и в браузер не уезжает. Токены делаются короткоживущими (секунды или минуты) и одноразовыми, чтобы их нельзя было переиспользовать. Бонус прокси-схемы: время жизни токена становится чисто серверной заботой, и тихие сбои старта из-за протухших токенов исчезают – плеер видит эндпоинт, который всегда отвечает.
Это и причина, по которой проверку прав нельзя «срезать» на клиенте ради скорости. Любая проверка в JavaScript-плеере обходится тем, кто откроет инструменты разработчика. Сервер лицензий – последняя дверь к ключу; держать её должна серверная логика, а не клиент, которому пользователь полностью управляет.
Где живёт каждый секрет – и кто не должен видеть его никогда
Соберём все секреты на одной карте, потому что это и есть практический итог статьи. У защищённого потока несколько разных секретов, и у каждого – своё место, дальше которого ему хода нет.
Контент-ключ (CEK) – само 128-битное число, открывающее байты. Он живёт в трёх местах и больше нигде: в хранилище ключей (key server, как правило поверх KMS или аппаратного модуля HSM), куда упаковщик ходит за ним при шифровании через защищённый обмен CPIX (Content Protection Information Exchange Format, спецификация DASH-IF; на нём же построен SPEKE от AWS); в сервере лицензий, который достаёт его, чтобы завернуть под устройство; и, на миг воспроизведения, внутри CDM/TEE устройства. Всё. В манифесте его нет, в открытых файлах на CDN нет, в JavaScript-плеере нет, в логах приложения нет.
Идентификатор ключа (default_KID) – публичное имя ключа, а не сам ключ. Как номер ячейки в камере хранения, его спокойно кладут в манифест и в бокс tenc (по ISO/IEC 23001-7), чтобы плеер знал, какой ключ просить. Путать имя ключа с ключом – частый источник страхов на ровном месте: называть ключ в открытую безопасно.
Личность устройства – заводской сертификат и приватные ключи в защищённом «железе» (keybox у Widevine, сертификат приложения у FairPlay). Они не покидают устройство; на них держится доверие, позволяющее серверу убедиться, что он говорит с настоящим CDM.
Токен прав – короткоживущий пропуск между плеером и вашим бэкендом, разобранный выше. Живёт секунды; одноразовый.
Кто не должен видеть контент-ключ в открытом виде, никогда: ваш JavaScript-плеер, CDN, логи, аналитика и любой человек в эксплуатации. Правило простое – открытый контент-ключ существует только внутри защищённых границ (KMS/HSM, сервер лицензий, CDM устройства); между ними он перемещается лишь завёрнутым. Нарушение этого правила и есть способ номер один потерять каталог.
Сессионная против постоянной лицензии: онлайн и офлайн
До сих пор лицензия у нас «жила» ровно столько, сколько играет видео. Так бывает не всегда, и выбор задаётся типом сессии ключей в EME – он решает, переживает ли лицензия закрытие вкладки и пишет ли модуль что-либо на диск. Типов три.
temporary – сессионная (временная). Лицензия и ключи живут в памяти и умирают вместе с вкладкой. Это умолчание для онлайн-воспроизведения: закрыл – при следующем запуске плеер берёт лицензию заново. Ничего на диск не пишется, поверхность атаки минимальна.
persistent-license – постоянная. Лицензию модуль сохраняет на диск, привязав к источнику (origin), чтобы пережить перезапуск. Это механизм офлайн-скачивания: пользователь грузит фильм в дорогу, и сохранённая лицензия даёт играть его без сети. Сильнее по возможностям – и по требованиям: офлайн-лицензия несёт собственное окно действия, а на диске лежит секрет, к которому правила надо применять строже.
persistent-usage-record – запись использования. Лицензия не хранится, но модуль сохраняет на диск запись о том, что и сколько воспроизводилось, – чтобы отчитаться позже. Применяется редко и точечно, под аналитику и сверку прав.
| Тип сессии (EME) | Переживает закрытие? | Пишет на диск? | Зачем нужен |
|---|---|---|---|
| temporary | Нет | Нет | Обычное онлайн-воспроизведение |
| persistent-license | Да | Лицензию (ключ) | Офлайн-скачивание и просмотр |
| persistent-usage-record | Да | Только запись о просмотре | Отчётность и сверка прав |
Что именно лицензия разрешает помимо «да» – окно аренды, офлайн-срок, потолок разрешения, требование HDCP, – это уже политика лицензии, и ей посвящена отдельная статья Политика лицензий: аренда, офлайн, output control и права. Здесь нам важно одно: тип сессии решает, где и сколько живёт ключ, и постоянная лицензия – это сознательный выбор записать секрет на диск ради офлайна.
Ротация ключей для прямых эфиров
У VOD один контент-ключ может обслуживать весь фильм. У премиум-эфира – матча, премьеры – ставки выше, и появляется ротация ключей: контент-ключ меняется через равные интервалы, так что один утёкший ключ открывает лишь короткий отрезок, а не весь эфир. Плеер прозрачно запрашивает лицензию под каждый новый идентификатор ключа. В обмене ключами это размечает CPIX: элемент ContentKeyPeriod задаёт период, на котором действует конкретный ключ.
Считаем вслух, потому что здесь прячется ловушка масштаба. Пусть идёт двухчасовой матч, ключ ротируется каждые 60 секунд:
Периодов ключа = 2 часа × 60 мин × 60 с ÷ 60 с = 120 ключей за эфир
Наивно: каждое устройство берёт новую лицензию на каждый период.
Пик аудитории 500 000 зрителей:
запросов лицензий = 500 000 × 120 = 60 000 000 за эфир
— и каждая ротация бьёт по серверу лицензий синхронным всплеском.
Иерархическая лицензия: один ответ несёт пачку ключей вперёд.
запросов лицензий ≈ 500 000 (по одной на зрителя, плюс редкие добор)
— в ~120 раз меньше нагрузки на сервер лицензий.Отсюда два инженерных вывода. Первый: частая ротация без продуманной доставки сама создаёт DDoS по вашему серверу лицензий – 500 тысяч устройств, синхронно просящих новый ключ каждую минуту. Второй, и это решение: иерархические ключи – лицензия отдаёт сразу текущие и будущие ключи в одном ответе, так что устройство не бегает за лицензией на каждую ротацию. Современные связки (например, AWS Elemental MediaPackage с ротацией ключей, или Unified Streaming с castLabs для высокочастотной ротации) кладут несколько ключей в один лицензионный ответ и разводят запросы во времени, чтобы не ронять сервер. Пик премьеры как режим нагрузки разобран в Доставка живых событий и всплеск премьеры; здесь достаточно правила: чем чаще ротация, тем важнее доставлять ключи пачками, а не по одному.
Частая ошибка: лимит без проверки и секрет в клиенте
Самая дорогая ошибка этого слоя – поставить «защиту», которую обходит любой, кто откроет инструменты разработчика. Случается она в двух видах.
Первый – проверка прав на клиенте. Команда делает лимит одновременных потоков или гео-проверку в JavaScript-плеере, потому что так быстрее. Но клиент полностью под контролем пользователя: проверку отключают, и сервер лицензий покорно отдаёт ключ. Лекарство – правило из середины статьи: запрос лицензии обязан пройти через серверный сервис прав (прокси), и решение «выдавать ключ или нет» принимает сервер, а не клиент.
Второй – секрет в клиенте или в манифесте. Контент-ключ кладут рядом с видео; ключ связи с сервером лицензий зашивают в приложение; токен прав делают долгоживущим и многоразовым. Любое из трёх превращает защиту в декорацию. Лекарство – карта секретов: открытый контент-ключ только внутри KMS/HSM, сервера лицензий и CDM; токены короткие и одноразовые; в манифесте – лишь публичное имя ключа (default_KID), но не он сам. Проверяется это одним грепом по тому, что реально уходит на клиент: если там виден контент-ключ или вечный токен – каталог уже не защищён, просто пока этого никто не заметил.
Где здесь Фора Софт
Доставка ключей – слой, где неверная архитектура либо роняет старт у всей аудитории на пике, либо тихо отдаёт контент-ключ туда, откуда его скопируют, и оба провала всплывают только в продакшене. Фора Софт с 2005 года создаёт ПО для видеостриминга, OTT/Internet TV, e-learning и телемедицины – 250+ выпущенных проектов для 400+ клиентов, – и доставка ключей проходит через всю эту работу: лицензионный прокси с серверным сервисом прав, который держит подписку, регион и лимит потоков вне досягаемости клиента; обмен ключами по CPIX/SPEKE между хранилищем и упаковщиком; ротация ключей с иерархической доставкой, чтобы пик премьеры не превратился в самоналоженный DDoS по серверу лицензий; и аккуратная граница секретов, на которой открытый контент-ключ не выходит за пределы KMS, сервера лицензий и CDM устройства. Когда медиакомпании нужна защита, которая держит и масштаб премьеры, и аудит студии, именно эту инженерию доставки ключей мы и приносим.
Ключевые выводы
- Зашифрованное видео бесполезно без ключа; вся защита – это доставить ключ на одно воспроизведение и никуда больше.
- В браузере поток лицензии идёт по EME (W3C): запрос → проверка → ключ, завёрнутый под устройство.
- Три диалекта: запрос лицензии Widevine, challenge PlayReady, пара SPC/CKC у FairPlay – форма одна.
- Проверку прав делает серверный прокси; в клиент логика и ключ связи не уезжают никогда.
- Открытый контент-ключ живёт только в KMS/HSM, сервере лицензий и CDM устройства – не в манифесте и не в плеере.
- Постоянная лицензия включает офлайн; ротация ключей защищает эфиры, но требует иерархической доставки.