יצירת אסימונים

במדריך הזה מוסבר איך ליצור טוקן, ומוצגים השדות הנדרשים והאופציונליים לטוקנים.

כדי ליצור טוקן, צריך ליצור מחרוזת לחתימה, שאנחנו קוראים לה במדריך הזה ערך חתום. הערך החתום כולל פרמטרים שמתארים את התוכן שאתם מגנים עליו, את זמן התפוגה של הערך החתום וכו'.

משתמשים בערך החתום כשיוצרים מחרוזת של טוקן. יוצרים מחרוזת של טוקן על ידי הרכבת הפרמטרים של הטוקן, כמו קוד אימות הודעות (HMAC) מבוסס-גיבוב (hash) של ערך חתום עם מפתח סימטרי.

‫Media CDN משתמש באסימון הסופי המורכב כדי להגן על התוכן שלכם.

יצירת טוקן

  1. כדי ליצור ערך חתום, צריך לשרשר מחרוזת שמכילה את שדות הטוקן הנדרשים ואת שדות הטוקן האופציונליים הרצויים. מפרידים בין כל שדה וכל פרמטר באמצעות התו ~ (טילדה).

  2. חתימה על הערך החתום באמצעות חתימת Ed25519 או מפתח סימטרי HMAC.

  3. יוצרים את האסימון על ידי שרשור של מחרוזת שמכילה את שדות החובה של האסימון ואת השדות האופציונליים של האסימון. מפרידים בין כל שדה ובין כל פרמטר באמצעות התו 'טילדה' (~).

    כשיוצרים את האסימון, הערכים של כל אחד מהפרמטרים זהים בין הערך החתום לבין מחרוזת האסימון, למעט המקרים הבאים:

    • FullPath
    • Headers

בדוגמת הקוד הבאה אפשר לראות איך ליצור טוקן באופן פרוגרמטי:

Python

כדי לבצע אימות ב-Media CDN, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.

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, צריך להגדיר את Application Default Credentials. מידע נוסף זמין במאמר הגדרת אימות לסביבת פיתוח מקומית.


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
  • אחד מהפרטים הבאים:
    • Signature
    • hmac

אלא אם צוין אחרת, השמות של הפרמטרים והערכים שלהם הם תלויי אותיות רישיות.

בטבלה הבאה מוסבר על כל פרמטר:

שם השדה / כינויים פרמטרים של טוקנים ערך חתום

Expires

exp

מספר שלם של שניות שחלפו מאז ראשית זמן יוניקס (Unix epoch) (1970-01-01T00:00:00Z) Expires=EXPIRATION_TIME, ואחריו הטוקן כבר לא בתוקף.

PathGlobs

paths, acl

רשימה של עד חמישה פלחים בנתיב שאליהם רוצים להעניק גישה. אפשר להשתמש בפסיקים (,) או בסימני קריאה (!) כדי להפריד בין הפלחים, אבל לא בשניהם.

PathGlobs תומך בשימוש בתווים כלליים בנתיבים באמצעות כוכביות (*) וסימני שאלה (?). כוכבית אחת (*) מייצגת מספר כלשהו של מקטעי נתיב, בניגוד לתחביר של התאמת תבניות ב-pathMatchTemplate.

אסור להשתמש בפרמטרים של נתיבים, שמסומנים באמצעות נקודה ופסיק (;), כי הם יוצרים דו-משמעות בזמן ההתאמה.

לכן, חשוב לוודא שכתובת ה-URL לא מכילה את התווים המיוחדים הבאים: ,!*?;

PathGlobs=PATHS
URLPrefix

כתובת URL מקודדת ב-base64 שמתאימה לאינטרנט, כולל הפרוטוקול ‫http:// או https:// עד לנקודה שתבחרו.

