トークンを生成する

このガイドでは、トークンを生成する方法と、トークンの必須フィールドとオプション フィールドについて説明します。

トークンを作成するには、署名する文字列を作成します。このガイドでは、これを署名付き値と呼びます。署名付き値には、保護するコンテンツ、署名付き値の有効期限などを記述するパラメータが含まれます。

署名付き値は、トークン文字列の作成時に使用します。トークン文字列を作成するには、署名付き値の対称鍵ハッシュベースのメッセージ認証コード(HMAC)など、トークンのパラメータを構成します。

Media CDN は、最終的な作成されたトークンを使用してコンテンツを保護します。

トークンの作成

  1. 必須のトークン フィールドと必要なオプションのトークン フィールドを含む文字列を連結して、署名付き値を作成します。各フィールドとパラメータはチルダ ~ 文字で区切ります。

  2. Ed25519 署名または対称鍵 HMAC のいずれかを使用して署名付き値に署名します。

  3. 必須のトークン フィールドとオプションのトークン フィールドを含む文字列を連結してトークンを作成します。各フィールドとパラメータはチルダ ~ 文字で区切ります。

    トークンを作成するとき、各パラメータの値は署名付き値とトークン文字列の間で同じですが、次の点が異なります。

    • FullPath
    • Headers

次のコードサンプルでは、プログラムでトークンを作成する方法を示します。

Python

Media CDN に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。 詳細については、ローカル開発環境の認証を設定するをご覧ください。

import base64
import datetime
import hashlib
import hmac

import cryptography.hazmat.primitives.asymmetric.ed25519 as ed25519


def base64_encoder(value: bytes) -> str:
    """
    Returns a base64-encoded string compatible with Media CDN.

    Media CDN uses URL-safe base64 encoding and strips off the padding at the
    end.
    """
    encoded_bytes = base64.urlsafe_b64encode(value)
    encoded_str = encoded_bytes.decode("utf-8")
    return encoded_str.rstrip("=")


