March 6, 2014

The Paranoid’s Guide to Internet Video Streaming

Recently our customer and collaborator Thomas Gires released an article describing the process of building secure streaming infrastructure with WMSPanel, Wowza Streaming Engine and Nimble Streamer. He called it The Paranoid’s Guide to Internet Video Streaming and it expresses his experience in video streaming.

You can contact Thomas if you have any questions or if you want to discuss further use of his professional services.

Here is the copy of aforementioned article for convenience of our customers and visitors.


Why protect streams?

Despite increased regulation and commercialization, the Internet is still an unruly place populated by very clever, unscrupulous people. They will spend a lot of time and effort to gain access to paid content, either for their personal consumption, or to make a profit themselves.

If you distribute premium video content online, whether it is live content or on-demand, it is vitally important to have a strong infrastructure to serve the content and also protect it from unauthorized access.

Wowza Streaming Engine - the most versatile streaming server

There are quite a few streaming servers available commercially, but our software of choice is Wowza Streaming Engine. The greatest strength of Wowza is its flexibility. With it, you can stream to Flash players on computers, iOS and Android devices, set-top boxes, etc. They call this “Any screen, done right”.

There are many more features to this server, such as live transcoding, adaptive playlists, etc, that are beyond the scope of this document.

Wowza’s weakness is that it has very limited built-in security measures. But, thanks to its modular system, it is possible to extend the functionalities of the server with monitoring and access control. - bringing Wowza to new heights

This platform is an indispensable companion to Wowza, filling in the gaps where Wowza is lacking.
WMSpanel offers a simple web interface to monitor and control what your Wowza servers are doing. You are able to see the number of users connected to each server, users per country, bandwidth usage.

But what interests us the most, is the Pay-Per-View (PPV) functionality. This allows us to attach a special token to each stream request, which can then be used to have complete control over that session. In other words, we can assign user IDs and other tracking information, which will be passed back to our web server. Then, our service can analyze this information and send back a request to disconnect certain streams (for example if a single user ID is detected on streams from multiple different IP addresses).

Nimble Streamer - a lightweight extender for your servers

Nimble Streamer (or simply Nimble) is a HTTP streaming server created by the team behind WMSPanel, and it integrates perfectly with their service and Wowza.

Nimble’s advantage is that it has very light system requirements. Where Wowza really requires a high-spec server to run optimally, Nimble runs fine on lower-end servers or even Virtual Machines. So, it makes an ideal low-cost method of extending the reach of your streaming network.

Additionally, Nimble Streamer is somewhat more secure than Wowza in HLS mode, so I recommend using it exclusively, for HLS distribution.



So, we need to make Wowza and Nimble interact with each other while preventing devious users from gaining unauthorized access...

Wowza Streaming Engine

We do not want Wowza’s HLS streams to be accessible from end-users. But, we cannot completely disable this function, as Nimble needs to have an HLS source.

So, the best approach is to separate the Wowza side into an origin-edge configuration. Your encoders will send the streams to the origin, which will take care of packetizing it to HLS for Nimble to use, while preventing direct access to the chunklist thanks to the firewall.

Sample firewall rule for origin server:

-A INPUT -p tcp --dport 1935 -s -j ACCEPT
-A INPUT -p tcp --dport 1935 -j DROP

You will also need to allow the IPs of your encoders, or they will not be able to publish!

(Note: Wowza does have its own IP blacklisting capability, but it is not as reliable as the system firewall.)

Then, you will need an edge server. With some fancier firewall rules, you could in theory run both the origin and edge on the same server, but this is something I have yet to try.

Instead, I recommend setting up a separate edge server. This can run both Wowza and Nimble. The Wowza edge will be configured to server only RTMP, so there is no HLS vulnerability to exploit. And, the Nimble side will handle the HLS requests.

Nimble Streamer

Now we need to handle the HLS side, by using Nimble. The first step will be to configure the default route, this can be done through’s web interface, or manually in the configuration files. In short, you must set the route to point to domain http://origin-server:1935 and the path being the name of your Wowza origin application. Check Nimble's installation page for full set of instructions:

Now, this means you have an edge server accessible from ports 1935 for RTMP/RTSP and 8081 for HLS (assuming you have not changed default port numbers).

Why not use Wowza for HLS?

