HLS encryption

Updated at:

HLS encryption prevents unauthorized access to video content and is widely used in online education and finance. Alibaba Cloud supports two encryption methods: Alibaba Cloud proprietary cryptography (recommended) and HLS encryption. This topic explains how HLS encryption works in ApsaraVideo Media Processing (MPS) and how to implement it.

How it works

Key concepts

MPS uses envelope encryption. Your service calls KMS to generate a data key (DK) and an enveloped data key (EDK). The DK encrypts the video. The encrypted file and EDK are stored together. The player obtains the DK from your decryption service to decrypt and play the video.

Note

HLS encryption requires you to protect the data key (DK).

Concept

Description

Data key (DK)

A plaintext key used to encrypt videos.

Enveloped data key (EDK)

A ciphertext key produced by encrypting the DK with envelope encryption. Used to recover the plaintext DK during decryption.

Resource Access Management (RAM)

Manages user identities and resource access permissions. What is RAM?

Key Management Service (KMS)

Provides key management, data encryption, and credential security. What is Key Management Service?

Object Storage Service (OSS)

Stores media resources processed by MPS. What is OSS?

Content Delivery Network (CDN)

Delivers content and dynamically modifies the decryption URI in M3U8 files during HLS playback. What is Alibaba Cloud CDN?

Encryption flow

The encryption flow works as follows:

mts_wf_hls_encrypt

  1. Activate MPS, OSS, RAM, KMS, and CDN if not already activated.

  2. Authorize MPS to access KMS.

    Note

    MPS calls the GenerateDataKey operation of KMS to generate a DK and an EDK during encryption.

  3. Configure the output OSS bucket domain as a CDN-accelerated domain. Set up the CNAME record and origin fetch host.

  4. Create an encryption workflow. Specify the output OSS bucket and the Key URI.

    The Key URI is your decryption service endpoint. MPS writes it into the M3U8 file after encryption.

  5. Upload the video and specify the encryption workflow during upload.

  6. After upload, MPS automatically triggers encryption and transcoding.

    MPS calls GenerateDataKey to generate a DK and an EDK, encrypts the video with the DK, then writes the Key URI and EDK to the M3U8 file.

  7. MPS stores the M3U8 file and the TS files in the output OSS bucket.

Decryption flow

The decryption and playback flow works as follows:

mts_hls_decrypt

  1. Build a token service to issue MtsHlsUriToken tokens.

    Important

    The token service issues MtsHlsUriTokens.

  2. Build a decryption service that calls the KMS Decrypt API to obtain and return the DK to the player.

    Important

    KMS returns the Base64-encrypted data key. Your service must Base64-decode it before returning it to the player.

  3. Call QueryMediaList to get the M3U8 file URL. Append the MtsHlsUriToken and return the URL to the player.

  4. The player sends a request with the MtsHlsUriToken to CDN to retrieve the M3U8 file. CDN rewrites the file to include your Key URI and the EDK, then returns it. The player requests the decryption key and plays the video.

Implementation overview

To implement the full HLS encryption and playback flow, you must build the following components:

  1. Create an encryption workflow.

    Note

    You can create a workflow in the console, but integrating a server-side SDK provides a more complete and efficient encryption service.

  2. Build a token issuance and validation service for MtsHlsUriToken. Each token should be used only once.

  3. Build a decryption service that calls KMS Decrypt. Base64-decode the plaintext key and return it to the player.

Prerequisites

Before you use HLS encryption, complete the following prerequisites:

  1. Activate the required Alibaba Cloud services.

    Activate MPS, OSS, KMS, RAM, and CDN if not already activated.

    1. Activate MPS. Activate MPS

    2. Activate OSS. Activate OSS

    3. Activate KMS. Activate KMS

    4. Activate RAM and grant permissions. Create a RAM user and grant permissions

    5. Activate CDN. Activate CDN

  2. Grant MPS access to KMS.

    1. Log on to the RAM console.

    2. Click Authorize to go to the Authorize page.

    3. In the Principal search box, search for AliyunMtsDefaultRole and select the system-created role for MPS.

    4. In the search box under Permissions, search for KMS, select AliyunKMSFullAccess, and then click OK.

    After authorization, MPS can call KMS API operations to obtain data keys for video encryption.

  3. Configure the CNAME record and origin fetch host for the output OSS bucket. Configure an accelerated domain name. Skip this step if already configured.

    Note

    You can manually enter the public endpoint of an Alibaba Cloud OSS bucket, such as exampleBucket****.oss-cn-hangzhou.aliyuncs.com , or select an OSS bucket that you want to accelerate in the same account. Endpoints for internal access within the same region are not supported.

