After you enable M3U8 encryption and rewrite, Alibaba Cloud CDN can rewrite M3U8 files that are transmitted over HTTP Live Streaming (HLS). After an M3U8 file is rewritten, encryption parameters are appended to the #EXT-X-KEY tag of the file. The encryption parameters include the encryption algorithm, key URI, and authentication parameters. After a client receives an M3U8 file that is rewritten by Alibaba Cloud CDN, the client uses the key URI that carries authentication parameters to initiate a request. The request retrieves the key from the CDN edge node. Then, the client uses the encryption algorithm and key to decrypt transport stream (TS) files. M3U8 encryption and rewrite can encrypt HLS data transmission.

This topic consists of the following sections:

Background information

HLS is an HTTP-based adaptive bitrate streaming communications protocol developed by Apple Inc. HLS is based on HTTP. Clients download files from servers over HTTP in order. HLS specifies that video files are encapsulated in TS format. Apart from the TS video file, HLS also specifies the M3U8 file that controls playback. HLS splits a video stream into several TS video files for transmission. At the start of a streaming media session, the client first downloads an M3U8 file that contains TS file URLs, which functions as a media playlist. Then, the client uses the URLs to download TS files.

HLS basic fields:
  • #EXTM3U: the M3U8 file header, which must be placed in the first line.
  • EXT-X-MEDIA-SEQUENC: the serial number of the first TL file. In most cases, this serial number is 0. In live streaming scenarios, this serial number marks the start position of the streaming segment. Example: #EXT-X-MEDIA-SEQUENCE:0.
  • #EXT-X-TARGETDURATION: the maximum length of each TS file. For example, #EXT-X-TARGETDURATION:10 specifies that each TS file can be 10 seconds in length.
  • #EXT-X-ALLOW-CACHE: specifies whether the file can be cached. Valid values: #EXT-X-ALLOW-CACHE:YES and #EXT-X-ALLOW-CACHE:NO. In most cases, the value is set to YES.
  • #EXT-X-ENDLIST: the terminator of the M3U8 file.
  • #EXTINF: contains information about the TS files, such as the length and bandwidth. In most cases, the parameter is set in the #EXTINF:<duration>,[<title>] format. You can append other information to the value. The value before the comma (,) specifies the length of the current TS file. The length of a TS file must be smaller than the value of #EXT-X-TARGETDURATIO.
  • #EXT-X-VERSION: the version number of M3U8.
  • #EXT-X-DISCONTINUITY: specifies that two consecutive TS files are interrupted.
  • #EXT-X-PLAYLIST-TYP: the type of the streaming media.
  • #EXT-X-KEY: specifies whether to encrypt and parse data. For example, #EXT-X-KEY:METHOD=AES-128,URI="https://example.com/video.key?token=xxx" specifies that the encryption algorithm is AES-128. Clients can send requests to https://example.com/video.key?token=xxx to acquire the key. The key is stored on the on-premises machine for decrypting TS files.

How it works

  1. A client sends a request to a CDN edge node for an M3U8 file, sch as http://example.com/media/index.m3u8?MtsHlsUriToken=xxx.
  2. The edge node verifies the request. The request passes the verification.
  3. The edge node downloads the M3U8 file from the origin server and caches the M3U8 file.
  4. The edge node rewrites the #EXT-X-KEY tag of the M3U8 file and appends the encryption algorithm, key URI, and authentication parameters to the tag, such as #EXT-X-KEY:METHOD=AES-128,URI="https://example.com/video.key?MtsHlsUriToken=xxx".
  5. The edge node sends the rewritten M3U8 file to the client.
  6. The client receives and parses the M3U8 file and acquires the key URI https://example.com/video.key?MtsHlsUriToken=xxx. Then, the client sends a request to the URI.
  7. The edge node receives and verifies the request, and sends the key file to the client.
  8. The client continues parsing the M3U8 file and downloads TS files from the edge node.
  9. The client uses the key in the key file and the encryption algorithm specified by #EXT-X-KEY to decrypt downloaded TS files.

Scenarios

HLS uses M3U8 files to provide clients with media playlists. After a client receives an M3U8 file, the client can start video playback. To protect video files on origin servers from unauthorized access, Alibaba Cloud CDN must encrypt the TS files that are transmitted over HLS, and inform the clients of the decryption method. To implement this type of encryption, Alibaba Cloud CDN supports the M3U8 encryption and rewrite feature. This feature uses the #EXT-X-KEY tag to inform clients of the encryption algorithm, key URI, and authentication key.

