HTTP Live Streaming (HLS) encryption must be used together with Key Management Service (KMS) and the token service. This topic describes the terms, preparations, and related procedures of HLS encryption.
Terms
KMS
KMS is a security management service that is used to generate, encrypt, and decrypt data keys.
RAM
Resource Access Management (RAM) allows you to manage user identities and control access to resources.
DK
Data key (DK) is called the plaintext key and is used to encrypt data.
EDK
Enveloped data key (EDK) is also called the ciphertext key and is generated by using envelope encryption.
Preparations
Activate ApsaraVideo VOD and log on to the ApsaraVideo VOD console to enable the bucket in your region. For more information, see Step 1: Enable storage management.
In the ApsaraVideo VOD console, configure an accelerated domain name and turn on Parameter Pass-through for HLS Encryption in Video Related for the domain name. After you enable this feature, the MtsHlsUriToken parameter can be rewritten. For more information, see Add a domain name for CDN and Parameter pass-through for HLS encryption.
Log on to the RAM console. Obtain and save the AccessKey ID and AccessKey secret.
Activate KMS and obtain a service key.
NoteA service key is a primary encryption key used in KMS. You must use the service key to generate keys for HLS encryption.
A service key must be created in the same region as the origin server where videos are stored. For example, if videos are stored in the China (Shanghai) region, you must create a service key in the China (Shanghai) region.
Log on to the ApsaraVideo VOD console. In the left-side navigation pane, choose . In the upper-left corner of the page, select the region in which you want to create a service key and click Create Service Key.
After the service key is created, call the GenerateDataKey operation and set
KeyId
to alias/acs/vod. The returnedKeyId
is used in subsequent transcoding processes.
Set up a server SDK based on your business requirements. For more information, see Installation.
Encryption procedure
Add a transcoding template and a non-transcoding template.
HLS encryption and transcoding require two templates: a No Transcoding template and a transcoding template with encryption configured.
The No Transcoding template is automatically generated after you enable the bucket in your region.
NoteBy default, ApsaraVideo VOD automatically transcodes the videos that are uploaded. Automatic transcoding does not support HLS encryption. To prevent automatic transcoding when you use HLS encryption, you must use the No Transcoding template to upload videos. Then, you can call the SubmitTranscodeJobs operation to start HLS encryption and transcoding.
To create a transcoding template with encryption configured and record the template ID, perform the following steps:
Log on to the ApsaraVideo VOD console.
In the left-side navigation pane, choose . On the page that appears, click Create Transcoding Template Group.
On the Create Transcoding Template Group page, enter a name in the Template Group Name field.
In the Regular Transcoding Template section, click Add Template to create a transcoding template.
In the Basic Parameters section, select hls from the Encapsulation Format drop-down list.
In the Video Parameters, Audio Parameters, and Conditional Transcoding Parameters sections, configure the parameters based on your business requirements. For more information about the meaning and limits of each parameter, see Overview.
In the Advanced Parameters section, turn on Video Encryption and keep Alibaba Cloud Proprietary Cryptography selected for Encryption Method.
NoteWhen you call the SubmitTranscodeJobs operation, you can specify the TemplateGroupId parameter to use this template. Then ApsaraVideo VOD encrypts and transcodes videos based on the template settings and key.
Click Save. On the Transcoding Template Groups page, obtain and record the encryption template ID.
Authorize ApsaraVideo VOD to access your KMS resources.
To use RAM to grant ApsaraVideo VOD permissions to access your KMS resources, click Confirm Authorization Policy on the Cloud Resource Access Authorization page.
Set up a key management service that encapsulates Alibaba Cloud KMS.
When you call the GenerateDataKey operation to generate an AES_128 key, you need to only set the KeyId parameter to the ID of the service key and the KeySpec parameter to AES_128. If you do not configure the preceding parameters, the encryption may fail.
After the call is successful, record the value of the returned parameter
CiphertextBlob
. The parameter indicates the ciphertext key.NoteYou are charged for using a key. For more information, see Billing of KMS.
Set up a token issuance service to generate MtsHlsUriToken.
The following content shows the Java sample code and parameters in the code that require manual change.
ENCRYPT_KEY: a custom string for encryption. The string is 16 characters in length.
INIT_VECTOR: a custom string. The string is 16 characters in length and cannot contain special characters.
playToken.generateToken(""): a custom string. The string is 16 characters in length.
The token generated by the final code is 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 the AES algorithm to generate a token. // A custom string for encryption. private static String ENCRYPT_KEY = ""; // A custom string that is 16 characters in length and 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 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; // 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. Make sure that the value is 16 characters in length. base += "_" + expire; // Generate a token. String token = encrypt(base, ENCRYPT_KEY); System.out.println(token); // 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); // Check the validity period of the token. Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1)); 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. Token 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("_"); // Check the validity of the business attributes. You can create custom 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 Token getToken(String token) { // TODO. Obtain the token from the database and check whether the token is valid. return null; } /** * Check the validity of the business attributes. You can create a custom logic. * * @param infos * @return */ public boolean validateInfo(String... infos) { // TODO. Check the validity of the information, such as the UID. return true; } /** * Generate a token by using the AES algorithm. * * @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 by using the AES algorithm. * * @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); } /** * Obtain the token information. This sample code is for reference only. You can obtain more token information based on your business scenario. */ class Token { // Obtain the number of times that the token can be used. Modifications must be synchronized in a distributed environment. int useCount; // The content of the token. String token; }}
Set up a decryption service.
ImportantThe decryption service must be started before you play the video. Otherwise, the video cannot be decrypted as expected.
Call the Decrypt operation of KMS to decrypt EDKs. If you want to authenticate the decryption operation, set up a token issuance service and make sure that the issued token can be parsed and verified by the decryption service.
The GenerateDataKey operation is used to generate a ciphertext key and a plaintext key. The plaintext key is Base64-encoded. The decryption operation returns the key that is decoded by using the Base64 algorithm.
The following content shows the Java sample code and parameters in the code that require manual change.
region: Enter a region ID. For example, specify cn-shanghai for China (Shanghai).
AccessKey: Enter the AccessKey ID and the AccessKey secret of your account.
httpserver: Enter the port number to start the service based on your business requirements.
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 region where KMS is used. The region must be the same as the region where the video resides. String region = ""; // The AccessKey pair that is authorized to access KMS. // The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. We recommend that you use a RAM user to call API operations or perform routine O&M. // We recommend that you do not include your AccessKey pair (AccessKey ID and AccessKey secret) in your project code. Otherwise, the AccessKey pair may be leaked and the security of all resources within your account may be compromised. // In this example, ApsaraVideo VOD reads the AccessKey pair from the environment variables to implement identity verification for API access. Before you run the sample code, configure the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET. 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 a decryption request and obtain the EDK and token. * 2. Call the decryption operation of KMS to obtain the DK. * 3. Decode the DK by using the Base64 algorithm and return the DK. */ 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)) { // Check whether the token is valid. String token = getMtsHlsUriToken(httpExchange); boolean validRe = validateToken(token); if (!validRe) { return; } // Obtain the EDK from the video URL. String ciphertext = getCiphertext(httpExchange); if (null == ciphertext) return; // Decrypt the key in KMS and decode the key by using the Base64 algorithm. byte[] key = decrypt(ciphertext); // Configure the headers. setHeader(httpExchange, key); // Return the key that is decoded by using the Base64 algorithm. 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 decryption operation of KMS to decrypt the key and decode the key by using the Base64 algorithm. * @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: You must decode the key by using the Base64 algorithm. return Base64.decodeBase64(plaintext); } catch (ClientException e) { e.printStackTrace(); return null; } } /** * Check whether the token is valid. * @param token * @return */ private boolean validateToken(String token) { if (null == token || "".equals(token)) { return false; } // TODO. You can create custom logic to check whether the token is valid. return true; } /** * Obtain the EDK from the video 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; } } /** * Obtain the value of 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(); // You can specify a custom listening port. The listening port can receive 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(); }}
Upload a video.
Use the No Transcoding template to create the credential and the URL that you can use to upload the video. For more information about how to upload a video by using the ApsaraVideo VOD console, see Upload media files by using the ApsaraVideo VOD console. For more information about how to upload a video by calling a server operation, see CreateUploadVideo.
Receive the upload callback message.
Call the SetMessageCallback operation to configure the callback and call the GetMessageCallback operation to query the callback message. After you receive the callback message of FileUploadComplete, the video is uploaded to ApsaraVideo VOD.
Start HLS Encryption and transcoding
Call the SubmitTranscodeJobs operation to start HLS encryption and transcoding.
The following content shows the Java sample code and parameters in the code that require manual change.
request.setTemplateGroupId(""): Specify the ID of the encryption template.
request.setVideoId(""): Specify the video ID.
encryptConfig.put("CipherText",""): Specify the value of the CiphertextBlob parameter that is obtained in Step 3.
encryptConfig.put("DecryptKeyUri",""): Specify the playback URL, the value of
CiphertextBlob
, and the value ofMtsHlsUriToken
. For example, if local port 8099 is used, the playback URL ishttp://172.16.0.1:8099?CipherText=CiphertextBlob value&MtsHlsUriToken=MtsHlsUriToken value
.
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 { // The AccessKey pair of an Alibaba Cloud account has permissions on all API operations. We recommend that you use a RAM user to call API operations or perform routine O&M. // We recommend that you do not include your AccessKey pair (AccessKey ID and AccessKey secret) in your project code. Otherwise, the AccessKey pair may be leaked and the security of all resources within your account may be compromised. // In this example, ApsaraVideo VOD reads the AccessKey pair from the environment variables to implement identity verification for API access. Before you run the sample code, configure the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET. 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 { // Specify the region where ApsaraVideo VOD is activated. 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(); } } }
Check whether HLS encryption and transcoding are successful
You can log on to the ApsaraVideo VOD console to view the video URL. Use the following methods to check whether HLS encryption is successful.
After a video is encrypted and transcoded, if only one URL in the M3U8 format is generated for the video, video transcoding failed:
After a video is encrypted and transcoded, if a M3U8 URL is generated for the video and a file in the original format such as MP4 exists, you can determine whether HLS encryption is successful by checking whether the Alibaba Cloud Proprietary Cryptography flag is displayed in the Format column. In most cases, HLS encryption is successful if "Alibaba Cloud Proprietary Cryptography" is displayed in the Format column.
If the preceding methods fail, copy the URL of the M3U8 file that contains the encryption flag and run the
curl -v "URL of the M3U8 file"
command to check whether the obtained content includes the key informationURI="<The decryption URL that you pass when you start HLS encryption, which is the value of the DecryptKeyUri parameter in EncryptConfig>"
. If the key information exists, HLS encryption is successful.
Playback procedure
Obtain the playback URL and credential of a video.
Call the GetPlayInfo and GetVideoPlayAuth operations to obtain the playback URL and credential of the video.
Specify the authentication information.
After the player obtains the URI of the M3U8 file, the player parses and accesses the URI in the EXT-X-KEY tag of the M3U8 file. This way, the player obtains the URI of the decryption operation that contains the ciphertext key. This URI is the value of the
DecryptKeyUri
parameter in EncryptConfig that you specify when you start HLS encryption.If you want only authorized users to access the video, the player must provide the authentication information that you acknowledged when the player obtained the decryption key. The authentication information can be specified by using the MtsHlsUriToken parameter.
Example:
The video playback URL is
https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8
. The request must contain theMtsHlsUriToken
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 request URL for decryption is
https://demo.aliyundoc.com?Ciphertext=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****&MtsHlsUriToken=<The issued token>
.
Play the video.
After the player obtains the decryption URI, the player automatically sends a request to obtain the decryption key. After the player obtains the decryption key, the player decrypts the encrypted TS file for playback.