All Products
Search
Document Center

:HLS encryption

Last Updated:Jan 29, 2026

HLS encryption requires Key Management Service (KMS) and a token service. This topic describes the related concepts, preparations, and integration process for HLS encryption.

HLS encryption and decryption flow

Note
  • Using Key Management Service incurs fees. For more information, see Billing of KMS 1.0.

  • You must build the token service and decryption service yourself.

Related concepts

  • Key Management Service

    Key Management Service (KMS) is a security management service that handles the generation, encryption, and decryption of data keys.

  • Resource Access Management

    Resource Access Management (RAM) is an Alibaba Cloud service that provides user identity management and resource access control.

  • Data key

    A data key (DK), also known as a plaintext key, is used to encrypt data.

  • Enveloped data key

    An enveloped data key (EDK), also known as a ciphertext key, is a data key protected using envelope encryption.

Preparations

  1. Activate ApsaraVideo VOD, log on to the ApsaraVideo VOD console, and enable the bucket in the corresponding region. For more information, see Quick Start.

  2. In the ApsaraVideo VOD console, configure an accelerated domain name. In the Video Related section for the domain name, enable Parameter Pass-through for HLS Encryption. After enabling this feature, the MtsHlsUriToken parameter can be rewritten. For more information, see Add an accelerated domain name and Parameter pass-through for HLS encryption.

  3. Log on to the RAM console to obtain and save your AccessKey ID and AccessKey secret.

  4. Activate Key Management Service and obtain a service key.

    Note
    • A service key is a customer master key (CMK) in Key Management Service. You must use this service key to generate keys for standard encryption.

    • The service key must be in the same region as the origin bucket where your videos are stored. For example, if your videos are stored in China (Shanghai), create the service key in China (Shanghai).

    1. Log on to the ApsaraVideo VOD console. Choose Configuration Management > Media Processing > HLS Standard Encryption. On the HLS Standard Encryption page, create a service key.

    2. After successful creation, call the GenerateDataKey operation. Set the request parameter KeyId to the alias alias/acs/vod. Use the returned KeyId parameter for subsequent transcoding.

  5. Set up a server-side SDK. For more information, see Install server-side SDKs.