def sign_token(
    base64_key: bytes,
    signature_algorithm: str,
    start_time: datetime.datetime = None,
    expiration_time: datetime.datetime = None,
    url_prefix: str = None,
    full_path: str = None,
    path_globs: str = None,
    session_id: str = None,
    data: str = None,
    headers: str = None,
    ip_ranges: str = None,
) -> str:
    """Gets the Signed URL Suffix string for the Media CDN' Short token URL requests.
    One of (`url_prefix`, `full_path`, `path_globs`) must be included in each input.
    Args:
        base64_key: Secret key as a base64 encoded string.
        signature_algorithm: Algorithm can be either `SHA1` or `SHA256` or `Ed25519`.
        start_time: Start time as a UTC datetime object.
        expiration_time: Expiration time as a UTC datetime object. If None, an expiration time 1 hour from now will be used.
        url_prefix: the URL prefix to sign, including protocol.
                    For example: http://example.com/path/ for URLs under /path or http://example.com/path?param=1
        full_path:  A full path to sign, starting with the first '/'.
                    For example: /path/to/content.mp4
        path_globs: a set of ','- or '!'-delimited path glob strings.
                    For example: /tv/*!/film/* to sign paths starting with /tv/ or /film/ in any URL.
        session_id: a unique identifier for the session
        data: data payload to include in the token
        headers: header name and value to include in the signed token in name=value format.  May be specified more than once.
                    For example: [{'name': 'foo', 'value': 'bar'}, {'name': 'baz', 'value': 'qux'}]
        ip_ranges: A list of comma separated ip ranges. Both IPv4 and IPv6 ranges are acceptable.
                    For example: "203.0.113.0/24,2001:db8:4a7f:a732/64"

    Returns:
        The Signed URL appended with the query parameters based on the
        specified URL prefix and configuration.
    """

    decoded_key = base64.urlsafe_b64decode(base64_key)
    algo = signature_algorithm.lower()

    # For most fields, the value we put in the token and the value we must sign
    # are the same.  The FullPath and Headers use a different string for the
    # value to be signed compared to the token.  To illustrate this difference,
    # we'll keep the token and the value to be signed separate.
    tokens = []
    to_sign = []

    # check for `full_path` or `path_globs` or `url_prefix`
    if full_path:
        tokens.append("FullPath")
        to_sign.append(f"FullPath={full_path}")
    elif path_globs:
        path_globs = path_globs.strip()
        field = f"PathGlobs={path_globs}"
        tokens.append(field)
        to_sign.append(field)
    elif url_prefix:
        field = "URLPrefix=" + base64_encoder(url_prefix.encode("utf-8"))
        tokens.append(field)
        to_sign.append(field)
    else:
        raise ValueError(
            "User Input Missing: One of `url_prefix`, `full_path` or `path_globs` must be specified"
        )

    # check & parse optional params
    if start_time:
        epoch_duration = start_time.astimezone(
            tz=datetime.timezone.utc
        ) - datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)
        field = f"Starts={int(epoch_duration.total_seconds())}"
        tokens.append(field)
        to_sign.append(field)

    if not expiration_time:
        expiration_time = datetime.datetime.now() + datetime.timedelta(hours=1)
        epoch_duration = expiration_time.astimezone(
            tz=datetime.timezone.utc
        ) - datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)
    else:
        epoch_duration = expiration_time.astimezone(
            tz=datetime.timezone.utc
        ) - datetime.datetime.fromtimestamp(0, tz=datetime.timezone.utc)
    field = f"Expires={int(epoch_duration.total_seconds())}"
    tokens.append(field)
    to_sign.append(field)

    if session_id:
        field = f"SessionID={session_id}"
        tokens.append(field)
        to_sign.append(field)

    if data:
        field = f"Data={data}"
        tokens.append(field)
        to_sign.append(field)

    if headers:
        header_names = []
        header_pairs = []
        for each in headers:
            header_names.append(each["name"])
            header_pairs.append("%s=%s" % (each["name"], each["value"]))
        tokens.append(f"Headers={','.join(header_names)}")
        to_sign.append(f"Headers={','.join(header_pairs)}")

    if ip_ranges:
        field = f"IPRanges={base64_encoder(ip_ranges.encode('ascii'))}"
        tokens.append(field)
        to_sign.append(field)

    # generating token
    to_sign = "~".join(to_sign)
    to_sign_bytes = to_sign.encode("utf-8")
    if algo == "ed25519":
        digest = ed25519.Ed25519PrivateKey.from_private_bytes(decoded_key).sign(
            to_sign_bytes
        )
        tokens.append("Signature=" + base64_encoder(digest))
    elif algo == "sha256":
        signature = hmac.new(
            decoded_key, to_sign_bytes, digestmod=hashlib.sha256
        ).hexdigest()
        tokens.append("hmac=" + signature)
    elif algo == "sha1":
        signature = hmac.new(
            decoded_key, to_sign_bytes, digestmod=hashlib.sha1
        ).hexdigest()
        tokens.append("hmac=" + signature)
    else:
        raise ValueError(
            "Input Missing Error: `signature_algorithm` can only be one of `sha1`, `sha256` or `ed25519`"
        )
    return "~".join(tokens)

Java

Media CDN に対する認証を行うには、アプリケーションのデフォルト認証情報を設定します。 詳細については、ローカル開発環境の認証を設定するをご覧ください。


import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Optional;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
import org.bouncycastle.crypto.signers.Ed25519Signer;
import org.bouncycastle.util.encoders.Hex;

public class DualToken {

  public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
    // TODO(developer): Replace these variables before running the sample.
    // Secret key as a base64 encoded string.
    byte[] base64Key = new byte[]{};
    // Algorithm can be one of these: SHA1, SHA256, or Ed25519.
    String signatureAlgorithm = "ed25519";
    // (Optional) Start time as a UTC datetime object.
    DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT;
    Optional<Instant> startTime = Optional.empty();
    // Expiration time as a UTC datetime object.
    // If None, an expiration time that's an hour after the current time is used.
    Instant expiresTime = Instant.from(formatter.parse("2022-09-13T12:00:00Z"));

    // ONE OF (`urlPrefix`, `fullPath`, `pathGlobs`) must be included in each input.
    // The URL prefix and protocol to sign.
    // For example: http://example.com/path/ for URLs under /path or http://example.com/path?param=1
    Optional<String> urlPrefix = Optional.empty();
    // A full path to sign, starting with the first '/'.
    // For example: /path/to/content.mp4
    Optional<String> fullPath = Optional.of("http://10.20.30.40/");
    // A set of path glob strings delimited by ',' or '!'.
    // For example: /tv/*!/film/* to sign paths starting with /tv/ or /film/ in any URL.
    Optional<String> pathGlobs = Optional.empty();