Encrypt videos

To encrypt a video:

  1. Create an encryption workflow.

    Integrate an Alibaba Cloud SDK with MPS dependencies. Select your programming language for the code sample.

    Important

    When you create the encryption workflow, provide your Key URI. MPS writes this URI to the M3U8 file and stores it in OSS during encryption. Example: example.aliyundoc.com.

    Language

    Integrate SDK

    Encryption workflow code sample

    Java

    Install Java SDK

    Create an HLS encryption workflow

    Python

    Install Python SDK

    Create an HLS encryption workflow

    PHP

    Install PHP SDK

    Create an HLS encryption workflow

    Node.js

    Install Node.js SDK

    Create an HLS encryption workflow

  2. Upload a video to trigger encryption and transcoding. Upload from the MPS console or the OSS console. Upload videos

    Note

    Specifying the encryption workflow during upload automatically triggers encryption and transcoding.

    After encryption, view the M3U8 file in the output bucket on the OSS console. Sample file:

    #EXTM3U
         #EXT-X-VERSION:3
         #EXT-X-TARGETDURATION:5
         #EXT-X-MEDIA-SEQUENCE:0
         #EXT-X-KEY:METHOD=AES-128,URI="https://example.aliyundoc.com?Ciphertext=aabbccddeeff&MediaId=fbbf98691ea44b7c82dd75c5bc8b****"
         #EXTINF:4.127544,
         15029611683170-00001.ts
         #EXT-X-ENDLIST

    The URI contains the Key URI you configured and the EDK obtained from KMS.

Play HLS-encrypted videos

