Azure Media Services : enable dynamic PlayReady content protection on your assets

November 20, 2014 -

As you maybe already know, Azure Media Services (AMS) has a feature called dynamic packaging that enables to stream a multi-bitrate asset as Smooth Streaming, HLS or MPEG-Dash dynamically, without storing an asset for each package format. Recently Microsoft added a new feature called dynamic content protection that allows to protect a content dynamically using AES or PlayReady DRM. In this post, I am going to explain how to enable PlayReady encryption, publish the asset and play it with the Silverlight player that has been updated by the AMS team to support PlayReady.

I wrote this post using the lot of samples provided on GitHub by the Azure Media Services team (thanks to them!) and the code of the Azure Media Services Explorer, so it may have some code that I took directly from these samples Smile

All the code of this blog post is available on my GitHub.

Overview of dynamic content protection

The feature is currently in preview on Microsoft Azure, so you have to enable it for your subscription if you want to use it. To do that, go to and search for Content Protection. Then click the Try it button and follow the steps to enable the feature :


Once done, you are ready to play with PlayReady Smile

There are two way to work with PlayReady in AMS. The first one is to use an external PlayReady license server to deliver the license (maybe you already have one for your services, so you can keep using it!). The second one is to use the license server that is hosted on Azure Media Services and protect the access to the service via a secure token service (like Azure Access Control or any other STS that can deliver SWT tokens). I am going to present the second way in this post.

The workflow to enable dynamic protection on a media asset is the following :

  1. Upload your asset in the Azure BLOB storage
  2. Encode the asset in a multi-bitrate file to use dynamic packaging
  3. Generate content keys and content delivery policy on the asset and configure PlayReady
  4. Create a locator to be able to stream the asset dynamically

Configure your media service in the Azure portal

Before starting to work with PlayReady, you need to configure your media service to be able to work with the STS that will deliver tokens. If you want to use ACS you can follow this post. In this article I will use a simple class to deliver tokens in my application.

Go on and select your media service. Go to the CONTENT PROTECTION tab. In the middle of the page you should see the PlayReady configuration :


For the license authorization policy, choose Token. This option tells Azure Media Services that it should validate a bearer token when the client call the PlayReady license server.

The issuer represents the name or URI of the STS that will generates the token, and the scope (or audience) represents the scope that is given to the token when it is issued.

Click on the manage verification keys button. In the primary verification key field put the key that will be used by the STS to sign the token :


Eventually you can add a secondary validation key in case of the first one get compromised.

All these information will make Media Services able to validate the token that your player will pass in header when calling the PlayReady license through the license server.

Your media service is ready to work with PlayReady.

Deliver Simple Web Token (SWT)

As they are named, SWT are really simple token that are easy to exchange on the http protocol. They should contains at least :

  • An issuer
  • An audience (scope)
  • An expiration date
  • A HMAC of all the parameters to ensure the token has not been compromised

If needed you can also pass a list of claims to the token.

All these parameters are URL encoded and represented in a simple query string : Issuer=your_issuer&Audience=your_audience etc…

The following class is a sample of Simple Web Token implementation :