Integration process

  1. Add an encryption template and a no-transcoding template.

    HLS standard encryption and transcoding require two transcoding templates: an encryption template and a no-transcoding template.

    The no-transcoding template is automatically generated after you enable the bucket in the corresponding region.

    Note

    By default, ApsaraVideo VOD automatically transcodes uploaded videos. However, automatic transcoding does not support HLS standard encryption. To prevent automatic transcoding, first upload videos using the no-transcoding template (which does not trigger automatic transcoding). Then, call the SubmitTranscodeJobs operation to start standard encryption transcoding.

    Add an encryption template and save its ID as follows:

    1. Log on to the ApsaraVideo VOD console.

    2. In the Configuration Management area, choose Media Processing > Transcoding Template Groups > Create Transcoding Template Group.

    3. On the Create Transcoding Template Group page, enter a Template Group Name.

    4. In the Regular Transcoding Template section, click Add Template to create a transcoding template.

    5. In the Basic Parameters section, set Encapsulation Format to hls.

    6. In the Video Parameters, Audio Parameters, and Conditional Transcoding Parameters sections, configure parameters as needed. For details about each parameter and configuration limits, see Audio and video transcoding.

    7. In the Advanced Parameters area, enable Video Encryption and check the Alibaba Cloud Encryption option (This option is checked by default. Unchecking it disables video encryption.).

    8. 加密模板

      Note

      When calling the SubmitTranscodeJobs operation, pass this template ID in the TemplateGroupId parameter. ApsaraVideo VOD then performs standard encryption transcoding based on the template settings and provided key information.

    9. Click Save. You are automatically redirected to the Transcoding Template Groups page. Obtain and save the encryption template ID.

  2. Grant RAM authorization.

    Use RAM to grant ApsaraVideo VOD permission to access your Key Management Service resources. Go to the RAM authorization page and click Confirm Authorization Policy.

    授权页面

  3. Build a key management service that encapsulates Alibaba Cloud KMS.

    Call the GenerateDataKey operation to generate an AES_128 key. Pass only the KeyId (service key) and KeySpec (fixed value: AES_128). Do not pass other parameters, or encryption may fail.

    After a successful call, save the value of the returned CiphertextBlob parameter (the ciphertext key).

    Note

    Using keys incurs fees. For details, see API call fees.

  4. Build a token issuance service to generate an MtsHlsUriToken.

    The following Java sample code shows parts that require manual changes:

    • ENCRYPT_KEY: A 16-character encryption string that you define.

    • INIT_VECTOR: A custom 16-character string that contains no special characters.

    • playToken.generateToken(""): A custom 16-character string.

    The token generated by this code is the MtsHlsUriToken.

    import com.sun.deploy.util.StringUtils;
    import org.apache.commons.codec.binary.Base64;
    
    import javax.crypto.Cipher;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.util.Arrays;
    
    public class PlayToken {
        // The following parameters are not required if you do not use AES to generate the token.
        // The encryption string. Define it yourself.
        private static String ENCRYPT_KEY = "";  
        // A custom string of 16 characters. It cannot contain special characters.
        private static String INIT_VECTOR = "";  
    
        public static void main(String[] args) throws Exception {
    
            PlayToken playToken = new PlayToken();
            playToken.generateToken("");
        }
    
        /**
         * Generate a token based on the passed parameters.
         * Note:
         *  1. The parameters can be your user ID, the type of the playback client, and other information.
         *  2. A token is generated when the token API is called.
         * @param args
         * @return
         */
        public String generateToken(String... args) throws Exception {
            if (null == args || args.length <= 0) {
                return null;
            }
            String base = StringUtils.join(Arrays.asList(args), "_");
            // Set the token to expire after 30 seconds. You can adjust the expiration time.
            long expire = System.currentTimeMillis() + 30000L;
            // A custom string. The final length of the base string must be 16 characters. In this example, the timestamp occupies 13 characters and the underscore (_) occupies 1 character. This means you need to pass another 2 characters. You can also change the entire string as needed, but ensure the final base string is 16 characters long.
            base += "_" + expire;  
            // Generate the token.
            String token = encrypt(base, ENCRYPT_KEY);
            System.out.println(token);
            // Save the token to validate its effectiveness during decryption, such as its expiration time and number of uses.
            saveToken(token);
            return token;
        }
        /**
         * Validate the effectiveness of the token.
         * Note:
         *  1. Before the decryption API returns the playback key, it must validate the legitimacy and effectiveness of the token.
         *  2. We strongly recommend that you also validate the token's expiration time and its number of valid uses.
         * @param token
         * @return
         * @throws Exception
         */
        public boolean validateToken(String token) throws Exception {
            if (null == token || "".equals(token)) {
                return false;
            }
            String base = decrypt(token, ENCRYPT_KEY);
            // First, validate the token's expiration time.
            Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1));
            if (System.currentTimeMillis() > expireTime) {
                return false;
            }
            // Obtain the token information from the database to determine its effectiveness. Implement this yourself.
            Token dbToken = getToken(token);
            // Check if the token has been used.
            if (dbToken == null || dbToken.useCount > 0) {
                return false;
            }
            // Obtain business attribute information for validation.
            String businessInfo = base.substring(0, base.lastIndexOf("_"));
            String[] items = businessInfo.split("_");
            // Validate the legitimacy of the business information. Implement this yourself.
            return validateInfo(items);
        }
        /**
         * Save the token to the database.
         * Implement this yourself.
         *
         * @param token
         */
        public void saveToken(String token) {
            //TODO Store the token.
        }
        /**
         * Query the token.
         * Implement this yourself.
         *
         * @param token
         */
        public Token getToken(String token) {
            //TODO Obtain the token information from the database to validate its effectiveness and legitimacy.
            return null;
        }
        /**
         * Validate the effectiveness of the business information. Implement this yourself.
         *
         * @param infos
         * @return
         */
        public boolean validateInfo(String... infos) {
            //TODO Validate the effectiveness of the information, such as whether the UID is valid.
            return true;
        }
        /**
         * Generate a token using AES encryption.
         *
         * @param key
         * @param value
         * @return
         * @throws Exception
         */
        public String encrypt(String value, String key) throws Exception {
            IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, e);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.encodeBase64String(encrypted);
        }
        /**
         * Decrypt the token using AES.
         *
         * @param key
         * @param encrypted
         * @return
         * @throws Exception
         */
        public String decrypt(String encrypted, String key) throws Exception {
            IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, e);
            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
            return new String(original);
        }
        /**
         * Token information. You can provide more information. This is just an example.
         */
        class Token {
            // The number of times the token can be used. In a distributed environment, be aware of synchronization issues.
            int useCount;
            // The token content.
            String token;
        }}
  5. Build a decryption service.

    Important

    Start the decryption service before video playback. Otherwise, the video cannot be decrypted.

    To decrypt the EDK (ciphertext key), call the Decrypt operation. To authenticate requests to the decryption API, provide a token generation service. The decryption service must parse and verify the generated token.

    The decryption API returns the base64-decoded plaintext (PlainText) originally generated by the GenerateDataKey operation.

    The following Java sample code shows parts that require manual changes:

    • region: Enter the region ID. For China (Shanghai), enter cn-shanghai.

    • AccessKey: Enter the AccessKey ID and AccessKey secret for your account.

    • httpserver: Select the port number for the service.

    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.aliyuncs.profile.DefaultProfile;
    import com.sun.net.httpserver.Headers;
    import com.sun.net.httpserver.HttpExchange;
    import com.sun.net.httpserver.HttpHandler;
    import com.sun.net.httpserver.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;
    public class HlsDecryptServer {
        private static DefaultAcsClient client;
        static {
            // The KMS region must match the video region.
            String region = "";
            // The authorized AccessKey information for accessing KMS.
            // An AccessKey of an Alibaba Cloud account has full permissions on all APIs. We recommend that you use a RAM user for API access or routine O&M.
            // We strongly recommend that you do not hard-code the AccessKey ID and AccessKey secret in your project code. Otherwise, the AccessKey pair may be leaked, which compromises the security of all your resources.
            // This example reads the AccessKey from environment variables to authenticate API access. Before you run the example code, configure the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.
            String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
            String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
            client = new DefaultAcsClient(DefaultProfile.getProfile(region, accessKeyId, accessKeySecret));
        }
        /**
         * Note:
         * 1. Receive the decryption request and obtain the ciphertext key and token.
         * 2. Call the KMS decrypt API to obtain the plaintext key.
         * 3. Return the base64-decoded plaintext key.
         */
        public class HlsDecryptHandler implements HttpHandler {
            /**
             * Process the decryption request.
             * @param httpExchange
             * @throws IOException
             */
            public void handle(HttpExchange httpExchange) throws IOException {
                String requestMethod = httpExchange.getRequestMethod();
                if ("GET".equalsIgnoreCase(requestMethod)) {
                    // Validate the effectiveness of the token.
                    String token = getMtsHlsUriToken(httpExchange);
                    boolean validRe = validateToken(token);
                    if (!validRe) {
                        return;
                    }
                    // Get the ciphertext key from the URL.
                    String ciphertext = getCiphertext(httpExchange);
                    if (null == ciphertext)
                        return;
                    // Decrypt from KMS and Base64 decode.
                    byte[] key = decrypt(ciphertext);
                    // Set the header.
                    setHeader(httpExchange, key);
                    // Return the base64-decoded 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);
            }
            /**
             * Call the KMS decrypt API to decrypt, and then base64 decode the plaintext.
             * @param ciphertext
             * @return
             */
            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: Base64 decoding is required.
                    return Base64.decodeBase64(plaintext);
                } catch (ClientException e) {
                    e.printStackTrace();
                    return null;
                }
            }
            /**
             * Validate the token's effectiveness.
             * @param token
             * @return
             */
            private boolean validateToken(String token) {
                if (null == token || "".equals(token)) {
                    return false;
                }
                //TODO Implement token effectiveness validation.
                return true;
            }
            /**
             * Get the ciphertext key parameter from the URL.
             * @param httpExchange
             * @return
             */
            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 Param");
                    return null;
                }
            }
            /**
             * Get the Token parameter.
             *
             * @param httpExchange
             * @return
             */
            private String getMtsHlsUriToken(HttpExchange httpExchange) {
                URI uri = httpExchange.getRequestURI();
                String queryString = uri.getQuery();
                String pattern = "MtsHlsUriToken=(\\w*)";
                Pattern r = Pattern.compile(pattern);
                Matcher m = r.matcher(queryString);
                if (m.find())
                    return m.group(1);
                else {
                    System.out.println("Not Found MtsHlsUriToken Param");
                    return null;
                }
            }
        }
        /**
         * Start the service.
         *
         * @throws IOException
         */
        private void serviceBootStrap() throws IOException {
            HttpServerProvider provider = HttpServerProvider.provider();
            // The listening port can be customized. It can accept up to 30 requests at the same time.
            HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(8099), 30);
            httpserver.createContext("/", new HlsDecryptHandler());
            httpserver.start();
            System.out.println("hls decrypt server started");
        }
        public static void main(String[] args) throws IOException {
            HlsDecryptServer server = new HlsDecryptServer();
            server.serviceBootStrap();
        }}
  6. Upload a video.

    Use the No Transcoding template to create a video upload URL and credential. For console instructions, see Upload files in the ApsaraVideo VOD console. For server-side API instructions, see Obtain the upload URL and credential for audio or video.

  7. Receive the upload completion callback message.

    Call the SetMessageCallback operation to set a callback. Call the GetMessageCallback operation to query callback messages. When you receive the callback message for a Video Upload Completed event, the file has been uploaded to ApsaraVideo VOD.

  8. Start standard encryption transcoding.

    Call the SubmitTranscodeJobs operation to start standard encryption transcoding.

    The following Java sample code shows parts that require manual changes:

    • request.setTemplateGroupId(""): The encryption template ID.

    • request.setVideoId(""): The video ID.

    • encryptConfig.put("CipherText",""): The CiphertextBlob value from Step 3.

    • encryptConfig.put("DecryptKeyUri",""): The playback URL containing the CiphertextBlob value and the MtsHlsUriToken. For example, if the local port is 8099, the playback URL is: http://172.16.0.1:8099?CipherText=CiphertextBlobValue&MtsHlsUriToken=MtsHlsUriTokenValue.

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import com.aliyuncs.DefaultAcsClient;
    import com.aliyuncs.exceptions.ClientException;
    import com.aliyuncs.profile.DefaultProfile;
    import com.aliyuncs.vod.model.v20170321.SubmitTranscodeJobsRequest;
    import com.aliyuncs.vod.model.v20170321.SubmitTranscodeJobsResponse;
    
    public class SubmitTranscodeJobs {
    
        // An AccessKey of an Alibaba Cloud account has full permissions on all APIs. We recommend that you use a RAM user for API access or routine O&M.
        // We strongly recommend that you do not hard-code the AccessKey ID and AccessKey secret in your project code. Otherwise, the AccessKey pair may be leaked, which compromises the security of all your resources.
        // This example reads the AccessKey from environment variables to authenticate API access. Before you run the example code, configure the ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET environment variables.
        private static String accessKeyId = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID");
        private static String accessKeySecret = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET");
    
        public static SubmitTranscodeJobsResponse submitTranscodeJobs(DefaultAcsClient client) throws Exception{
            SubmitTranscodeJobsRequest request = new SubmitTranscodeJobsRequest();
            request.setTemplateGroupId("");
            request.setVideoId("");
            JSONObject encryptConfig = new JSONObject();
            encryptConfig.put("CipherText","");
            encryptConfig.put("DecryptKeyUri","");
            encryptConfig.put("KeyServiceType","KMS");
            request.setEncryptConfig(encryptConfig.toJSONString());
            return client.getAcsResponse(request);
        }
    
        public static void main(String[] args) throws ClientException {
            // The region where the ApsaraVideo VOD service is accessed.  
            String regionId = "cn-shanghai";  
            DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
            DefaultAcsClient client = new DefaultAcsClient(profile);
    
            SubmitTranscodeJobsResponse response;
            try {
                response = submitTranscodeJobs(client);
                System.out.println("RequestId is:"+response.getRequestId());
                System.out.println("TranscodeTaskId is:"+response.getTranscodeTaskId());
                System.out.println("TranscodeJobs is:"+ JSON.toJSON(response.getTranscodeJobs()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }       
  9. Verify that encryption transcoding succeeded.

    Log on to the ApsaraVideo VOD console to view the video URL. Use one of the following methods to determine whether standard encryption succeeded:

    • If the video has only one M3U8-format URL after encryption transcoding, the video status is Transcoding Failed.

    • If the video output includes formats other than M3U8 (for example, the original MP4 file), check whether the M3U8 format is followed by HLS Standard Encryption. If present, standard encryption succeeded.

    • If neither method above works, copy the URL of the M3U8 file with the encryption flag and run curl -v "M3U8 file URL". Check whether the M3U8 content contains the key information URI="<The decryption URL you passed when initiating standard encryption, which is the value of the DecryptKeyUri parameter in EncryptConfig>". If present, standard encryption succeeded.

Playback process

  1. Obtain the video playback URL and credential.

    Call the GetPlayInfo and GetVideoPlayAuth operations to obtain the video playback URL and credential.

  2. Pass authentication information.

    After a player obtains the address of an m3u8 file, it parses the file and accesses the URI from the EXT-X-KEY tag to retrieve the decryption interface URI for the ciphertext key. This URI is the value of the DecryptKeyUri parameter in the EncryptConfig configuration that you pass when you initiate standard encryption.

    To restrict access to legitimate users only, the player must include authentication information you recognize when requesting the decryption key. Pass this authentication information in the MtsHlsUriToken parameter.

    Example:

    • The video playback URL is https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8. The request must include the MtsHlsUriToken parameter.

    • The final request URL is: https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8?MtsHlsUriToken=<token>

    • The decryption URL is https://demo.aliyundoc.com?Ciphertext=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****.

    • The final decryption request URL is: https://demo.aliyundoc.com?Ciphertext=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****&MtsHlsUriToken=<issued_token>.

  3. Play the video.

    When the player parses the decryption URI, it automatically calls the decryption API to obtain the decryption key. The player then uses this key to decrypt the encrypted TS file for playback.