Skip to content

Commit

Permalink
Add key rotation for ts file example
Browse files Browse the repository at this point in the history
bug fixes and other minor improvements
  • Loading branch information
aminyazdanpanah committed Feb 12, 2020
1 parent 7d63891 commit 7f8ede3
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 16 deletions.
76 changes: 73 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ This package provides integration with **[PHP-FFMpeg](https://github.com/PHP-FFM
- [Opening a Resource](#opening-a-resource)
- [DASH](#dash)
- [HLS](#hls)
- [Encrypted HLS](#encrypted-hls)
- [DRM (Encrypted HLS)](#drm-encrypted-hls)
- [Transcoding](#transcoding)
- [Saving Files](#saving-files)
- [Metadata Extraction](#metadata-extraction)
Expand Down Expand Up @@ -160,10 +160,14 @@ $video->HLS()
```
**NOTE:** You cannot use HEVC and VP9 formats for HLS packaging.

#### Encrypted HLS
#### DRM (Encrypted HLS)
The encryption process requires some kind of secret (key) together with an encryption algorithm. HLS uses AES in cipher block chaining (CBC) mode. This means each block is encrypted using the ciphertext of the preceding block. [Learn more](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation)

You must specify a path to save a random key to your local machine and also a URL(or a path) to access the key on your website(the key you will save must be accessible from your website). You must pass both these parameters to the `encryption` method:

##### Single Key
The following code generates a key for all TS files.

``` php
//A path you want to save a random key to your server
$save_to = '/home/public_html/PATH_TO_KEY_DIRECTORY/random_key.key';
Expand All @@ -179,8 +183,74 @@ $video->HLS()
->autoGenerateRepresentations([1080, 480, 240])
->save('/var/www/media/videos/hls-stream.m3u8');
```

##### Key Rotation
The code below, allows you to encrypt each TS file with a new encryption key. This can improve security and allows for more flexibility. You can also modify the code to use a different key for each set of segments(i.e. if 10 TS files has been generated then rotate the key) or you can generate a new encryption key at every periodic time(i.e. every 10 seconds).

First you need to create a listener class that is extended by `Evenement\EventEmitter` and is implemented by `Alchemy\BinaryDriver\Listeners\ListenerInterface`. This allows you to get all lines of FFmpeg logs regardless of the type of them.
``` php
class LineListener extends Evenement\EventEmitter implements Alchemy\BinaryDriver\Listeners\ListenerInterface
{
private $event;

public function __construct($event = 'line')
{
$this->event = $event;
}

public function handle($type, $data)
{
foreach (explode(PHP_EOL, $data) as $line) {
$this->emit($this->event, [$line]);
}
}

public function forwardedEvents()
{
return [$this->event];
}
}
```

You can also use `Alchemy\BinaryDriver\Listeners\DebugListener` object instead and skip this step.

After that, you should pass an instance of the object to the `listen` method in the `FFMpegDriver` object and get the line of FFmpeg logs. When a new TS file has been created, you should generate a new encryption key and update the key info file.
``` php
$save_to = "/home/public_html/PATH_TO_KEY_DIRECTORY/key_rotation";
$url = "https://www.aminyazdanpanah.com/PATH_TO_KEY_DIRECTORY/key_rotation";
$key_info_path = Streaming\File::tmp();
$ts_files = [];

$update_key_info_file = function ($number) use ($save_to, $url, $key_info_path) {
$u_key_path = $save_to . "_" . $number;
$u_url = $url . "_" . $number;
Streaming\HLSKeyInfo::generate($u_key_path, $u_url, $key_info_path);
};

$ffmpeg = Streaming\FFMpeg::create();
$ffmpeg->getFFMpegDriver()->listen(new LineListener);
$ffmpeg->getFFMpegDriver()->on('line', function ($line) use (&$ts_files, $update_key_info_file) {
// Check if a new TS file is generated or not
if(false !== strpos($line, ".ts' for writing") && !in_array($line, $ts_files)){
// A new TS file has been created! Generate a new encryption key and update the key info file
array_push($ts_files, $line);
call_user_func($update_key_info_file, count($ts_files));
}
});

$video = $ffmpeg->open("path/to/video");

$video->HLS()
->encryption($save_to, $url, $key_info_path)
->setAdditionalParams(['-hls_flags', 'periodic_rekey'])
->X264()
->autoGenerateRepresentations([240])
->save('c:\\test\\encryption_videos\\hls-stream.m3u8');
```

**NOTE:** It is very important to protect your key on your website using a token or a session/cookie(**It is highly recommended**).

**NOTE:** However HLS supports AES encryption, that you can encrypt your streams, it is not a full DRM solution. If you want to use a full DRM solution, I recommend to try **[FairPlay Streaming](https://developer.apple.com/streaming/fps/)** solution which then securely exchange keys, and protect playback on devices.

### Transcoding
A format can also extend `FFMpeg\Format\ProgressableInterface` to get realtime information about the transcoding.
Expand Down Expand Up @@ -290,7 +360,7 @@ $stream = $ffmpeg->open('https://www.aminyazdanpanah.com/PATH/TO/DASH-MANIFEST.M
$stream->HLS()
->X264()
->autoGenerateRepresentations([720, 360])
->save('/var/www/media/hls-stream.mpd');
->save('/var/www/media/hls-stream.m3u8');
```

#### 3. Stream(DASH or HLS) To File
Expand Down
4 changes: 2 additions & 2 deletions src/Clouds/Cloud.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Cloud
*/
public static function uploadDirectory(array $clouds, string $tmp_dir): void
{
if (!is_array(current($clouds))) {
if (isset($clouds['cloud'])) {
$clouds = [$clouds];
}

Expand All @@ -40,7 +40,7 @@ public static function uploadDirectory(array $clouds, string $tmp_dir): void
*/
public static function download(array $cloud, string $save_to = null): array
{
list($save_to, $is_tmp) = $save_to ? [$save_to, false] : [File::tmpFile(), true];
list($save_to, $is_tmp) = $save_to ? [$save_to, false] : [File::tmp(), true];
static::transfer($cloud, __FUNCTION__, $save_to);

return [$save_to, $is_tmp];
Expand Down
2 changes: 1 addition & 1 deletion src/Export.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public function getPathInfo(): array
private function moveTmp(?string $path): void
{
if ($this->isTmpDir() && !is_null($path)) {
File::moveDir($this->tmp_dir, dirname($path));
File::move($this->tmp_dir, dirname($path));
$this->path_info = pathinfo($path);
$this->tmp_dir = '';
}
Expand Down
4 changes: 2 additions & 2 deletions src/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public static function directorySize(string $dir): int
/**
* @return string
*/
public static function tmpFile(): string
public static function tmp(): string
{
return tempnam(static::tmpDirPath(), 'stream');
}
Expand Down Expand Up @@ -90,7 +90,7 @@ private static function tmpDirPath(): string
* @param string $src
* @param string $dst
*/
public static function moveDir(string $src, string $dst): void
public static function move(string $src, string $dst): void
{
static::filesystem('mirror', [$src, $dst]);
static::remove($src);
Expand Down
7 changes: 4 additions & 3 deletions src/HLS.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,13 @@ public function setHlsKeyInfoFile(string $hls_key_info_file): HLS
/**
* @param string $save_to
* @param string $url
* @param string|null $key_info_path
* @param int $length
* @return HLS
*/
public function encryption(string $save_to, string $url, int $length = 16): HLS
public function encryption(string $save_to, string $url, string $key_info_path = null, int $length = 16): HLS
{
$this->setHlsKeyInfoFile(HLSKeyInfo::generate($save_to, $url, $length));
$this->setHlsKeyInfoFile(HLSKeyInfo::generate($save_to, $url, $key_info_path, $length));
$this->tmp_key_info_file = true;

return $this;
Expand Down Expand Up @@ -199,6 +200,6 @@ protected function getPath(): string
*/
private function savePlaylist($path, $reps)
{
HLSPlaylist::save($this->master_playlist ?? $path, $reps, pathinfo($path, PATHINFO_FILENAME));
HLSPlaylist::save($this->master_playlist ?? $path, $reps, pathinfo($path, PATHINFO_FILENAME));
}
}
16 changes: 12 additions & 4 deletions src/HLSKeyInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,28 @@
class HLSKeyInfo
{
/**
* @param $url
* @param $path
* @param string $path
* @param string $url
* @param string $key_info_path
* @param int $length
* @return string
*/
public static function generate(string $path, string $url, int $length = 16): string
public static function generate(string $path, string $url, string $key_info_path = null, int $length = 16): string
{
if (!extension_loaded('openssl')) {
throw new RuntimeException('OpenSSL is not installed.');
}

File::makeDir(dirname($path));
file_put_contents($path, openssl_random_pseudo_bytes($length));
file_put_contents($path_f = File::tmpFile(), implode(PHP_EOL, [$url, $path, bin2hex(openssl_random_pseudo_bytes($length))]));

file_put_contents(
$path_f = $key_info_path ?? File::tmp(),
implode(
PHP_EOL,
[$url, $path, bin2hex(openssl_random_pseudo_bytes($length))]
)
);

return $path_f;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/FileManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public function testMakeDir()

public function testTmp()
{
$tmp_file = File::tmpFile();
$tmp_file = File::tmp();
$tmp_dir = File::tmpDir();

$this->assertIsString($tmp_file);
Expand Down

0 comments on commit 7f8ede3

Please sign in to comment.