HLS 暗号化には、Key Management Service (KMS) とトークンサービスが必要です。このトピックでは、HLS 暗号化に関連する概念、事前準備、および統合プロセスについて説明します。
HLS の暗号化と復号のフロー
Key Management Service (KMS) を使用すると料金が発生します。詳細については、「KMS 1.0 の課金」をご参照ください。
トークンサービスと復号サービスは、お客様自身で構築する必要があります。
関連概念
Key Management Service
Key Management Service (KMS) は、データキーの生成、暗号化、復号を処理するセキュリティ管理サービスです。
Resource Access Management
Resource Access Management (RAM) は、ユーザー ID 管理とリソースのアクセス制御を提供する Alibaba Cloud サービスです。
データキー
データキー (DK) は、平文キーとも呼ばれ、データの暗号化に使用されます。
エンベロープデータキー
エンベロープデータキー (EDK) は、暗号文キーとも呼ばれ、エンベロープ暗号化を使用して保護されたデータキーです。
事前準備
ApsaraVideo VOD を有効化し、ApsaraVideo VOD コンソールにログインして、対応するリージョンのバケットを有効にします。詳細については、「クイックスタート」をご参照ください。
ApsaraVideo VOD コンソールで、高速化ドメイン名を設定します。 ドメイン名の 動画関連 セクションで、HLS 標準暗号化パラメータのパススルー を有効にします。 この機能を有効にすると、MtsHlsUriToken パラメーターを書き換えることができます。 詳細については、「高速化ドメイン名の追加」および「HLS 暗号化のパラメーターパススルー」をご参照ください。
RAM コンソールにログインして、ご自身の AccessKey ID と AccessKey Secret を取得して保存します。
KMS を有効化し、サービスキーを取得します。
説明サービスキーは、KMS のカスタマーマスターキー (CMK) です。標準暗号化のキーを生成するには、このサービスキーを使用する必要があります。
サービスキーは、動画が保存されているオリジンバケットと同じリージョンにある必要があります。たとえば、動画が中国 (上海) に保存されている場合は、サービスキーを中国 (上海) で作成します。
ApsaraVideo VOD コンソールにログインします。設定の管理 > ApsaraVideo Media Processing の設定 > 標準暗号化 の順に選択します。HLS 標準暗号化ページで、サービスキーを作成します。
作成が成功したら、GenerateDataKey 操作を呼び出します。リクエストパラメーターの
KeyIdをエイリアス alias/acs/vod に設定します。返されたKeyIdパラメーターを後続のトランスコーディングで使用します。
サーバーサイド SDK を設定します。詳細については、「サーバーサイド SDK のインストール」をご参照ください。
統合プロセス
暗号化テンプレートと非トランスコーディングテンプレートの追加
HLS 標準暗号化とトランスコーディングには、暗号化テンプレートとトランスコードなしテンプレートの 2 つのトランスコーディングテンプレートが必要です。
非トランスコーディングテンプレートは、対応するリージョンでバケットを有効にすると自動的に生成されます。
説明デフォルトでは、ApsaraVideo VOD はアップロードされた動画を自動的にトランスコードします。ただし、自動トランスコーディングは HLS 標準暗号化をサポートしていません。自動トランスコーディングを防ぐには、まずトランスコードなしテンプレート (自動トランスコーディングをトリガーしない) を使用して動画をアップロードします。次に、SubmitTranscodeJobs 操作を呼び出して、標準暗号化トランスコーディングを開始します。
暗号化テンプレートを追加し、その ID を次のように保存します。
ApsaraVideo VOD コンソールにログインします。
設定の管理 エリアで、ApsaraVideo Media Processing の設定 > トランスコードテンプレートグループ > トランスコードテンプレートグループの追加 を選択します。
トランスコードテンプレートグループの追加 ページで、テンプレートグループ名 を入力します。
通常のトランスコードテンプレート セクションで、テンプレートの追加 をクリックしてトランスコーディングテンプレートを作成します。
基本パラメーター セクションで、カプセル化形式 を [hls] に設定します。
「ビデオパラメーター」、「オーディオパラメーター」、および「条件付きトランスコードパラメーター」の各セクションで、必要に応じてパラメーターを設定します。各パラメーターと構成の制限の詳細については、「音声と動画のトランスコード」をご参照ください。
[高度なパラメーター]エリアで、ビデオの暗号化を有効にして、Alibaba Cloud Encryptionオプションをチェックします(このオプションはデフォルトでチェックされています。チェックを解除すると、動画暗号化が無効になります。)。
説明SubmitTranscodeJobs 操作を呼び出す際、このテンプレート ID を `TemplateGroupId` パラメーターで渡します。ApsaraVideo VOD は、テンプレート設定と提供されたキー情報に基づいて標準暗号化トランスコーディングを実行します。
[保存]をクリックします。自動的に[トランスコードテンプレートグループ]ページにリダイレクトされます。暗号化テンプレートIDを取得して保存します。
RAM 権限付与
RAM を使用して、ApsaraVideo VOD に KMS リソースへのアクセス権限を付与します。RAM 権限付与ページに移動し、[権限付与ポリシーの確認] をクリックします。

