Choosing an encryption approach for web media

Protecting media delivered over the web comes down to two broad strategies: Clear Key encryption for basic protection, and a commercial DRM service such as Google Widevine, Microsoft PlayReady, or Apple FairPlay for stronger security. The difference matters because of how decryption keys are handled. Clear Key transmits the key-value pair as plain text alongside the encrypted media, meaning the key itself is not secret. DRM services encrypt keys under a separate decryption key stored on a license server, so interception of the key exchange does not expose the content.

The practical work of converting and encrypting media in this article relies on Shaka Packager, FFmpeg, and OpenSSL. Modern browser support typically requires all three major DRM services—Widevine in Chrome and Firefox, PlayReady in Microsoft browsers, and FairPlay in Apple's ecosystem—though the examples here focus on Clear Key and Widevine only.

Clear Key encryption

Clear Key is appropriate when you do not need the licensing infrastructure of a commercial DRM and basic encryption is an acceptable trade-off. Because the key is sent in plain text, anyone who can observe the exchange can decrypt the media. It is not a substitute for DRM when content protection is the priority.

Generating a key and IV

Key generation for both DASH and HLS follows the same process. OpenSSL creates a 16-byte encryption key:

openssl rand -hex 16 > media.key

An initialization vector (IV) is generated separately the same way:

openssl rand -hex 16
6143b5373a51cb46209cfed0d747da66

Creating a key information file for HLS

HLS encryption requires a key information file (with a .keyinfo extension, e.g. encrypt.keyinfo) in addition to the key itself. The file format is a text file that specifies three things: the key URI (where media.key will live on your server), the key file path (relative to the key information file), and the private key, which is the contents of media.key or the IV you generated above:

key URI
key file path
private key

A concrete example of that file would look like the following, with the URI pointing at the server location of your key file:

https://example.com/keys/media.key
/path/to/media.key
6143b5373a51cb46209cfed0d747da66

Encrypting with Clear Key

Encryption with Clear Key uses Shaka Packager's raw key mode, where keys and key_ids are supplied directly on the command line. Use the key you created earlier from the media.key file, being careful to strip any whitespace when pasting it into the command. For key_id, you can reuse the media.id value or substitute the IV you generated above:

packager \
  input=glocken.mp4,stream=audio,output=glockena.m4a \
  input=glocken.mp4,stream=video,output=glockenv.mp4 \
  --enable_fixed_key_encryption \
  --keys label=audio:key=INSERT_AUDIO_KEY_HERE:key_id=INSERT_AUDIO_KEY_ID_HERE,label=video:key=INSERT_VIDEO_KEY_HERE:key_id=INSERT_VIDEO_KEY_ID_HERE

For HLS, the command includes the key information file and accepts a key of either 16 or 32 characters:

packager \
  'input=input.mp4,stream=video,segment_template=output$Number$.ts,playlist_name=video_playlist.m3u8' \
  'input=input.mp4,stream=audio,segment_template=output_audio$Number$.ts,playlist_name=audio_playlist.m3u8,hls_group_id=audio,hls_name=ENGLISH' \
  --hls_master_playlist_output="master_playlist.m3u8" \
  --hls_base_url="http://localhost:5000/"
ffmpeg -i myvideo.mov -c:v libx264 -c:a aac -hls_key_info_file encrypt.keyinfo myvideo.m3u8

Encrypting with Widevine

Widevine is Google's DRM, supported by Chrome, Firefox, Android MediaDRM, Android TV, and other devices implementing Encrypted Media Extensions and Media Source Extensions. When you move from Clear Key to Widevine, most of your Shaka Packager command stays the same. The key difference is replacing the raw key–related options with the Widevine license server settings:

--enable_fixed_key_encryption \
--enable_fixed_key_decryption \
--keys label=:key=INSERT_KEY_HERE:key_id=INSERT_KEY_ID_HERE

In the demux stage, copy everything from the example exactly except your file names and the --content-id flag. That flag must be a 16 or 32 digit random hex value. Use the keys shown in the example rather than your own generated ones. Shaka Packager's Widevine Key Server documentation has additional use cases.

Full conversion sequences

The following recipes walk from a raw .mov source file to encrypted assets packaged for DASH or HLS. The sample conversion targets an 8 Mbps bitrate at 1080p (1920 × 1080); adapt the values to your own requirements.

DASH/WebM

  1. Convert container and codecs. Either liborbis or libopus can serve as the audio codec:

    ffmpeg -i glocken.mov -c:v libvpx-vp9 -c:a libvorbis -b:v 8M -vf setsar=1:1 -f webm tmp_glocken.webm
    
  2. Generate a Clear Key encryption key:

    openssl rand -hex 16 > media.key
    
  3. Demux audio and video, encrypt the resulting files, and emit an MPD manifest:

    packager \
      input=tmp_glocken.webm,stream=video,output=glocken_video.webm \
      input=tmp_glocken.webm,stream=audio,output=glocken_audio.webm \
      --enable_fixed_key_encryption \
      --enable_fixed_key_decryption \
      --keys label=:key=INSERT_KEY_HERE:key_id=INSERT_KEY_ID_HERE \
      --mpd_output glocken_webm_vod.mpd
    
  4. Remux the audio and video streams. If you use a video framework, this step may be unnecessary:

    ffmpeg -i glocken_video.webm -i glocken_audio.webm -c copy glocken.webm
    

DASH/MP4

  1. Convert container, video codec, and bitrate:

    ffmpeg -i glocken.mov -c:v libx264 -c:a aac -b:v 8M -strict -2 tmp_glocken.mp4
    
  2. Generate a Clear Key encryption key:

    openssl rand -hex 16 > media.key
    
  3. Demux, encrypt, and produce the MPD file:

    packager \
      input=tmp_glocken.mp4,stream=video,output=glocken_video.mp4 \
      input=tmp_glocken.mp4,stream=audio,output=glocken_audio.m4a \
      --enable_fixed_key_encryption \
      --enable_fixed_key_decryption \
      --keys label=:key=INSERT_KEY_HERE:key_id=INSERT_KEY_ID_HERE \
      --mpd_output glocken_mp4_vod.mpd
    
  4. Remux the encrypted audio and video streams, unless your framework handles it:

    ffmpeg -i glocken_video.mp4 -i glocken_audio.m4a -c copy glocken.mp4
    

HLS/MP4

HLS restricts you to the MP4 container, so the source must first be converted to MP4 with compatible codecs before encryption.

  1. Convert container, video codec, and bitrate:

    ffmpeg -i glocken.mov -c:v libx264 -c:a aac -b:v 8M -strict -2 glocken.mp4
    
  2. Generate a Clear Key encryption key:

    openssl rand -hex 16 > media.key
    
  3. Create the key information file:

    packager \
      'input=glocken.mp4,stream=video,segment_template=output$Number$.ts,playlist_name=video_playlist.m3u8' \
      'input=glocken.mp4,stream=audio,segment_template=output_audio$Number$.ts,playlist_name=audio_playlist.m3u8,hls_group_id=audio,hls_name=ENGLISH' \
      --hls_master_playlist_output="master_playlist.m3u8" \
      --hls_base_url="http://localhost:5000/" \
      --enable_fixed_key_encryption \
      --enable_fixed_key_decryption \
      --keys label=:key=INSERT_KEY_HERE:key_id=INSERT_KEY_ID_HERE
    

With these steps you can move from a raw source file to encrypted DASH or HLS assets under either Clear Key or Widevine protection, choosing the approach that matches your security requirements.