Заметки 6111 ~ 2 мин.

Расшифровка hls потока с помощью openssl_decrypt

Расшифровка hls потока с помощью openssl_decrypt

Что использовать для расшифровки hls стрима, краткий гайд для работы с функцией openssl_decrypt() в в PHP

С чего все началось

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

Что имеем на руках

Итак, у нас есть обычный плейлист m3u8 потока в виде:


#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:6
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:04.871Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0x07a6ed33e15beca7f64e57e5137f7fde
#EXTINF:2.035,
k0_chunk_1555561741998417630_0_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:06.907Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0xb51d4f0db93191e12fd7cb405596f57c
#EXTINF:3.198,
k0_chunk_1555561742000754678_1_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:10.104Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0xc24dd0563a70e8b70b499f173d7e11eb
#EXTINF:3.212,
k0_chunk_1555561742002319677_2_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:13.316Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0xfe07b0f27f9bb09a156208859cf7a9fd
#EXTINF:3.224,
k0_chunk_1555561747042459190_3_a.ts
#EXT-X-PROGRAM-DATE-TIME:2019-04-18T04:29:16.540Z
#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0x6ce77aa4e50f4724395e90536da47234
#EXTINF:3.228,

Раньше в плейлисте были указаны только чанки (куски) потока, пример k0_chunk_1555561741998417630_0_a.ts. Именно после их объединения и и получался на выходе один большой файл. Теперь можно заметить, что появилась новая строка с указанием метода шифрования, ключа и дополнительных параметров.


#EXT-X-KEY:METHOD=AES-128,URI="hlsEncryptionKey?stream_name=LhSX5NKOZDnZEBlbXQjdyq86CWWVjx-wqvhT_oQ7UJ-0HUweUwmPYb0WiocgyTwTAjZ4R7sF16l48l5LHUzx7A",IV=0x07a6ed33e15beca7f64e57e5137f7fde

Алгоритм шифрования - AES-128, более подробно о нем можно почитать на Хабре. Нам же он понадобится для указания метода в  функции дешифрования.

Можно было бы использовать функцию mcrypt_decrypt() или попробовать расшифровать с помощью:

openssl aes-128 -d -in encrypted_segment.ts -out decrypted_segment.ts -nosalt -iv <iv_hex> -K <key_hex>

Но mcrypt_decrypt() устарела и в PHP 7.2 была удалена. Вместо нее воспользуемся openssl_decrypt().