    // (Optional) A unique identifier for the session.
    Optional<String> sessionId = Optional.empty();
    // (Optional) Data payload to include in the token.
    Optional<String> data = Optional.empty();
    // (Optional) Header name and value to include in the signed token in name=value format.
    // May be specified more than once.
    // For example: [{'name': 'foo', 'value': 'bar'}, {'name': 'baz', 'value': 'qux'}]
    Optional<List<Header>> headers = Optional.empty();
    // (Optional) A list of comma-separated IP ranges. Both IPv4 and IPv6 ranges are acceptable.
    // For example: "203.0.113.0/24,2001:db8:4a7f:a732/64"
    Optional<String> ipRanges = Optional.empty();

    DualToken.signToken(
        base64Key,
        signatureAlgorithm,
        startTime,
        expiresTime,
        urlPrefix,
        fullPath,
        pathGlobs,
        sessionId,
        data,
        headers,
        ipRanges);
  }

  // Gets the signed URL suffix string for the Media CDN short token URL requests.
  // Result:
  //     The signed URL appended with the query parameters based on the
  // specified URL prefix and configuration.
  public static void signToken(
      byte[] base64Key, String signatureAlgorithm, Optional<Instant> startTime,
      Instant expirationTime, Optional<String> urlPrefix, Optional<String> fullPath,
      Optional<String> pathGlobs, Optional<String> sessionId, Optional<String> data,
      Optional<List<Header>> headers, Optional<String> ipRanges)
      throws NoSuchAlgorithmException, InvalidKeyException {

    String field = "";
    byte[] decodedKey = Base64.getUrlDecoder().decode(base64Key);

    // For most fields, the value in the token and the value to sign
    // are the same. Compared to the token, the FullPath and Headers
    // use a different string for the value to sign. To illustrate this difference,
    // we'll keep the token and the value to be signed separate.
    List<String> tokens = new ArrayList<>();
    List<String> toSign = new ArrayList<>();

    // Check for `fullPath` or `pathGlobs` or `urlPrefix`.
    if (fullPath.isPresent()) {
      tokens.add("FullPath");
      toSign.add(String.format("FullPath=%s", fullPath.get()));
    } else if (pathGlobs.isPresent()) {
      field = String.format("PathGlobs=%s", pathGlobs.get().trim());
      tokens.add(field);
      toSign.add(field);
    } else if (urlPrefix.isPresent()) {
      field = String.format("URLPrefix=%s",
          base64Encoder(urlPrefix.get().getBytes(StandardCharsets.UTF_8)));
      tokens.add(field);
      toSign.add(field);
    } else {
      throw new IllegalArgumentException(
          "User Input Missing: One of `urlPrefix`, `fullPath` or `pathGlobs` must be specified");
    }

    // Check & parse optional params.
    long epochDuration;
    if (startTime.isPresent()) {
      epochDuration = ChronoUnit.SECONDS.between(Instant.EPOCH, startTime.get());
      field = String.format("Starts=%s", epochDuration);
      tokens.add(field);
      toSign.add(field);
    }

    if (expirationTime == null) {
      expirationTime = Instant.now().plus(1, ChronoUnit.HOURS);
    }
    epochDuration = ChronoUnit.SECONDS.between(Instant.EPOCH, expirationTime);
    field = String.format("Expires=%s", epochDuration);
    tokens.add(field);
    toSign.add(field);

    if (sessionId.isPresent()) {
      field = String.format("SessionID=%s", sessionId.get());
      tokens.add(field);
      toSign.add(field);
    }

    if (data.isPresent()) {
      field = String.format("Data=%s", data.get());
      tokens.add(field);
      toSign.add(field);
    }

    if (headers.isPresent()) {
      List<String> headerNames = new ArrayList<>();
      List<String> headerPairs = new ArrayList<>();

      for (Header entry : headers.get()) {
        headerNames.add(entry.getName());
        headerPairs.add(String.format("%s=%s", entry.getName(), entry.getValue()));
      }
      tokens.add(String.format("Headers=%s", String.join(",", headerNames)));
      toSign.add(String.format("Headers=%s", String.join(",", headerPairs)));
    }

    if (ipRanges.isPresent()) {
      field = String.format("IPRanges=%s",
          base64Encoder(ipRanges.get().getBytes(StandardCharsets.US_ASCII)));
      tokens.add(field);
      toSign.add(field);
    }

    // Generate token.
    String toSignJoined = String.join("~", toSign);
    byte[] toSignBytes = toSignJoined.getBytes(StandardCharsets.UTF_8);
    String algorithm = signatureAlgorithm.toLowerCase();

    if (algorithm.equalsIgnoreCase("ed25519")) {
      Ed25519PrivateKeyParameters privateKey = new Ed25519PrivateKeyParameters(decodedKey, 0);
      Ed25519Signer signer = new Ed25519Signer();
      signer.init(true, privateKey);
      signer.update(toSignBytes, 0, toSignBytes.length);
      byte[] signature = signer.generateSignature();
      tokens.add(String.format("Signature=%s", base64Encoder(signature)));
    } else if (algorithm.equalsIgnoreCase("sha256")) {
      String sha256 = "HmacSHA256";
      Mac mac = Mac.getInstance(sha256);
      SecretKeySpec secretKeySpec = new SecretKeySpec(decodedKey, sha256);
      mac.init(secretKeySpec);
      byte[] signature = mac.doFinal(toSignBytes);
      tokens.add(String.format("hmac=%s", Hex.toHexString(signature)));
    } else if (algorithm.equalsIgnoreCase("sha1")) {
      String sha1 = "HmacSHA1";
      Mac mac = Mac.getInstance(sha1);
      SecretKeySpec secretKeySpec = new SecretKeySpec(decodedKey, sha1);
      mac.init(secretKeySpec);
      byte[] signature = mac.doFinal(toSignBytes);
      tokens.add(String.format("hmac=%s", Hex.toHexString(signature)));
    } else {
      throw new Error(
          "Input Missing Error: `signatureAlgorithm` can only be one of `sha1`, `sha256` or "
              + "`ed25519`");
    }
    // The signed URL appended with the query parameters based on the
    // specified URL prefix and configuration.
    System.out.println(String.join("~", tokens));
  }

  // Returns a base64-encoded string compatible with Media CDN.
  // Media CDN uses URL-safe base64 encoding and strips off the padding at the
  // end.
  public static String base64Encoder(byte[] value) {
    byte[] encodedBytes = Base64.getUrlEncoder().withoutPadding().encode(value);
    return new String(encodedBytes, StandardCharsets.UTF_8);
  }

  public static class Header {

    private String name;
    private String value;

    public Header(String name, String value) {
      this.name = name;
      this.value = value;
    }

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getValue() {
      return value;
    }

    public void setValue(String value) {
      this.value = value;
    }

    @Override
    public String toString() {
      return "Header{"
          + "name='" + name + '\''
          + ", value='" + value + '\''
          + '}';
    }
  }

}

