Forensic Watermarking with Beacon Apps

In this topic, you will learn how to implement Forensic Watermarking with Beacon apps.

Introduction

Brightcove has partnered with NAGRA to provide Forensic Watermarking with Beacon apps to help protect premium content from piracy and unauthorized content sharing. With this feature, customers will be able to identify the source of a content leak and take appropriate action.

The following diagram shows an overview:

  • Content preparation
    • The Forensic Watermark is an invisible watermark embedded into the video during transcoding using Nagra’s SDK
    • Ingestion creates 2 VOD renditions, one with watermark A and another with watermark B; both renditions are part of the same title in Video Cloud
  • Delivery
    • When playing the content, the Forensic Watermark token is provided to the player, then included in the URL used to request content from the CDN
    • The CDN interprets the token and delivers the video with the correct sequence of A/B segments to the viewer
Overview diagram
Forensic watermarking overview

Requirements

The following requirements are needed to support this feature:

  • Beacon apps need to use OIDC authentication
  • Only available for VOD assets
  • MP4 renditions will not be generated for watermarked videos

Setup

The following setup is needed to support Brightcove's Forensic Watermarking solution:

  1. Customer Video Cloud account:
    • Make sure the customer's account is enabled for Dynamic Delivery.
    • Open a customer support ticket to enable the customer's account for Forensic Watermarking; This is a paid add-on to Video Cloud.
  2. The customer will get their License Key from NAGRA.
  3. The customer will generate a public-private key pair which will be used by the Forensic Watermarking Token (WMT) and decrypted by the CDN. For examples, see the section below.
  4. The customer will use the script provided by NAGRA to generate a Forensic Watermarking Token (WMT).
  5. From the customer, request the name of the claim that will pass the Watermarking Token (WMT) to the apps. Share this claim with the Beacon team, so that they can configure it in the Beacon Master CMS.

Generate a public-private key pair

There are many ways to generate the public-private key pair. Here are some examples:

Example bash script:

Example script to generate the key pair:

#!/bin/bash
set -euo pipefail

NAME=${1:-}
test -z "${NAME:-}" && NAME="brightcove-forensic-watermarking-key-$(date +%s)"
mkdir "$NAME"

PRIVATE_PEM="./$NAME/private.pem"
PUBLIC_PEM="./$NAME/public.pem"
PUBLIC_TXT="./$NAME/public_key.txt"

ssh-keygen -t rsa -b 2048 -m PEM -f "$PRIVATE_PEM" -q -N ""
openssl rsa -in "$PRIVATE_PEM" -pubout -outform PEM -out "$PUBLIC_PEM" 2>/dev/null
openssl rsa -in "$PRIVATE_PEM" -pubout -outform DER | base64 > "$PUBLIC_TXT"

rm "$PRIVATE_PEM".pub

echo "Public key to saved in $PUBLIC_TXT"

Run the script:

$ bash keygen.sh
Example using Go

Example using the Go programming language to generate the key pair:

package main
  
  import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/base64"
    "encoding/pem"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "path"
    "strconv"
    "time"
  )
  
  func main() {
    var out string
  
    flag.StringVar(&out, "output-dir", "", "Output directory to write files into")
    flag.Parse()
  
    if out == "" {
      out = "rsa-key_" + strconv.FormatInt(time.Now().Unix(), 10)
    }
  
    if err := os.MkdirAll(out, os.ModePerm); err != nil {
      panic(err.Error())
    }
  
    priv, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
      panic(err.Error())
    }
  
    privBytes := x509.MarshalPKCS1PrivateKey(priv)
  
    pubBytes, err := x509.MarshalPKIXPublicKey(priv.Public())
    if err != nil {
      panic(err.Error())
    }
  
    privOut, err := os.OpenFile(path.Join(out, "private.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
    if err != nil {
      panic(err.Error())
    }
  
    if err := pem.Encode(privOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: privBytes}); err != nil {
      panic(err.Error())
    }
  
    pubOut, err := os.OpenFile(path.Join(out, "public.pem"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
    if err != nil {
      panic(err.Error())
    }
  
    if err := pem.Encode(pubOut, &pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes}); err != nil {
      panic(err.Error())
    }
  
    var pubEnc = base64.StdEncoding.EncodeToString(pubBytes)
  
    var pubEncOut = path.Join(out, "public_key.txt")
    if err := ioutil.WriteFile(pubEncOut, []byte(pubEnc+"\n"), 0600); err != nil {
      panic(err.Error())
    }
  
    fmt.Println("Public key saved in " + pubEncOut)
  }
  

Example using node.js

Example using node.js to generate the key pair:

var crypto = require("crypto");
  var fs = require("fs");
  
  var now = Math.floor(new Date() / 1000);
  var dir = "rsa-key_" + now;
  fs.mkdirSync(dir);
  
  crypto.generateKeyPair(
    "rsa",
    {modulusLength: 2048},
    (err, publicKey, privateKey) => {
      fs.writeFile(
        dir + "/public.pem",
        publicKey.export({ type: "spki", format: "pem" }),
        err => {}
      );
      fs.writeFile(
        dir + "/public_key.txt",
        publicKey.export({ type: "spki", format: "der" }).toString("base64") +
          "\n",
        err => {}
      );
      fs.writeFile(
        dir + "/private.pem",
        privateKey.export({ type: "pkcs1", format: "pem" }),
        err => {}
      );
    }
  );
  
  console.log("Public key saved in " + dir + "/public_key.txt");

Supported features and limitations

For a list of supported features and limitations when using Forensic Watermarking, see the Overview: Forensic Watermarking document.