To decrypt and play the video:

  1. Build a token service.

    Note

    Build the token service based on your encryption logic to enhance video security.

  2. Build a decryption service.

    Build a local HTTP service to decrypt the video and return the decryption key. MPS provides code samples in Java and Python.

    • Java code sample

      The Java SDK requires the following dependencies:

      Base64 decryption sample:

      Click to view the sample code

      import com.sun.net.httpserver.*;
      import com.sun.net.httpserver.spi.HttpServerProvider;
      
      import java.io.IOException;
      import java.io.OutputStream;
      import java.net.HttpURLConnection;
      import java.net.InetSocketAddress;
      
      /**
       * *****   Notes   ******
       * This demo provides a code sample for a decryption service that uses Base64.
       * The port number in the demo is 8888. Make sure it is the same as the port of the KeyUri decryption service.
       * If you require additional token verification, see the implementation logic of the KMS-based decryption service.
       *
       * *****   Logic overview   ******
       * 1. Receive a decryption request and obtain the ciphertext key.
       * 2. Base64-decode the plaintext key and return it.
       */
      public class Base64DecryptServer {
      
          public static void main(String[] args) throws IOException {
              Base64DecryptServer server = new Base64DecryptServer();
              server.startService();
          }
      
          public class Base64DecryptHandler implements HttpHandler {
              /**
               * Handle the decryption request.
               * @param httpExchange
               * @throws IOException
               */
              public void handle(HttpExchange httpExchange) throws IOException {
                  String requestMethod = httpExchange.getRequestMethod();
                  if ("GET".equalsIgnoreCase(requestMethod)) {
                      // The decryption key here must be the same as the encryption key.
                      byte[] key = "encryptionkey128".getBytes();
                      // Set the header.
                      setHeader(httpExchange, key);
                      // Return the Base64-decoded key.
                      OutputStream responseBody = httpExchange.getResponseBody();
                      System.out.println(new String(key));
                      responseBody.write(key);
                      responseBody.close();
                  }
              }
              private void setHeader(HttpExchange httpExchange, byte[] key) throws IOException {
                  Headers responseHeaders = httpExchange.getResponseHeaders();
                  responseHeaders.set("Access-Control-Allow-Origin", "*");
                  httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, key.length);
              }
          }
          /**
           * Start the service.
           * @throws IOException
           */
          private void startService() throws IOException {
              HttpServerProvider provider = HttpServerProvider.provider();
              // Listen on port 8888 and accept 30 concurrent requests.
              HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8888), 30);
              httpserver.createContext("/", new Base64DecryptHandler());
              httpserver.start();
              System.out.println("base64 hls decrypt server started");
          }
      
      }

      KMS decryption sample:

      Click to view the sample code

      import com.aliyun.mps.sdk.utils.InitClient;
      import com.aliyuncs.DefaultAcsClient;
      import com.aliyuncs.exceptions.ClientException;
      import com.aliyuncs.http.ProtocolType;
      import com.aliyuncs.kms.model.v20160120.DecryptRequest;
      import com.aliyuncs.kms.model.v20160120.DecryptResponse;
      import com.sun.net.httpserver.*;
      import com.sun.net.httpserver.spi.HttpServerProvider;
      import org.apache.commons.codec.binary.Base64;
      
      import java.io.IOException;
      import java.io.OutputStream;
      import java.net.HttpURLConnection;
      import java.net.InetSocketAddress;
      import java.net.URI;
      import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      /**
       * *****   Notes   ******
       * This demo provides a code sample for a KMS-based decryption service when standard encryption rewriting is disabled. It does not include the logic for MtsHlsUriToken verification.
       * The port number in the demo is 8888. Make sure it is the same as the port of the KeyUri decryption service.
       *
       * *****   Logic overview   ******
       * 1. Receive a decryption request and obtain the ciphertext key.
       * 2. Call the KMS Decrypt operation to obtain the plaintext key from the ciphertext. 
       * 3. Base64-decode the plaintext key and return it.
       */
      public class HlsDecryptServerNoToken {
      
          private static DefaultAcsClient client;
          static {
              try{
                  client = InitClient.initMpsClient();
              }catch (Exception e){
                  e.printStackTrace();
              }
          }
      
          public static void main(String[] args) throws IOException {
              HlsDecryptServerNoToken server = new HlsDecryptServerNoToken();
              server.startService();
          }
      
          public class HlsDecryptHandler implements HttpHandler {
              public void handle(HttpExchange httpExchange) throws IOException {
                  String requestMethod = httpExchange.getRequestMethod();
                  if(requestMethod.equalsIgnoreCase("GET")){
                      // Get the ciphertext key from the URL.
                      String ciphertext = getCiphertext(httpExchange);
                      System.out.println(ciphertext);
                      if (null == ciphertext)
                          return;
                      // Decrypt it in KMS and then Base64-decode it.
                      byte[] key = decrypt(ciphertext);
                      // Set the header.
                      setHeader(httpExchange, key);
                      // Return the key.
                      OutputStream responseBody = httpExchange.getResponseBody();
                      responseBody.write(key);
                      responseBody.close();
                  }
              }
      
              private void setHeader(HttpExchange httpExchange, byte[] key) throws IOException {
                  Headers responseHeaders = httpExchange.getResponseHeaders();
                  responseHeaders.set("Access-Control-Allow-Origin", "*");
                  httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, key.length);
              }
      
              private byte[] decrypt(String ciphertext) {
                  DecryptRequest request = new DecryptRequest();
                  request.setCiphertextBlob(ciphertext);
                  request.setProtocol(ProtocolType.HTTPS);
                  try {
                      DecryptResponse response = client.getAcsResponse(request);
                      String plaintext = response.getPlaintext();
                      // Note: The key must be Base64-decoded.
                      return Base64.decodeBase64(plaintext);
                  } catch (ClientException e) {
                      e.printStackTrace();
                      return null;
                  }
              }
              private String getCiphertext(HttpExchange httpExchange) {
                  URI uri = httpExchange.getRequestURI();
                  String queryString = uri.getQuery();
                  String pattern = "Ciphertext=(\\w*)";
                  Pattern r = Pattern.compile(pattern);
                  Matcher m = r.matcher(queryString);
                  if (m.find())
                      return m.group(1);
                  else {
                      System.out.println("Not Found Ciphertext");
                      return null;
                  }
              }
          }
      
          /**
           * Start the service.
           *
           * @throws IOException
           */
          private void startService() throws IOException {
              HttpServerProvider provider = HttpServerProvider.provider();
              // Listen on port 8888 and accept 30 concurrent requests. You can change the port number as needed.
              HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8888), 30);
              httpserver.createContext("/", new HlsDecryptHandler());
              httpserver.start();
              System.out.println("no token hls decrypt server started");
          }
      
      }
    • Python code sample

      The Python SDK requires the following dependencies:

      • pip install aliyun-python-sdk-core

      • pip install aliyun-python-sdk-kms

      • pip install aliyun-python-sdk-mts

      The following is a Python code sample:

      Click to view the sample code

      # -*- coding: UTF-8 -*-
      from http.server import BaseHTTPRequestHandler, HTTPServer
      from urllib.parse import urlparse, parse_qs
      from aliyunsdkcore.client import AcsClient
      from aliyunsdkcore.request import CommonRequest
      import cgi
      import json
      import base64
      import logging
      from urllib.parse import urlparse
      
      logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
      # Please replace access_key_id, access_secret, and region_id.
      client = AcsClient("","")
      
      class AuthorizationHandler(BaseHTTPRequestHandler):
        def do_GET(self):
          #self.check()
          self.set_header()
          cipertext = self.get_cihpertext()
          #print(cipertext)
          plaintext = self.decrypt_cihpertext(cipertext)
          #print(plaintext)
          key = base64.b64decode(plaintext)
          #print(key)
          self.wfile.write(key)
      
        def do_POST(self):
          pass
        
        def check(self):
      # Check MtsHlsUriToken, etc.
          pass
        
        def set_header(self):
          self.send_response(200)
      # CORS
          self.send_header('Access-Control-Allow-Origin', '*')
          self.end_headers()
      
        def get_cihpertext(self):
          path = urlparse(self.path)
          query = parse_qs(path.query) 
          return query.get('Ciphertext')[0]
          
        def decrypt_cihpertext(self, cipertext):
          request = CommonRequest()
          request.set_domain('')
          request.set_version('2014-06-18')
          request.set_action_name('DecryptKMSDataKey')
          request.add_query_param('CipherText', cipertext)
      
          response = client.do_action_with_exception(request)
          # print("####################################")
          # print(response)
          jsonResp = json.loads(response)
          return jsonResp["PlainText"]
        
      if __name__ == '__main__':
      # Start a simple server and loop forever.
        print("Starting server, use <Ctrl+C> to stop")
        server = HTTPServer(('127.0.0.1', 8099), AuthorizationHandler)
        server.serve_forever()
  3. Call QueryMediaList to retrieve the playback URL.

    Call this operation in API Explorer or integrate it into your service.

  4. Play the encrypted video.

    Use your own player or ApsaraVideo Player to play the encrypted video.

    • If you use a different player, implement the playback logic yourself.

    • If you use ApsaraVideo Player, obtain the token and authentication information, then start playback. Video playback

    You can test playback with an online player.

    For example, use the ApsaraVideo Player diagnostic tool. Paste the playback URL and click Play Video.

    Note

    During browser debugging, the player automatically requests the decryption key from the authentication server, then decrypts and plays the video.

    The playback process in ApsaraVideo Player works as follows:

    • The player replaces the OSS domain with the CDN domain, appends MtsHlsUriToken, and requests the M3U8 file from CDN. Sample request: https://example.aliyundoc.com/test_01.m3u8?MediaId=fbbf98691ea44b7c82dd75c5bc8b****&MtsHlsUriToken=<The token issued by your service>.

      Important

      ApsaraVideo Player automatically appends the MtsHlsUriToken. If you use a different player, you must append the token manually.

    • CDN dynamically modifies the decryption URI in the M3U8 file. For example, https://example.aliyundoc.com?Ciphertext=aabbccddeeff&MediaId=fbbf98691ea44b7c82dd75c5bc8b**** becomes https://example.aliyundoc.com?Ciphertext=aabbccddeeff&MediaId=fbbf98691ea44b7c82dd75c5bc8b****&MtsHlsUriToken=<The token issued by your service>.

    • The player accesses the URI in the EXT-X-KEY tag to obtain the decryption key. Your service calls Decrypt, Base64-decodes the plaintext key, and returns it to the player. The player uses the DK to decrypt the TS files for playback.

References

Alibaba Cloud proprietary cryptography