Gere tokens

Este guia explica como gerar um token e os campos obrigatórios e opcionais para tokens.

Para criar um token, compõe uma string para assinar, a que nos referimos como um valor assinado neste guia. O valor assinado inclui parâmetros que descrevem o conteúdo que está a proteger, a hora de validade do valor assinado e assim sucessivamente.

Usa o valor assinado ao criar uma string de token. Cria uma string de token ao compor os parâmetros do token, como um código de autenticação de mensagens (HMAC) baseado em hash de chave simétrica do valor assinado.

A RFC usa o token composto final para ajudar a proteger o seu conteúdo.

Crie um símbolo

  1. Crie um valor assinado concatenando uma string que contenha os campos de token obrigatórios e os campos de token opcionais desejados. Separe cada campo e todos os parâmetros com o caráter til ~.

  2. Assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

  3. Componha o token concatenando uma string que contenha os campos de token obrigatórios e os campos de token opcionais. Separe cada campo e todos os parâmetros com um caráter til ~.

    Ao compor o token, os valores de cada um dos parâmetros são os mesmos entre o valor assinado e a string do token, com as seguintes exceções:

    • FullPath
    • Headers

O seguinte exemplo de código mostra como criar um token de forma programática:

Python

Para se autenticar na RFC de multimédia, configure as Credenciais padrão da aplicação. Para mais informações, consulte o artigo Configure a autenticação para um ambiente de desenvolvimento local.

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

Para se autenticar na RFC de multimédia, configure as Credenciais padrão da aplicação. Para mais informações, consulte o artigo Configure a autenticação para um ambiente de desenvolvimento local.


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 + '\''
          + '}';
    }
  }

}

As secções seguintes descrevem os campos usados pelos tokens.

Campos de token obrigatórios

Os seguintes campos são obrigatórios para cada token:

  • Expires
  • Uma das seguintes opções:
    • PathGlobs
    • URLPrefix
    • FullPath
  • Uma das seguintes opções:
    • Signature
    • hmac

Salvo especificação em contrário, os nomes dos parâmetros e os respetivos valores são sensíveis a maiúsculas e minúsculas.

A tabela seguinte explica cada parâmetro:

Nome do campo / aliases Parâmetros de tokens Valor assinado

Expires

exp

Segundos inteiros decorridos desde o início da época Unix (1970-01-01T00:00:00Z) Expires=EXPIRATION_TIME, após o qual o token deixa de ser válido.

PathGlobs

paths, acl

Uma lista de até cinco segmentos de caminho aos quais conceder acesso. Os segmentos podem ser delimitados por vírgulas (,) ou pontos de exclamação (!), mas não ambos.

PathGlobs suporta carateres universais em caminhos através de asteriscos (*) e pontos de interrogação (?). Um único caráter asterisco (*) abrange qualquer número de segmentos de caminho, ao contrário da sintaxe de correspondência de padrões para pathMatchTemplate.

Os parâmetros de caminho, indicados com pontos e vírgulas (;), não são permitidos porque criam ambiguidade durante a correspondência.

Por estes motivos, certifique-se de que o URL não contém os seguintes carateres especiais: ,!*?;

PathGlobs=PATHS
URLPrefix

Um URL codificado em base64 seguro para a Web, incluindo o protocolo http:// ou https:// até um ponto à sua escolha.

Por exemplo, alguns valores URLPrefix válidos para `https://example.com/foo/bar.ts` são `https://example.com`, `https://example.com/foo` e `https://example.com/foo/bar`.

URLPrefix=BASE_64_URL_PREFIX
FullPath Nenhum. Quando especificar FullPath num token, não duplique o caminho que especificou no valor assinado. Num token, inclua o nome do campo sem =. FullPath=FULL_PATH_TO_OBJECT
Signature Uma versão codificada em Base64 segura para a Web da assinatura. Não aplicável
hmac Uma versão codificada em Base64 segura para a Web do valor HMAC. Não aplicável

Sintaxe de carateres universais do PathGlobs