To truly secure HLS on Wowza, it is recommended to use one of their DRM solutions, or implement their own AES encryption system. These methods are very secure, but have drawbacks. A full-fledged DRM system is rather expensive, and some players are not able to handle the AES encryption (for example Android versions < 4.1, which are still quite commonly used). So, for peace of mind, we find it a lot more convenient to use Nimble Streamer to handle all HLS duties.

How the token system works (in a nutshell)

The WMSAuth PPV token has two functions:
1) Act as a key for granting access to the stream
2) Act as an identifier for the user’s streaming session

Each time a viewer requests a stream, the token value is verified. For example, each token has an encrypted password, and a validity time. If both of these are correct, the stream will be allowed.

Periodically, the streaming server will send a report of the currently active sessions to the provider’s web server. The server can then process this information for record keeping, and also to detect illicit use.
The web server can then reply with a list of token IDs to force disconnect from the server.

For more information about the WMSAuth PPV token system, you may want to read the official documentation.

Creating the WMS token

(Note: the token system is extremely flexible, you can configure it in any way you wish. I use the following style in production and it has proven to be the best in terms of security and user monitoring)

The authentication token is made of four separate parts: server_time, validminutes, hash_value, and id

server_time is the GMT time at which the token was generated. This is used to check if the token is expired or not.
validminutes is the number of minutes for which the token will be valid. This can be as long as you want, depending on your implementation. For normal web-based application, 3 minutes is plenty.
hash_value is a short encrypted string, based on the server time, valid minutes, and a password that is defined in WMSPanel web interface. This hash value is what makes the token unique and nearly impossible to generate by a third party.
id is where the PPV flexibility comes into play. You can make this id anything you want, it will be passed back to your web server for analysis.

I recommend the id to be structured in this way:


user_id: your user’s identifier, depending on your service this can be an id number, username or email address.
platform: You can use this to keep track of what device is being used, such as web, android, ios, etc.
device_id: This can identify the devices used by the user. For example, Android and iOS apps can pass the device’s unique identifier, and for a website, you can use a randomized cookie to identify the browser used. This device ID is very important to detect abuse if you offer some kind of trial period (if someone signs up for multiple accounts to enjoy multiple trial periods, you will know they are using the same browser or mobile devices)
stream: The name of the stream for which this token is generated, to be compared with the actual stream being played.
time: UNIX timestamp, use this to keep track how long the user has been watching.
ip: You can keep track of the IP that generated the stream request. The PPV response to your server will contain the actual IP of the device watching the stream, so you can compare these values to detect abuse.

Following is a sample PHP code to generate this token:

$id = $customer_id . "-" . $platform . "-" . $device_id . "-" . $streamname . “-” . time() . "-" . $_SERVER['REMOTE_ADDR'];

$today = gmdate("n/j/Y g:i:s A");
$key = "your secret word"; //this is also set up in WMSPanel rule
$validminutes = 3;
$str2hash = $id . $key . $today . $validminutes;
$md5raw = md5($str2hash, true);
$base64hash = base64_encode($md5raw);
$urlsignature = "server_time=" . $today ."&hash_value=" . $base64hash. "&validminutes=" . $validminutes . "&id=" . $id;
$base64urlsignature = base64_encode($urlsignature);

$stream_url = your_stream_url . "?wmsAuthSign=" . $base64urlsignature;

So, you now have a token which is unique to this user, their device, the stream they are watching, and is valid for only 3 minutes. While it is technically possible for malicious users to share the link within 3 minutes, the PPV system will start reporting multiple sessions and IP addresses using the same details, so you will know something is not right.

You also need to configure the authentication system in the WMSPanel web configuration, the following official article has all the details.

Integration with your system

The next step is to make your servers talk to your database through the PPV system. For this, you need to set up an API script on your server, and link to it in

What will happen is, at the specified interval, your Wowza and Nimble servers will send a POST request to the API URL, and the POST data will contain details about every single PPV connection to this server.
Additionally, your API can return a list of connections which you would like to terminate.

The job of your API is then to extract the information and go through each connection. What happens next is up to you, depending on the layout of your database, your usage policies, etc. I will explain a fairly typical usage scenario, where you would want to prevent users from sharing their accounts with others, as well as preventing any unauthorized sharing of the stream URL.