/// <summary>
/// Represents a simple web token
/// </summary>
public class SimpleWebToken
    private static readonly DateTime epochStart = 
        new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
    private NameValueCollection claimsCollection;
    private byte[] signingKey = null;

    /// <summary>
    /// Initializes a new instance of the <see cref="SimpleWebToken"/> class.
    /// </summary>
    /// <param name="base64Key">The base64 key used to sign the token.</param>
    /// <param name="ttl">The time to live.</param>
    public SimpleWebToken(string base64Key, TimeSpan ttl)
        TimeSpan ts = DateTime.UtcNow - epochStart + ttl;
        this.ExpiresOn = Convert.ToUInt64(ts.TotalSeconds);
        this.claimsCollection = new NameValueCollection();

        var securityKey = new InMemorySymmetricSecurityKey(Convert.FromBase64String(base64Key));
        signingKey = securityKey.GetSymmetricKey();

    /// <summary>
    /// Gets or sets the issuer.
    /// </summary>
    public string Issuer { get; set; }

    /// <summary>
    /// Gets or sets the audience.
    /// </summary>
    public string Audience { get; set; }

    /// <summary>
    /// Gets or sets the signature.
    /// </summary>
    public byte[] Signature { get; set; }

    /// <summary>
    /// Gets the expires on.
    /// </summary>
    public ulong ExpiresOn { get; private set; }

    /// <summary>
    /// Gets the claims.
    /// </summary>
    /// <value>
    /// The claims.
    /// </value>
    public IList<Claim> Claims
            return this.claimsCollection.AllKeys
                .SelectMany(key =>
                        .Select(value => new Claim(key, value)))

    /// <summary>
    /// Adds the claim.
    /// </summary>
    /// <param name="name">The name.</param>
    /// <param name="value">The value.</param>
    public void AddClaim(string name, string value)
        this.claimsCollection.Add(name, value);

    /// <summary>
    /// Returns a <see cref="System.String" /> that represents this instance.
    /// </summary>
    /// <returns>
    /// A <see cref="System.String" /> that represents this instance.
    /// </returns>
    public override string ToString()
        StringBuilder content = new StringBuilder();


        foreach (string key in this.claimsCollection.AllKeys)


        if (!string.IsNullOrWhiteSpace(this.Audience))

        using (HMACSHA256 hmac = new HMACSHA256(signingKey))
            byte[] signatureBytes = hmac.ComputeHash(

            string signature = HttpUtility.UrlEncode(


        return content.ToString();

In our case, we will use the same issuer, audience and signing key than defined in the Azure Media Services portal.

It’s now really easy to issue a SWT using these information :

Uri issuerName = new Uri(ConfigurationManager.AppSettings["IssuerName"]);
Uri scope = new Uri(ConfigurationManager.AppSettings["Scope"]);
byte[] signingKey = Convert.FromBase64String(ConfigurationManager.AppSettings["SigningKey"]);

var swt = new SimpleWebToken(ConfigurationManager.AppSettings["SigningKey"], TimeSpan.FromMinutes(10));
swt.Audience = scope.ToString();
swt.Issuer = issuerName.ToString();

string token = swt.ToString();

Upload and encode the asset

In this part you will see how to upload an asset in the Azure BLOB storage and launch an encoding job to generate a multi-bitrate asset. First you have to add the media services NuGet package to your solution :


Be sure to also add the SDK Extensions package that give a set of really cool extensions methods for the media services SDK ! Smile

Creating a media services asset from a file is now really simple using the CreateFromFile extension methods on the assets collection :

string mediaServiceName = ConfigurationManager.AppSettings["MediaServiceName"];
string mediaServiceKey = ConfigurationManager.AppSettings["MediaServiceKey"];

var context = new CloudMediaContext(mediaServiceName, mediaServiceKey);

var asset = context.Assets.CreateFromFile(videoFilePath, AssetCreationOptions.None, (sender, progressChanged) =>
    Console.WriteLine("{0} of {1} bytes uploaded", progressChanged.BytesUploaded, progressChanged.TotalBytes);

Once the asset as been uploaded, you can create a job that contains the encoding task :

IJob job = context.Jobs.CreateWithSingleTask(
    "Adaptive Bitrate MP4",

job = job.StartExecutionProgressTask(j =>
    Console.WriteLine("Job state : {0}", j.State);
    Console.WriteLine("Job progress: {0:0.##}%", j.GetOverallProgress());
}, CancellationToken.None).Result;

If you want more explanations about these extensions for media services, you can follow this post.

Configure dynamic PlayReady on the asset

Once the job is done, an output asset is available on it. This asset represents the multi-bitrate file that has been encoded. You can now generates all the stuff needed to enable PlayReady.

First you have to create a Content Key based on the CommonEncryption type :

private static async Task<IContentKey> CreateCommonTypeContentKeyAsync(IAsset asset, CloudMediaContext _context)
    // Create envelope encryption content key
    Guid keyId = Guid.NewGuid();
    byte[] contentKey = GetRandomBuffer(16);

    IContentKey key = await _context.ContentKeys.CreateAsync(
                            "ContentKey CENC",

    // Associate the key with the asset.

    return key;

private static byte[] GetRandomBuffer(int size)
    byte[] randomBytes = new byte[size];
    using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())

    return randomBytes;

Next you have to generate the token restrictions to associate the issuer, the audience and the signing key of your STS with the content key :

private static List<ContentKeyAuthorizationPolicyRestriction> GetTokenRestrictions(string name, Uri issuer, Uri scope, byte[] signingKey)
    TokenRestrictionTemplate tokenTemplate = new TokenRestrictionTemplate();
    tokenTemplate.Issuer = issuer;
    tokenTemplate.Audience = scope;
    tokenTemplate.PrimaryVerificationKey = new SymmetricVerificationKey(signingKey);

    string requirements = TokenRestrictionTemplateSerializer.Serialize(tokenTemplate);

    List<ContentKeyAuthorizationPolicyRestriction> restrictions = new List<ContentKeyAuthorizationPolicyRestriction>()
            new ContentKeyAuthorizationPolicyRestriction()
                    KeyRestrictionType = (int)ContentKeyRestrictionType.TokenRestricted, 
                    Requirements = requirements, 
                    Name = name

    return restrictions;

Now you have to get the template of PlayReady license you will use. Media Services SDK offers a set of classes to make it easy :

private static string ConfigurePlayReadyLicenseTemplate()
    PlayReadyLicenseResponseTemplate responseTemplate = new PlayReadyLicenseResponseTemplate();
    PlayReadyLicenseTemplate licenseTemplate = new PlayReadyLicenseTemplate();
    licenseTemplate.PlayRight.AllowPassingVideoContentToUnknownOutput = UnknownOutputPassingOption.Allowed;

    return MediaServicesLicenseTemplateSerializer.Serialize(responseTemplate);

These helper methods are used like the following :

private static async Task ConfigurePlayReadyDynamicEncryptionAsync(
    CloudMediaContext cloudMediaContext,
    IAsset outputAsset,
    Uri issuerName,
    Uri scope,
    byte[] signingKey)
    IContentKey contentKey = null;
    var assetContentKeys = outputAsset.ContentKeys
        .Where(c => c.ContentKeyType == ContentKeyType.CommonEncryption);

    if (!assetContentKeys.Any())
        contentKey = await CreateCommonTypeContentKeyAsync(outputAsset, cloudMediaContext);
        contentKey = assetContentKeys.First();

    var tokenRestrictions = GetTokenRestrictions(
        "My STS Token Restrictions",

    var playReadyLicenseTemplate = ConfigurePlayReadyLicenseTemplate();

    IContentKeyAuthorizationPolicyOption policyOption = await
        "Option with Token Restriction",

    IContentKeyAuthorizationPolicy policy = await cloudMediaContext
        .CreateAsync("ACS Authorization Policy");

    await policy.UpdateAsync();

    contentKey.AuthorizationPolicyId = policy.Id;
    await contentKey.UpdateAsync();

    var licenseAcquisitionUrl = await contentKey.GetKeyDeliveryUrlAsync(ContentKeyDeliveryType.PlayReadyLicense);
    string strLicenseAcquisitionUrl = System.Security.SecurityElement.Escape(licenseAcquisitionUrl.ToString());

    Dictionary<AssetDeliveryPolicyConfigurationKey, string> assetDeliveryPolicyConfiguration =
        new Dictionary<AssetDeliveryPolicyConfigurationKey, string>
                AssetDeliveryPolicyConfigurationKey.PlayReadyLicenseAcquisitionUrl, strLicenseAcquisitionUrl

    var assetDeliveryPolicy = await cloudMediaContext.AssetDeliveryPolicies.CreateAsync(
        "PlayReady Delivery Policy",

    await outputAsset.UpdateAsync();


As you can see, once you have the content key, the token restrictions and the PlayReady license to use, you can easily create the content key authorization policy and associate it to the content key. The next step is to create the content delivery policy to make the Media Services PlayReady licenses server able to generate the PlayReady license for your content.

Dynamic encryption with PlayReady is now configured !

Publish the asset

The last step is the asset’s publication. If you have already done this without PlayReady you will see that it works exactly in the same way. First, you have to create an access policy in your media services :

var accessPolicy = context.AccessPolicies
.Where(a => a.Name == "catchup_policy")

if (accessPolicy == null)
    accessPolicy = context.AccessPolicies
        .Create("catchup_policy", TimeSpan.FromDays(100 * 365), AccessPermissions.Read);

Next, you will use this access policy to publish the asset via a locator :

var locator = context.Locators
    .CreateLocator(LocatorType.OnDemandOrigin, asset, accessPolicy);


Et voilà !

Test the asset with PlayReady encryption

To test your new dynamic PlayReady encrypted asset you can go on (developed by the AMS team, I think). Put the smooth streaming URL of your content and the token your STS has delivered, and it just works :


Note : to make the dynamic packaging working you have to enable at least one streaming unit in the streaming endpoint configuration of your media service.


In this blog post we have seen how to upload, encode and protect a content with PlayReady DRM, the new feature available with Azure Media Services. This feature is not yet supported on assets that were recorded from live channels, but in this case you can use AES encryption.

Hope this helps !






Announcing general availability of @Azure database services for #MySQL and #PostgreSQL

March 22, 2018 09:59

Running your private #Kubernetes cluster on #Azure with ACS Engine

March 21, 2018 06:34

How to expose your #Kubernetes workloads on @Azure

March 18, 2018 11:22

RT @as_w: .@Azure Container Instances (ACI) across 3 regions in under 30 seconds with Azure Traffic Manager by @as_w

March 17, 2018 09:41

RT @nepeters: Integrate #Azure storage into your #Kubernetes deployments with Open Service Broker for Azure. Give it a spin with this ready…

March 17, 2018 09:40

RT @AzureFunctions: Great stuff from @jeffhollan! Reliable messaging with Functions and Event Hubs.

March 15, 2018 22:22

Bonsoir @SFR @SFR_SAV, pour la 23ème fois depuis le 28 janvier, coupure de la fibre ! Synchro KO. Quand est-ce que…

March 15, 2018 21:10

@nlamirault @KubernetesParis @sferquel L'intérêt est d'avoir un cluster kubernetes local sur le poste de dev, rempl…

March 15, 2018 13:13

@NicoRobPro @Uber Ouais, et puis après il a repris le périph à l'envers, et après il a oublié de tourner à gauche d…

March 15, 2018 07:49

And for those who want to try Service Catalog + Open Service Broker on @Azure with Azure #Container Service (AKS),…

March 15, 2018 07:45

Slides from my talk @KubernetesParis meetup yesterday about #Kubernetes Service Catalog and Open Service Broker for…

March 15, 2018 07:43

@KubernetesParis @sferquel @joinstationf @Docker @kubernetesio Merci ! Mes slides sont dispo ici :

March 15, 2018 07:42

Pourquoi faire simple quand on peut faire compliqué ^^ arrivée en beauté : "wesh faut que j'arrête et que je dorme…

March 14, 2018 23:04

Quand ton chauffeur @Uber sait pas utiliser Waze et transforme un trajet de 10 minutes en 30 minutes en arrêtant pa…

March 14, 2018 22:52

RT @thomasjkerjean: C’est avec fierté que nous @microsoftfrance ouvrons aujourd’hui nos Datacenters à Paris & Marseille! @BJavelot @jblefev

March 14, 2018 22:46