以降のセクションでは、トークンで使用されるフィールドについて説明します。

必須のトークン フィールド

すべてのトークンに次のフィールドが必要です。

  • Expires
  • 次のいずれか。
    • PathGlobs
    • URLPrefix
    • FullPath
  • 次のいずれか 1 つ。
    • Signature
    • hmac

特に指定のない限り、パラメータ名とその値では大文字と小文字が区別されます。

次の表に、各パラメータの説明を示します。

フィールド名 / エイリアス トークンのパラメータ 署名付きの値

Expires

exp

Unix エポックからの整数の経過秒数(1970-01-01T00:00:00Z) Expires=EXPIRATION_TIME。この期間が経過すると、トークンは無効になります。

PathGlobs

pathsacl

アクセス権を付与するパスセグメント(最大 5 つ)のリスト。セグメントは、カンマ(,)または感嘆符(!)のいずれかで区切ることができます。両方で区切ることはできません。

PathGlobs では、パスにアスタリスク(*)と疑問符(?)を使用したワイルドカードを使用できます。pathMatchTemplate のパターン マッチング構文とは異なり、1 つのアスタリスク(*)文字は任意の数のパスセグメントにまたがります。

セミコロン(;)を使用して示されたパスパラメータは、一致の際に曖昧さが生じるため、使用できません。

このため、URL には特殊文字 ,!*?; を含めないでください。