A tabela seguinte explica a sintaxe do caráter universal PathGlobs.

Operador Correspondências Exemplos
* (asterisco) Corresponde a zero ou mais carateres no caminho do URL, incluindo carateres de barra (/).
  • /videos/* corresponde a qualquer caminho que comece com /videos/.
  • /videos/s*/4k/* corresponde a /videos/s/4k/ e /videos/s01/4k/main.m3u8.
  • /manifests/*/4k/* corresponde a /manifests/s01/4k/main.m3u8 e /manifests/s01/e01/4k/main.m3u8. Não corresponde a /manifests/4k/main.m3u8.
? (ponto de interrogação) Corresponde a um único caráter no caminho do URL, não incluindo carateres de barra (/). /videos/s?main.m3u8 correspondências /videos/s1main.m3u8. Não corresponde a nenhuma das opções: /videos/s01main.m3u8 ou /videos/s/main.m3u8.

Os globais têm de começar com um asterisco (*) ou uma barra (/) para caminhos de URLs.

Uma vez que * e /* correspondem a todos os caminhos de URL, não recomendamos a utilização de nenhum deles nos seus tokens assinados. Para máxima proteção, certifique-se de que os seus globs correspondem ao conteúdo ao qual pretende conceder acesso.

Campos de token opcionais

Salvo especificação em contrário, os nomes dos parâmetros e os respetivos valores são sensíveis a maiúsculas e minúsculas.

A tabela seguinte explica os nomes dos parâmetros, os eventuais alias e os detalhes dos parâmetros opcionais:

Nome do campo / aliases Parâmetros Valor assinado

Starts

st

Segundos inteiros desde o início da época Unix (1970-01-01T00:00:00Z) Starts=START_TIME
IPRanges

Uma lista de até cinco endereços IPv4 e IPv6 no formato CIDR para os quais este URL é válido no formato base64 seguro para a Web. Por exemplo, para especificar os intervalos de IP "192.6.13.13/32,193.5.64.135/32", especifica IPRanges=MTkyLjYuMTMuMTMvMzIsMTkzLjUuNjQuMTM1LzMy.

Os IPRanges podem não ser úteis para incluir em tokens quando os clientes estão em risco de migrações de WAN ou casos em que o caminho de rede para o frontend da sua aplicação é diferente do caminho de entrega. A RFC de multimédia rejeita clientes com um código HTTP 403 quando se ligam a um endereço IP que não faz parte do pedido assinado.

Seguem-se casos que podem resultar na rejeição de clientes pela RFC de multimédia com um código HTTP 403:

  • Ambientes de pilha dupla (IPv4 e IPv6)
  • Migração da ligação (Wi-Fi para rede móvel e rede móvel para Wi-Fi)
  • Redes móveis que usam a NAT de gateway do operador (CGNAT ou CGN)
  • TCP multipath (MPTCP)

Todos estes fatores podem contribuir para que um determinado cliente tenha um endereço IP não determinístico durante uma sessão de reprodução de vídeo. Se o endereço IP do cliente mudar depois de ter emitido o acesso e o cliente tentar transferir um segmento de vídeo para o respetivo buffer de reprodução, recebe um HTTP 403 da RFC de multimédia.

IPRanges=BASE_64_IP_RANGES

SessionID

id

Uma string arbitrária, útil para a análise de registos ou o rastreio de reprodução.

Para evitar a criação de um token inválido, use strings com codificação em percentagem ou com codificação base64 segura para a Web. Não pode usar os seguintes carateres para SessionID, uma vez que fazem com que o token seja inválido: "~", "&" ou " " (espaço).

SessionID=SESSION_ID_VALUE

Data

data, payload

Uma string arbitrária, útil para a análise de registos.

Para evitar a criação de um token inválido, use strings com codificação em percentagem ou com codificação base64 segura para a Web. Não pode usar os seguintes carateres para Data, uma vez que fazem com que o token seja inválido: "~", "&" ou " " (espaço).

data=DATA_VALUE
Headers Uma lista de nomes de campos de cabeçalho delimitada por vírgulas. Os nomes dos cabeçalhos não são sensíveis a maiúsculas e minúsculas para as pesquisas no pedido. Os nomes dos cabeçalhos nos valores assinados são sensíveis a maiúsculas e minúsculas. Se um cabeçalho estiver em falta, o valor é a string vazia. Se existirem várias cópias de um cabeçalho, estas são concatenadas com vírgulas. Headers=HEADER_1_NAME=HEADER_1_EXPECTED_VALUE, HEADER_2_NAME=HEADER_2_EXPECTED_VALUE

Exemplos

As secções seguintes mostram exemplos de geração de tokens.

Exemplo de utilização de FullPath

Considere o seguinte exemplo com o campo FullPath:

  • Item pedido: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Tempo de expiração: 160000000

O valor assinado é:

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

Para criar um token, assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

Seguem-se exemplos de tokens criados a partir de um valor assinado:

Assinatura Ed25519

Expires=160000000~FullPath~Signature=SIGNATURE_OF_SIGNED_VALUE

Onde SIGNATURE_OF_SIGNED_VALUE é a assinatura ED25519 do valor assinado criado anteriormente.

HMAC de chave simétrica

Expires=160000000~FullPath~hmac=HMAC_OF_SIGNED_VALUE

Onde HMAC_OF_SIGNED_VALUE é o HMAC de chave simétrica do valor assinado criado anteriormente.

Nos exemplos anteriores, FullPath é fornecido no token, mas o valor não é repetido a partir do caminho especificado no valor assinado. Isto permite-lhe assinar o caminho completo do pedido sem duplicar o pedido no token.

Exemplo de utilização de URLPrefix

Considere o seguinte exemplo com o campo URLPrefix:

  • Item pedido: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Tempo de expiração: 160000000

O valor assinado é:

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4

No exemplo anterior, substituímos o caminho para o item pedido, http://example.com/tv/my-show/s01/e01/playlist.m3u8 pelo caminho para o item no formato Base64 seguro para a Web, aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4.

Para criar um token, assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

Seguem-se exemplos de tokens criados a partir de um valor assinado:

Assinatura Ed25519

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~Signature=SIGNATURE_OF_SIGNED_VALUE

Onde SIGNATURE_OF_SIGNED_VALUE é a assinatura ED25519 do valor assinado criado anteriormente.

HMAC de chave simétrica

Expires=160000000~URLPrefix=aHR0cDovL2V4YW1wbGUuY29tL3R2L215LXNob3cvczAxL2UwMS9wbGF5bGlzdC5tM3U4~hmac=HMAC_OF_SIGNED_VALUE

Onde HMAC_OF_SIGNED_VALUE é o HMAC de chave simétrica do valor assinado criado anteriormente.

Exemplo de utilização de Headers

Considere o seguinte exemplo com o campo Headers:

  • Item pedido: http://example.com/tv/my-show/s01/e01/playlist.m3u8
  • Tempo de expiração: 160000000
  • Valor de PathGlobs: *
  • Cabeçalhos do pedido esperados:
    • user-agent: browser
    • accept: text/html

O valor assinado é:

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

Para criar um token, assine o valor assinado com uma assinatura Ed25519 ou um HMAC de chave simétrica.

Seguem-se exemplos de tokens criados a partir de um valor assinado:

Assinatura Ed25519

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

Onde SIGNATURE_OF_SIGNED_VALUE é a assinatura ED25519 do valor assinado criado anteriormente.

HMAC de chave simétrica

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

Onde HMAC_OF_SIGNED_VALUE é o HMAC de chave simétrica do valor assinado criado anteriormente.

Nos exemplos anteriores, Headers=user-agent,accept é fornecido no token, mas os valores dos cabeçalhos esperados não são repetidos a partir do valor assinado. Isto permite-lhe assinar pares de chave-valor de cabeçalho de pedido específicos sem duplicar os valores no token.