לדוגמה, חלק מהערכים התקינים של URLPrefix עבור ‫`https://example.com/foo/bar.ts` הם `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, כולל תווים של לוכסן קדימה (/).
  • /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, לא כולל תווים של קו נטוי קדימה (/). /videos/s?main.m3u8 matches /videos/s1main.m3u8. הוא לא תואם ל-/videos/s01main.m3u8 או ל-/videos/s/main.m3u8.

כתובות Globs חייבות להתחיל בכוכבית (*) או בקו נטוי קדימה (/) עבור נתיבים של כתובות URL.

מכיוון ש-* ו-/* תואמים לכל נתיבי כתובות ה-URL, לא מומלץ להשתמש באף אחד מהם באסימונים החתומים. כדי להבטיח הגנה מקסימלית, חשוב לוודא שהתבניות תואמות לתוכן שאליו אתם רוצים להעניק גישה.

שדות אופציונליים של טוקן

אלא אם צוין אחרת, השמות של הפרמטרים והערכים שלהם הם תלויי אותיות רישיות.

בטבלה הבאה מוסבר על שמות הפרמטרים, הכינויים שלהם ופרטים על פרמטרים אופציונליים:

שם השדה / כינויים פרמטרים ערך חתום

Starts

st

מספר שלם של שניות מאז ראשית זמן יוניקס (1970-01-01T00:00:00Z) Starts=START_TIME
IPRanges

רשימה של עד חמש כתובות IPv4 ו-IPv6 בפורמט CIDR, שכתובת ה-URL הזו תקפה לגביהן בפורמט base64 בטוח לשימוש באינטרנט. לדוגמה, כדי לציין את טווחי כתובות ה-IP ‏192.6.13.13/32,193.5.64.135/32, מציינים את הערך IPRanges=MTkyLjYuMTMuMTMvMzIsMTkzLjUuNjQuMTM1LzMy.

יכול להיות שטווח כתובות ה-IP לא יעזור לכם אם אתם רוצים לכלול אותו בטוקנים כשלקוחות נמצאים בסיכון למיגרציות WAN או במקרים שבהם נתיב הרשת לקצה הקדמי של האפליקציה שונה מנתיב המסירה. ‫Media CDN דוחה לקוחות עם קוד HTTP 403 כשהם מתחברים עם כתובת IP שלא כלולה בבקשה החתומה.

אלה מקרים שבהם יכול להיות ש-Media CDN ידחה לקוחות עם קוד HTTP 403:

  • סביבות עם תמיכה כפולה (IPv4, ‏ IPv6)
  • העברת חיבור (מ-Wi-Fi לרשת סלולרית, ומחיבור לרשת סלולרית ל-Wi-Fi)
  • רשתות סלולריות שמשתמשות ב-NAT של שער הספק (CGNAT או CGN)
  • ‫Multi-path TCP (MPTCP)

כל הגורמים האלה יכולים לגרום לכך שללקוח מסוים תהיה כתובת IP לא דטרמיניסטית במהלך הפעלת סרטון. אם כתובת ה-IP של הלקוח משתנה אחרי שניתנה גישה, והלקוח מנסה להוריד קטע וידאו למאגר הזמני להפעלה, הוא מקבל קוד שגיאה HTTP 403 מ-Media CDN.

IPRanges=BASE_64_IP_RANGES

SessionID

id

מחרוזת שרירותית, שימושית לניתוח יומנים או למעקב אחר הפעלה.

כדי להימנע מיצירת טוקן לא תקין, צריך להשתמש במחרוזות עם קידוד % או במחרוזות עם קידוד Base64 שמתאים לשימוש באינטרנט. אסור להשתמש בתווים הבאים בשם SessionID, כי הם גורמים לכך שהאסימון לא יהיה תקין: "~", "&" או " " (רווח).

SessionID=SESSION_ID_VALUE

Data

data, payload

מחרוזת שרירותית, שימושית לניתוח יומנים.

כדי להימנע מיצירת טוקן לא תקין, צריך להשתמש במחרוזות עם קידוד % או במחרוזות עם קידוד 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 מופיע בטוקן, אבל ערכי הכותרת הצפויים לא חוזרים על עצמם מהערך החתום. כך אפשר לחתום על צמדי מפתח/ערך ספציפיים בכותרת הבקשה בלי לשכפל את הערכים בטוקן.