PathGlobs=PATHS
URLPrefix

http:// または https:// プロトコルを含む、選択した時点までのウェブセーフな Base64 エンコード URL。

たとえば、「https://example.com/foo/bar.ts」の有効な URLPrefix 値は「https://example.com」、「https://example.com/foo」、`https://example.com/foo/bar」などです。

URLPrefix=BASE_64_URL_PREFIX
FullPath なし。トークンで FullPath を指定するとき、署名付き値に指定したパスを複製しないでください。トークンには、= なしでフィールド名を含めます。 FullPath=FULL_PATH_TO_OBJECT
Signature ウェブセーフな Base64 エンコードの署名。 該当なし
hmac ウェブセーフな Base64 エンコードの HMAC 値のバージョン。 該当なし

PathGlobs ワイルドカード構文

PathGlobs ワイルドカード構文の説明を、次の表に示します。

演算子 一致
*(アスタリスク) URL のパスに含まれる 0 個以上の文字(スラッシュ(/)文字を含む)と一致します。
  • /videos/*/videos/ で始まるすべてのパス一致します。
  • /videos/s*/4k/*/videos/s/4k//videos/s01/4k/main.m3u8 と一致します。
  • /manifests/*/4k/*/manifests/s01/4k/main.m3u8/manifests/s01/e01/4k/main.m3u8 と一致します。/manifests/4k/main.m3u8 と一致しません。
?(疑問符) URL のパスに含まれる 1 文字と一致します。スラッシュ(/)文字は含まれません。 /videos/s?main.m3u8/videos/s1main.m3u8 と一致します。/videos/s01main.m3u8 または /videos/s/main.m3u8 のいずれとも一致しません。

glob は、URL パスにアスタリスク(*)またはスラッシュ(/)で始まる必要があります。

*/* はすべての URL パスと一致するため、署名付きトークンでこれを使用することはおすすめしません。最大限の保護のために、glob が、アクセスを許可するコンテンツと一致していることを確認してください。

オプションのトークン フィールド

特に指定のない限り、パラメータ名とその値では大文字と小文字が区別されます。

次の表は、パラメータ名、エイリアス、オプション パラメータの詳細を示しています。

フィールド名 / エイリアス パラメータ 署名付きの値

Starts

st

Unix エポックからの整数の秒数(1970-01-01T00:00:00Z) Starts=START_TIME
IPRanges

この URL がウェブセーフな Base64 形式で有効になる、最大 5 つの IPv4 アドレスと IPv6 アドレスの CIDR 形式のリスト。たとえば、IP 範囲「192.6.13.13/32,193.5.64.135/32」を指定するには、IPRanges=MTkyLjYuMTMuMTMvMzIsMTkzLjUuNjQuMTM1LzMy を指定します。

クライアントが WAN 移行の危険にさらされている場合や、アプリケーション フロントエンドへのネットワーク パスが配信パスと異なる場合、IPRanges はトークンに含めるのには役立ちません。 署名付きリクエストに含まれていない IP アドレスに接続すると、Media CDN は HTTP 403 コードを持つクライアントを拒否します。

以下のような場合、Media CDN は HTTP 403 コードを持つクライアントを拒否します。

  • デュアルスタック(IPv4、IPv6)環境
  • 接続の移行(Wi-Fi からモバイル、モバイルから Wi-Fi)
  • キャリア ゲートウェイ NAT(CGNAT または CGN)を使用するモバイル ネットワーク
  • マルチパス TCP(MPTCP)

これらすべての要因は、動画再生セッション中に特定のクライアントが非確定的な IP アドレスを持つ原因となる可能性があります。アクセスの発行後にクライアント IP アドレスが変更され、クライアントが動画セグメントを再生バッファにダウンロードしようとすると、Media CDN から HTTP 403 を受信します。

IPRanges=BASE_64_IP_RANGES

SessionID

id

任意の文字列。ログの分析や再生トレースに便利です。

無効なトークンが作成されないようにするには、% エンコードまたはウェブセーフの Base64 エンコード文字列を使用します。「~」、「&」、「 」(スペース)の文字は、トークンが無効になるため、SessionID には使用しないでください。

SessionID=SESSION_ID_VALUE

Data

datapayload

任意の文字列。ログ分析に便利です。