Procedure

  1. Log on to the ApsaraVideo VOD console and turn on Parameter Pass-through for HLS Encryption.
    For more information, see Parameter pass-through for HLS encryption.
  2. Send a request to the CDN point of presence (POP) to access the M3U8 file and include MtsHlsUriToken in the request.
    To obtain MtsHlsUriToken, you must set up a token service to issue MtsHlsUriToken.
    The following sample code provides an example on how to generate MtsHlsUriToken. The following table describes the parameters in the sample Java code that you can change based on your business requirements.
    ParameterDescription
    ENCRYPT_KEThe encryption key. You can specify a string that is 16, 24, or 32 characters in length.
    INIT_VECTORThe encryption offset. You can specify a string that is 16 characters in length. The value cannot contain special characters.
    import org.apache.commons.codec.binary.Base64;
    import org.apache.commons.lang3.StringUtils;
    
    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 the AES algorithm to generate a token.
        private static String ENCRYPT_KEY = ""; // A custom encryption key. The string must be 16, 24, or 32 characters in length.
        private static String INIT_VECTOR = ""; // A custom encryption offset. The string must be 16 characters in length and cannot contain special characters.
    
        public static void main(String[] args) throws Exception {
    
            String serviceId = "12";
            PlayToken playToken = new PlayToken();
            String aesToken = playToken.generateToken(serviceId);
            //System.out.println("aesToken " + aesToken);
            //System.out.println(playToken.validateToken(aesToken));   // Verify the token.
    
        }
        /**
         * Generate a token based on the configured parameters.
         * Note:
         *  1. The parameters include the user ID and the type of the playback device.
         *  2. A token is generated when the token operation 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 validity period of a token to 30 seconds. You can change the value based on your business requirements.
            long expire = System.currentTimeMillis() + 30000L;
            base += "_" + expire;   // A custom string that is 16 characters in length. In this example, 2 more characters are required because the timestamp contains 13 characters and the underscore (_) is 1 character in length. You can change the value of the base parameter. The value must be 16, 24, or 32 characters in length.
            // Generate a token.
            String token = encrypt(base, ENCRYPT_KEY);  // arg1 is the custom string to be encrypted. arg2 is the encryption key.
            // Save the token. The validity of the token is verified during decryption, such as the validity period and the number of times the token is used.
            saveToken(token);
            return token;
        }
    
        /**
         * Check whether the token is valid.
         * Note:
         *  1. Before the decryption operation returns the playback key, the decryption service checks whether the token is legitimate and valid.
         *  2. We recommend that you check the validity period of the token and the number of times the token is used.
         * @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); // arg1 is the string to be decrypted. arg2 is the decryption key.
            // Check the validity period of the token.
            Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1));
            System.out.println("Check the validity period:" + expireTime);
            if (System.currentTimeMillis() > expireTime) {
                return false;
            }
            // Obtain the token information from the database and check whether the token is valid. You can modify the logic.
            TokenInfo dbToken = getToken(token);
            // Check whether the token has been used.
            if (dbToken == null || dbToken.useCount > 0) {
                return false;
            }
            // Obtain the business attributes for verification.
            String businessInfo = base.substring(0, base.lastIndexOf("_"));
            String[] items = businessInfo.split("_");
            // Verify the validity of the business attributes. You can customize the logic.
            return validateInfo(items);
        }
        /**
         * Save the token to the database.
         * Create custom logic.
         *
         * @param token
         */
        public void saveToken(String token) {
            // TODO. Save the token.
        }
        /**
         * Query the token.
         * Create custom logic.
         *
         * @param token
         */
        public TokenInfo getToken(String token) {
            // TODO. Obtain the token from the database and check whether the token is valid.
            return null;
        }
        /**
         * Verify the validity of the business attributes. You can customize the logic.
         *
         * @param infos
         * @return
         */
        public boolean validateInfo(String... infos) {
            // TODO. Verify the validity of the information, such as the UID.
            return true;
        }
        /**
         * Generate a token by using the AES algorithm.
         *
         * @param encryptStr  The string to be encrypted.
         * @param encryptKey  The encryption key.
         * @return
         * @throws Exception
         */
        public String encrypt(String encryptStr, String encryptKey) throws Exception {
            IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(encryptKey.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, e);
            byte[] encrypted = cipher.doFinal(encryptStr.getBytes());
            return Base64.encodeBase64String(encrypted);
        }
        /**
         * Decrypt the token by using the AES algorithm.
         *
         * @param decryptStr  The string to be decrypted.
         * @param decryptKey  The decryption key.
         * @return
         * @throws Exception
         */
        public String decrypt(String encryptStr, String decryptKey) throws Exception {
    
            IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(decryptKey.getBytes("UTF-8"), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, e);
    
            byte[] encryptByte = Base64.decodeBase64(encryptStr);
            byte[] decryptByte = cipher.doFinal(encryptByte);
            return new String(decryptByte);
        }
        /**
         * Obtain the token information. The sample code is for reference only. You can obtain additional token information based on your business scenario.
         */
        class TokenInfo {
            // Obtain the number of times that the token is used. Modifications must be synchronized in a distributed environment.
            int useCount;
            // The content of the token.
            String token;
        }}
                            
  3. Play the file after the POP receives the request and the request passes the authentication.
    If the value of MtsHlsUriToken generated in step 2 is test, Alibaba Cloud CDN adds MtsHlsUriToken=test to the end of the URI under the #EXT-X-KEY tag in the M3U8 file to decrypt the file.

    You must develop the authentication logic. For more information, see the "Play an HLS-encrypted video" section of the HLS encryption topic.