Alibaba Cloud KMS をカプセル化するキー管理サービスの構築
GenerateDataKey 操作を呼び出して、AES_128 キーを生成します。`KeyId` (サービスキー) と `KeySpec` (固定値: AES_128) のみを渡してください。他のパラメーターを渡すと、暗号化が失敗する可能性があります。
呼び出しが成功したら、返された
CiphertextBlobパラメーターの値 (暗号文キー) を保存します。説明キーの使用には料金が発生します。詳細については、「API 呼び出し料金」をご参照ください。
MtsHlsUriToken を生成するトークン発行サービスの構築
以下の Java サンプルコードは、手動で変更が必要な部分を示しています。
ENCRYPT_KEY:お客様が定義する 16 文字の暗号化文字列。
INIT_VECTOR: 特殊文字を含まない、16 文字のカスタム文字列。
playToken.generateToken(""):お客様が定義する 16 文字の文字列。
このコードによって生成されたトークンが `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 { // AES を使用してトークンを生成しない場合、以下のパラメーターは不要です。 // 暗号化文字列。お客様自身で定義してください。 private static String ENCRYPT_KEY = ""; // 16 文字のカスタム文字列。特殊文字は含められません。 private static String INIT_VECTOR = ""; public static void main(String[] args) throws Exception { PlayToken playToken = new PlayToken(); playToken.generateToken(""); } /** * 渡されたパラメーターに基づいてトークンを生成します。 * 注意: * 1. パラメーターには、ユーザー ID、再生クライアントの種類、その他の情報を含めることができます。 * 2. トークンは、トークン API が呼び出されたときに生成されます。 * @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), "_"); // トークンを 30 秒後に失効するように設定します。有効期限は調整可能です。 long expire = System.currentTimeMillis() + 30000L; // カスタム文字列。最終的な base 文字列の長さは 16 文字である必要があります。この例では、タイムスタンプが 13 文字、アンダースコア (_) が 1 文字を占めるため、さらに 2 文字を渡す必要があります。必要に応じて文字列全体を変更することもできますが、最終的な base 文字列が 16 文字であることを確認してください。 base += "_" + expire; // トークンを生成します。 String token = encrypt(base, ENCRYPT_KEY); System.out.println(token); // 復号時に有効性 (有効期限や使用回数など) を検証するためにトークンを保存します。 saveToken(token); return token; } /** * トークンの有効性を検証します。 * 注意: * 1. 復号 API が再生キーを返す前に、トークンの正当性と有効性を検証する必要があります。 * 2. トークンの有効期限と有効な使用回数も検証することを強く推奨します。 * @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); // まず、トークンの有効期限を検証します。 Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1)); if (System.currentTimeMillis() > expireTime) { return false; } // データベースからトークン情報を取得して、その有効性を判断します。これはお客様自身で実装してください。 Token dbToken = getToken(token); // トークンが使用済みかどうかを確認します。 if (dbToken == null || dbToken.useCount > 0) { return false; } // 検証のためにビジネス属性情報を取得します。 String businessInfo = base.substring(0, base.lastIndexOf("_")); String[] items = businessInfo.split("_"); // ビジネス情報の正当性を検証します。これはお客様自身で実装してください。 return validateInfo(items); } /** * トークンをデータベースに保存します。 * これは お客様自身で実装してください。 * * @param token */ public void saveToken(String token) { //TODO トークンを保存します。 } /** * トークンをクエリします。 * これは お客様自身で実装してください。 * * @param token */ public Token getToken(String token) { //TODO データベースからトークン情報を取得して、その有効性と正当性を検証します。 return null; } /** * ビジネス情報の有効性を検証します。これはお客様自身で実装してください。 * * @param infos * @return */ public boolean validateInfo(String... infos) { //TODO UID が有効かどうかなど、情報の有効性を検証します。 return true; } /** * AES 暗号化を使用してトークンを生成します。 * * @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); } /** * 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); } /** * トークン情報。より多くの情報を提供できます。これは一例です。 */ class Token { // トークンが使用できる回数。分散環境では、同期の問題に注意してください。 int useCount; // トークンの内容。 String token; }}復号サービスの構築
重要動画再生の前に復号サービスを開始してください。そうしないと、動画を復号できません。
EDK (暗号文キー) を復号するには、Decrypt 操作を呼び出します。復号 API へのリクエストを認証するには、トークン生成サービスを提供します。復号サービスは、生成されたトークンを解析して検証する必要があります。
復号 API は、GenerateDataKey 操作によって最初に生成された base64 デコードされた平文 (PlainText) を返します。
以下の Java サンプルコードは、手動で変更が必要な部分を示しています。
region: リージョン ID を入力します。中国 (上海) の場合は、
cn-shanghaiと入力します。AccessKey: ご利用のアカウントの AccessKey ID と AccessKey Secret を入力します。
httpserver:サービスのポート番号を選択します。
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 { // KMS リージョンは動画のリージョンと一致する必要があります。 String region = ""; // KMS にアクセスするための権限が付与された AccessKey 情報。 // Alibaba Cloud アカウントの AccessKey は、すべての API に対する完全な権限を持っています。API アクセスや日常の O&M には RAM ユーザーを使用することを推奨します。 // プロジェクトコードに AccessKey ID と AccessKey Secret をハードコーディングしないことを強く推奨します。ハードコーディングすると、AccessKey ペアが漏洩し、すべてのリソースのセキュリティが損なわれる可能性があります。 // この例では、環境変数から AccessKey を読み取って API アクセスを認証します。サンプルコードを実行する前に、ALIBABA_CLOUD_ACCESS_KEY_ID と 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)); } /** * 注: * 1. 復号リクエストを受信し、暗号文キーとトークンを取得します。 * 2. KMS 復号 API を呼び出して、平文キーを取得します。 * 3. base64 デコードされた平文キーを返します。 */ public class HlsDecryptHandler implements HttpHandler { /** * 復号リクエストを処理します。 * @param httpExchange * @throws IOException */ public void handle(HttpExchange httpExchange) throws IOException { String requestMethod = httpExchange.getRequestMethod(); if ("GET".equalsIgnoreCase(requestMethod)) { // トークンの有効性を検証します。 String token = getMtsHlsUriToken(httpExchange); boolean validRe = validateToken(token); if (!validRe) { return; } // URL から暗号文キーを取得します。 String ciphertext = getCiphertext(httpExchange); if (null == ciphertext) return; // KMS から復号し、Base64 デコードします。 byte[] key = decrypt(ciphertext); // ヘッダーを設定します。 setHeader(httpExchange, key); // base64 デコードされたキーを返します。 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); } /** * KMS 復号 API を呼び出して復号し、平文を base64 デコードします。 * @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(); // 注:Base64 デコードが必要です。 return Base64.decodeBase64(plaintext); } catch (ClientException e) { e.printStackTrace(); return null; } } /** * トークンの有効性を検証します。 * @param token * @return */ private boolean validateToken(String token) { if (null == token || "".equals(token)) { return false; } //TODO トークンの有効性検証を実装します。 return true; } /** * 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; } } /** * トークンパラメーターを取得します。 * * @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; } } } /** * サービスを開始します。 * * @throws IOException */ private void serviceBootStrap() throws IOException { HttpServerProvider provider = HttpServerProvider.provider(); // リスニングポートはカスタマイズ可能です。同時に最大 30 のリクエストを受け入れることができます。 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(); }}動画のアップロード
トランスコードなし テンプレートを使用して、ビデオアップロード URL と認証情報を作成します。 コンソールの手順については、「ApsaraVideo VOD コンソールでファイルをアップロードする」をご参照ください。 サーバーサイド API の手順については、「オーディオまたはビデオのアップロード URL と認証情報を取得する」をご参照ください。
アップロード完了コールバックメッセージの受信
SetMessageCallback 操作を呼び出してコールバックを設定します。GetMessageCallback 操作を呼び出してコールバックメッセージをクエリします。動画アップロード完了イベントのコールバックメッセージを受信すると、ファイルは ApsaraVideo VOD にアップロードされています。
標準暗号化トランスコーディングの開始
SubmitTranscodeJobs 操作を呼び出して、標準暗号化トランスコーディングを開始します。
以下の Java サンプルコードは、手動で変更が必要な部分を示しています。
request.setTemplateGroupId(""): 暗号化テンプレート ID。
request.setVideoId(""): 動画 ID。
encryptConfig.put("CipherText",""): ステップ 3 の CiphertextBlob の値。
encryptConfig.put("DecryptKeyUri",""):
CiphertextBlobの値とMtsHlsUriTokenを含む再生 URL。たとえば、ローカルポートが 8099 の場合、再生 URL は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 { // Alibaba Cloud アカウントの AccessKey は、すべての API に対する完全な権限を持っています。API アクセスや日常の O&M には RAM ユーザーを使用することを推奨します。 // プロジェクトコードに AccessKey ID と AccessKey Secret をハードコーディングしないことを強く推奨します。そうしないと、AccessKey ペアが漏洩し、すべてのリソースのセキュリティが損なわれる可能性があります。 // この例では、環境変数から AccessKey を読み取って API アクセスを認証します。サンプルコードを実行する前に、ALIBABA_CLOUD_ACCESS_KEY_ID および 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 { // ApsaraVideo VOD サービスにアクセスするリージョン。 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(); } } }暗号化トランスコーディングの成功検証
ApsaraVideo VOD コンソールにログインして動画の URL を表示します。以下のいずれかの方法で、標準暗号化が成功したかどうかを判断します。
暗号化トランスコーディング後、動画に M3U8 形式の URL が 1 つしかない場合、動画のステータスは [トランスコーディング失敗] です。
ビデオ出力に M3U8 以外のフォーマット (たとえば、元の MP4 ファイル) が含まれている場合は、M3U8 フォーマットの後に標準暗号化が表示されているか確認してください。 表示されている場合、標準暗号化は成功です。
上記のいずれの方法でも確認できない場合は、暗号化フラグ付きの M3U8 ファイルの URL をコピーし、
curl -v "M3U8 ファイルの URL"を実行します。M3U8 の内容にキー情報URI="<標準暗号化を開始したときに渡した復号 URL、つまり EncryptConfig の DecryptKeyUri パラメーターの値>"が含まれているかどうかを確認します。含まれていれば、標準暗号化は成功です。
再生プロセス
動画の再生 URL と資格情報の取得
GetPlayInfo および GetVideoPlayAuth 操作を呼び出して、動画の再生 URL と資格情報を取得します。
認証情報の受け渡し
プレーヤーは m3u8 ファイルのアドレスを取得すると、そのファイルを解析し、EXT-X-KEY タグから URI にアクセスして暗号文キーの復号インターフェイス URI を取得します。この URI は、標準暗号化を開始するときに渡す EncryptConfig 構成内の
DecryptKeyUriパラメーターの値です。アクセスを正当なユーザーのみに制限するために、プレーヤーは復号キーをリクエストする際に、お客様が認識する認証情報を含める必要があります。この認証情報は `MtsHlsUriToken` パラメーターで渡します。
例:
動画の再生 URL は
https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8です。リクエストにはMtsHlsUriTokenパラメーターを含める必要があります。最終的なリクエスト URL は
https://demo.aliyundoc.com/encrypt-stream****-hd.m3u8?MtsHlsUriToken=<token>です。復号 URL は
https://demo.aliyundoc.com?Ciphertext=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****です。最終的な復号リクエスト URL は
https://demo.aliyundoc.com?Ciphertext=ZjJmZGViNzUtZWY1Mi00Y2RlLTk3MTMtOT****&MtsHlsUriToken=<issued_token>です。
動画の再生
プレーヤーが復号 URI を解析すると、自動的に復号 API を呼び出して復号キーを取得します。その後、プレーヤーはこのキーを使用して暗号化された TS ファイルを復号し、再生します。