openssl_decrypt ( string $data , string $method , string $key [, int $options = 0 [, string $iv = "" [, string $tag = "" [, string $aad = "" ]]]] ) : string
  • data - Данные для расшифровки.
  • method - Метод шифрования. Список доступных методов можно получить с помощью функции openssl_get_cipher_methods().
  • key - Ключ.
  • options - options можно задать одной из констант: OPENSSL_RAW_DATA, OPENSSL_ZERO_PADDING.
  • iv - Ненулевой инициализирующий вектор.
  • tag - Тег аутентификации в режиме шифрования AEAD. Если он некорректен, то аутентификация завершится неудачей и функция вернет FALSE.
  • aad - Дополнительные аутентификационные данные.


    //обращаю ваше внимание что ненулевые вектора мы вытаскиваем без 0x, это очень важно (!)
    preg_match_all('/.*URI="(.*)".*IV=0x(.*).*/', $playlist, $cryptoData);

    $cryptoData[0]  = [...//массив ключей для каждого чанка, в моем случае он одинаков для всех];
    $cryptoData[1]  = [...//массив инициализирующих ненулевых векторов];
    $chunkList  = [...//массив с именами чанков];
    
    $key = $this->getEncriptionKey($baseUrl . $cryptoData[0][0]);
foreach ($chunkList as $countChunk => $chunk) { try { //дергаем шифрованный файл чанка $chunkResponse = $this->guzzle->get($baseUrl . $chunk, ["cookies" => $cookies ])->getBody()->getContents(); } catch (ClientException $e) { continue; } /** * алгоритм шифрования, достаем из того же плейлиста m3u8 * но для правильной работы убран "-" */ $method = "AES128"; //"ломаем" с помощью openssl_decrypt() $chunkResponseDecrypt = openssl_decrypt( $chunkResponse, $method, $key, OPENSSL_RAW_DATA, hex2bin($cryptoData[1][$count]) ); //сохраняем расшифрованный чанк file_put_contents($tmpDir . $chunk, $chunkResponseDecrypt); } ... //загружаем ключ для дешифровки public function getEncriptionKey($urlKey) { //я использую Guzzle Client $client = new Client(); $key = $client->get($urlKey); return $key->getBody()->getContents(); }
Обратите внимание на то, что ненулевые инициализирующие вектора мы преобразуем из шестнадцатеричных данных в двоичные, с помощью функции hex2bin(). Когда я просто пытался расшифровать без преобразования векторов, то видео было без звука, тоже важно это заметить. Второе, на что нужно обратить внимание, если не обрезать 0x в строке с вектором, то звук также может отсутствовать, да и в принципе видео, в моем случае, было "битым" в некоторых местах. 

Подведем итоги

  • функция mcrypt_decrypt() устарела и удалена
  • используем openssl_decrypt()
  • обрезаем у ненулевых инициализирующих векторов 0x
  • не забываем преобразовать ненулевые вектора с помощью hex2bin()

Что думаешь?

Yrstal14.02.2024

Здравствуйте у меня такой вопрос. Можно ли с hls или с m3u8 стриминга потока узнать оригинальную ссылку стрима? Либо ip адрес первоначального стримера?

Игорь08.12.2023

Единственный вопрос. В моем случае статический AES 128. Вы не знаете, может какие программы сайты с дешифратором Aes в Hex. Потому что нашел, что так делают, а программа та уже и не доступна нигде.

Игорь06.12.2023

Здравствуйте. Не совсем понятно, где брать "обычный плейлист m3u8 потока"
Поискал в интернете. Даже нашел m3u8.exe Которую надо запускать через командную строку. А в данном случае как его организовать?

Сергей Мухин 07.12.2023

Здравствуйте, ну m3u8 это формат плейлиста, я удивлен, как вы нашли этот пост и почему читаете, если не знаете что это такое)

Т.е. если зайти на какой-нибудь сайт стримов типа twitch и запустить любой стрим игры, то можно заметить что подгружается плейлист m3u8 и там уже содержится нужная информация.


Игорь 07.12.2023

Случайно нашел :) понятно. Я не специалист в этих делах. Просто хочу скачать видео для себя, решил попробовать но там защита. Есть видео с разными защитами. Сегодня даже 6 видео скачал с помощью N_m3u8DL-RE.exe, mp4decrypt.exe и ffmpeg.exe, но некоторые видео так не качаются, идут с таким же master.mpd, но кодирование не clear с 64 base, а Widevine с кодированием AES-128. Так и попал через поиск к Вам. Но понял, что здесь метод немного другой. То есть в моем случае нет такого файла в инспекторе.

Категории
  • PHP 65
  • Заметки 15
  • Безопасность 3
  • Флуд 2
  • Nginx 2
  • ИТ новости 2
  • Видео 1
  • Docker 1
  • Roadmap 1
  • Архитектура 0

Хочешь поддержать сайт?

Делаем из мухи слона

sergeymukhin.com

персональный блог о веб-разработке от Сергея Мухина. Блог был основан в 2018 году, и собирался уделять основное внимание последним тенденциям, учебным пособиям, а также советам и рекомендациям, позволяющим начинающим девелоперам встать быстрее на правильную дорогу веб разработки, но что-то пошло не так 😃

Релизы PHP 8.4

Дата Релиз
8 Июня 2024 Альфа 1
20 Июня 2024 Альфа 2
04 Июля 2024 Альфа 3
16 Июля 2024 Feature freeze
18 Июля 2024 Бета 1
01 Августа 2024 Бета 2
15 Августа 2024 Бета 3
29 Августа 2024 RC 1
12 Сентября 2024 RC 2
26 Сентября 2024 RC 3
10 Октября 2024 RC 4
24 Октября 2024 RC 5
07 Ноября 2024 RC 6
21 Ноября 2024 GA

Что нового?