Thursday, January 21, 2016

Stream Encrypted Video with Windows Azure Media Services

After a massive break I thought to write on the topic since there are lack of resources. I'm streaming my AES encrypted video with JWT authentication and generating the auth token with Java. If you have Azure Media Service enabled account you are welcome to try this out :).

Following are couple of useful resources in the web for testing this process.

JWT official site and token verification tool - https://jwt.io/
Azure media player - http://amsplayer.azurewebsites.net/azuremediaplayer.html
Base64URL encoder - http://kjur.github.io/jsjws/tool_b64uenc.html
HMAC generator - http://www.freeformatter.com/hmac-generator.html

At first we need to have encrypted videos uploaded to our Azure account. Following are the steps to doing that.
1. Login to manage.windowsazure.com
2. Go inside your media service account
3. Click on Upload a video file link under Managemant Tasks section (or else click on Content tab and then click on Upload Content link) and upload a video file.
4. Click on Process icon at the bottom, and click OK with default values.
5. Click on Sync Metadata icon at the bottom (Probably this will take some time)
6. Click on Encryption > Enable AES Encryption (If the Encryption icon not yet enable, logout and login again and try. It may take some time to enable that)
7. Click on Publish icon (SS for 4-7 https://www.diigo.com/item/image/5j6wi/wdb1)

Now you can copy the encrypted video publish URL which will be like : http://chanaka.streaming.mediaservices.windows.net/481ea237-7f1f-48b8-a4c1-096f34c10cd6/Best%20Sweet%20Minions%20Video%20-%20Let%20The%20UFO%20Go%20Home.ism/Manifest

Then go to Content Protection tab and set up the necessary attributes like this SS and click on manage verification keys button and regenerate primary key. And don't forget to save :)

For streaming the above encrypted video, we need to create a JWT auth token which is the tricky part. Following is sample java code to generate the token.


package azurejwttoken;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

/**
 *
 * @author chanaka
 */
public class AzureJWTToken
{

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args)
    {
        // Use the Primary Verification Key which you regenerated
        String          secret              = "YEAIfGy6cIXy1xhcIdgEoBfiWNoI3zq2FCKVQhdZXMcHGN+BAwicafgbRscsVZj1Us3RlWihphY1XMBuQOhlBg==";
        
        long            validitymillis      = 15 * 60 * 1000; // 15 minutes
        long            nowMillis           = System.currentTimeMillis();
        long            expireSeconds       = (nowMillis + validitymillis) / 1000; // Use seconds instead milli seconds
        
        String          jsonHeaderString    = "{\"typ\":\"JWT\",\"alg\":\"HS256\"}";
        
        // Use your ISSUER and SCOPE attributes properly here
        String          jsonBodyString      = "{\"iss\":\"http://mytest.com/\",\"aud\":\"urn:jwttest\",\"exp\":" + expireSeconds + "}";
        
        String          token               = getAuthToken(secret, jsonHeaderString, jsonBodyString);
        
        System.out.println("Token : " + token);
    }
    
    public static String getAuthToken(String secret, String jsonHeaderString, String jsonBodyString)
    {
        String          token       = null;
        
        try
        {
            Mac             sha256_HMAC         = Mac.getInstance("HmacSHA256");
            SecretKeySpec   secret_key          = new SecretKeySpec(Base64.decodeBase64(secret), "HmacSHA256");
            
            sha256_HMAC.init(secret_key);
            
            String          content             = Base64.encodeBase64URLSafeString(jsonHeaderString.getBytes()) + "." + Base64.encodeBase64URLSafeString(jsonBodyString.getBytes());
            String          hash                = Base64.encodeBase64URLSafeString(sha256_HMAC.doFinal(content.getBytes()));
            
            token           = "Bearer%3D" + content + "." + hash;
        }
        catch (NoSuchAlgorithmException ex)
        {
            Logger.getLogger(AzureJWTToken.class.getName()).log(Level.SEVERE, "Error occured while generating AuthToken : NoSuchAlgorithmException", ex);
        }
        catch (InvalidKeyException ex)
        {
            Logger.getLogger(AzureJWTToken.class.getName()).log(Level.SEVERE, "Error occured while generating AuthToken : InvalidKeyException", ex);
        }
        catch (Exception ex)
        {
            Logger.getLogger(AzureJWTToken.class.getName()).log(Level.SEVERE, "Error occured while generating AuthToken", ex);
        }
        
        return token;
    }
    
    /**
     * In case you need to convert base64 string to URL safe manually ()
     * 
     * @param base64EncodedString
     * @return 
     */
    public static String replaceUrlSafeChars(String base64EncodedString)
    {
        base64EncodedString = base64EncodedString.replace('+', '-');
        base64EncodedString = base64EncodedString.replace('/', '_');
        base64EncodedString = base64EncodedString.replace("=", "");
        
        return base64EncodedString;
    }
    
}

Using the JWT site we can verify the generated auth token. Just paste your token to JWT Debugger without the "Bearer%3D" part like this SS.

If it's correct you should be able to play it with Azure media player like this SS. Note that token will expire after 15 minutes according to our code. You can increase that accordingly.

Finally you can stream your video with following hard coded HTML source. Make sure you open the HTML with web server.

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
  <iframe src="//aka.ms/azuremediaplayeriframe?url=http%3A%2F%2Fchanaka.streaming.mediaservices.windows.net%2F481ea237-7f1f-48b8-a4c1-096f34c10cd6%2FBest%2520Sweet%2520Minions%2520Video%2520-%2520Let%2520The%2520UFO%2520Go%2520Home.ism%2FManifest&protection=aes&token=Bearer%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vbXl0ZXN0LmNvbS8iLCJhdWQiOiJ1cm46and0dGVzdCIsImV4cCI6MTQ1MzM1MjE2MH0.j6pe2Ej9aJJy0AvaqR7OI9FCcNTS1qI8y_EGzNYKT4g&autoplay=false" name="azuremediaplayer" scrolling="no" frameborder="no" align="center" height="280px" width="500px" allowfullscreen></iframe>    </body>
</html><!--
Iframe src dynamic url parameter :
    url : URL encoded encrypted video URL - java.net.URLEncoder.encode(encryptedVedioURL, "UTF-8")
    token : The token generated from the java code
--> 

Hope you enjoy. Happy coding... :)

I like to thank Saliya Randunu for providing me lots of resources :)