無効なトークンが作成されないようにするには、% エンコードまたはウェブセーフの Base64 エンコード文字列を使用します。「~」、「&」、「 」(スペース)の文字は、トークンが無効になるため、Data には使用しないでください。

data=DATA_VALUE
Headers ヘッダー フィールド名のカンマ区切りリスト。リクエストのルックアップでは、ヘッダー名では大文字と小文字が区別されません。署名付き値のヘッダー名では大文字と小文字が区別されます。ヘッダーがない場合、値は空の文字列になります。ヘッダーのコピーが複数ある場合は、カンマで連結されます。 Headers=HEADER_1_NAME=HEADER_1_EXPECTED_VALUE, HEADER_2_NAME=HEADER_2_EXPECTED_VALUE

次のセクションでは、トークンを生成する例を示します。

FullPath の使用例

FullPath フィールドを使用した次の例について考えてみます。

  • リクエストされたアイテム: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • 有効期間: 160000000

符号付き値は次のとおりです。

Expires=160000000~FullPath=/tv/my-show/s01/e01/playlist.m3u8

トークンを作成するには、Ed25519 署名または対称鍵 HMAC のいずれかを使用して署名付き値に署名します。

署名付き値から作成されたトークンの例を次に示します。

Ed25519 署名

Expires=160000000~FullPath~Signature=SIGNATURE_OF_SIGNED_VALUE

ここで、SIGNATURE_OF_SIGNED_VALUE は以前に作成された符号付き値の ED25519 署名です。

対称鍵 HMAC

Expires=160000000~FullPath~hmac=HMAC_OF_SIGNED_VALUE

ここで、HMAC_OF_SIGNED_VALUE は、以前に作成された署名付き値の対称鍵 HMAC です。

上記の例では、FullPath がトークンに指定されていますが、値は、署名付き値に指定されたパスから繰り返されません。これにより、トークンでリクエストを複製することなく、リクエストのフルパスに署名できます。

URLPrefix の使用例

URLPrefix フィールドを使用した次の例について考えてみます。

  • リクエストされたアイテム: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • 有効期間: 160000000

符号付き値は次のとおりです。

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4

前の例では、リクエストされたアイテムへのパス http://example.com/tv/my-show/s01/e01/playlist.m3u8 をウェブセーフな Base64 形式のアイテムへのパス aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4 に置き換えています。

トークンを作成するには、Ed25519 署名または対称鍵 HMAC のいずれかを使用して署名付き値に署名します。

署名付き値から作成されたトークンの例を次に示します。

Ed25519 署名

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~Signature=SIGNATURE_OF_SIGNED_VALUE

ここで、SIGNATURE_OF_SIGNED_VALUE は以前に作成された符号付き値の ED25519 署名です。

対称鍵 HMAC

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~hmac=HMAC_OF_SIGNED_VALUE

ここで、HMAC_OF_SIGNED_VALUE は、以前に作成された署名付き値の対称鍵 HMAC です。

Headers の使用例

Headers フィールドを使用した次の例について考えてみます。

  • リクエストされたアイテム: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • 有効期間: 160000000
  • PathGlobs 値: *
  • 想定されるリクエスト ヘッダー:
    • user-agent: browser
    • accept: text/html

符号付き値は次のとおりです。

Expires=160000000~PathGlobs=*~Headers=user-agent=browser,accept=text/html

トークンを作成するには、Ed25519 署名または対称鍵 HMAC のいずれかを使用して署名付き値に署名します。

署名付き値から作成されたトークンの例を次に示します。

Ed25519 署名

Expires=160000000~PathGlobs=*~Headers=user-agent,accept~Signature=SIGNATURE_OF_SIGNED_VALUE

ここで、SIGNATURE_OF_SIGNED_VALUE は以前に作成された符号付き値の ED25519 署名です。

対称鍵 HMAC

Expires=160000000~PathGlobs=*~Headers=user-agent,accept~hmac=HMAC_OF_SIGNED_VALUE

ここで、HMAC_OF_SIGNED_VALUE は、以前に作成された署名付き値の対称鍵 HMAC です。

前述の例では、Headers=user-agent,accept がトークンで指定されていますが、想定されるヘッダー値は、署名付き値から繰り返されません。これにより、トークン内の値を複製することなく、特定のリクエスト ヘッダーの Key-Value ペアに署名できます。