Step 1: Register the token for each user.
When you generate the token as per the instructions in the previous section, you should also save the string to your user database, along with a timestamp and the user’s IP address. This way, your system will know what is the stream this user is supposed to be watching (any other non-matching IDs from the same user can thus be considered from sharing the account with someone else at the same time, and disconnected appropriately.)

Step 2: Set up activity tracker database
This is optional, but highly recommended if you want more details about what your users are doing. It consists of a temporary table that is wiped and repopulated every time the API script is called.

Here are the basic fields you should include:

Step 3: Set up the API
As mentioned earlier, this is up to your own requirements, additionally, my skills in manipulating XML data are very limited, so my examples may be very ugly, but they are functional!

//response will be in XML format
header('Content-Type: text/xml');

error_reporting (0);

echo '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>';
echo "<ApiSyncResponse><Solution></Solution><DenyList>";

//create connection to database
$hostname = "localhost";
$username = "db_user";
$password = "db_password";
$dbname = "db_name";

$con = mysql_connect($hostname,$username,$password);

if (!$con)
die('Could not connect: ' . mysql_error());

mysql_select_db($dbname, $con);

// Log entire incoming request to see what we have in it.
// if statement added to prevent errors when testing manually in browser
if (isset($HTTP_RAW_POST_DATA)) {
$fp = fopen('request.log', 'w');
fwrite($fp, $HTTP_RAW_POST_DATA);

// Use this object for accessing each viewer's ID, IP and stream name.
$sync_data = $HTTP_RAW_POST_DATA;
$xml=new SimpleXMLElement($sync_data);

//remove previous entries. Each Wowza/Nimble server sends its own information separately, so you must keep track of each server’s ID to avoid wiping other server’s activity at the same time.
$server = $xml->ID;

$query = "delete from cheat_tracker where channel_server = '" . $server . "'";

foreach($xml->PayPerViewInfo[0]->children() as $vhost){

foreach($vhost->children() as $application){

foreach ($application->children() as $instance) {

foreach ($instance->children() as $stream) {

foreach($stream->children() as $streaminfo){

if($streaminfo->getName() == "Player"){

//now we’re at the individual session
$user = explode("-", $streaminfo->id);
//clean up the reported channel
$channel_code = str_replace('.smil', '', $stream->name);
$channel_code = str_replace('.stream', '', $channel_code);
$channel_code = str_replace('wowz://originserver:1935/yourapplication/_definst_/', '', $channel_code);

$clean_code = explode('?wms', $channel_code);
$channel_code = $clean_code[0];

$customer_id = $user[0];
$platform = $user[1];

//extract the user’s device ID, use IP if not reported
if ($user[2] != ''){
$device_id = $user[2];
} else {
$device_id = $streaminfo->ip;
$start_time = $user[3];

$ip = $streaminfo->ip;
$request_ip = $user[5];

//extract the actual channel for which the token was generated
$channel_db = str_replace('.smil', '', $user[4]);
$channel_db = str_replace('.stream', '', $channel_db);

//ignore restreams and admins (add here your edge server IPs and any others you want to skip processing
if (($ip != '') && ($ip != '')){
//write to db
$query = "INSERT INTO cheat_tracker (user_id, user_ip, server, streamname, token, timestamp) values ('" . $customer_id . "', '" . $ip . "', '" . $server . "', '" . $device_id . "', '" . $channel_code . "', '" . $streaminfo->id . "', now())";

//you can add here any other updates you want to put to your user database…

//cheater? The channel codes don’t match
if ($channel_db != $channel_code){
$kill[] = $streaminfo->id;

//cheater? The IPs don’t match
if ($ip != $request_ip){
$kill[] = $streaminfo->id;


//now tell the server to disconnect the cheaters
if (isset($kill)){

//remove duplicates
$kill = array_unique($kill);

while ($i < count($kill)){
if ($kill[$i] != ''){
echo "<ID>" . $kill[$i] . "</ID>";


This is a rather basic script, you can extend it in many ways, such as a kill_list database table to manually disconnect users, permanently banning device IDs or IP addresses, etc.

The other advantage of keeping this activity in the cheat_tracker database table, you can access it for your own reporting of server load, user activity, etc


Video streaming is serious business, especially as bandwidth is not cheap.

Thank for reading this little writeup, I hope it has helped you in improving your streaming system’s security!

Related documentation

No comments:

Post a Comment