Sample-Level AES-128 HLS Encryption with FFmpeg

This article provides a direct guide on securing HTTP Live Streaming (HLS) streams using AES-128 encryption with FFmpeg. It explains the requirements for encryption keys, how to construct the necessary key information file, the precise FFmpeg commands for generating encrypted segments, and the technical distinction between standard segment-level AES-128 and sample-level (SAMPLE-AES) encryption workflows.

Understanding AES-128 vs. SAMPLE-AES

HLS supports two primary forms of AES-128 encryption:

  1. Segment-Level (AES-128): The entire media segment (Transport Stream or fragmented MP4) is encrypted using cipher block chaining (CBC). This is natively supported by FFmpeg's built-in HLS muxer.
  2. Sample-Level (SAMPLE-AES): Individual audio and video media samples within the container are encrypted, leaving container headers in plain text. This is commonly required for FairPlay Streaming DRM.

Native FFmpeg provides built-in support for segment-level AES-128. True SAMPLE-AES requires fragmented MP4 (fMP4) packaging and is typically handled either by piping FFmpeg output to a dedicated packager (such as Bento4 or Shaka Packager) or using FFmpeg to generate standard AES-128 encrypted HLS.


Step 1: Generate the Encryption Key and IV

AES-128 requires a 16-byte (128-bit) binary key and an optional 16-byte Initialization Vector (IV). Generate these using OpenSSL:

# Generate a random 16-byte binary key
openssl rand 16 > enc.key

# Generate a 16-byte hexadecimal IV (32 hex characters)
openssl rand -hex 16 > enc.iv

Step 2: Create the Key Info File

FFmpeg uses a key_info_file to map the key location, URI, and IV into the generated M3U8 playlist.

Create a plain text file named enc.keyinfo containing the following structure:

https://example.com/keys/enc.key
enc.key
0123456789abcdef0123456789abcdef
  • Line 1: The public URI where the player will fetch the key.
  • Line 2: The local path to enc.key so FFmpeg can read it during encoding.
  • Line 3 (Optional): The hexadecimal IV. If omitted, FFmpeg uses the segment sequence number as the IV.

Step 3: Run the FFmpeg Command

Execute FFmpeg with the -hls_key_info_file option to encrypt segments during packaging:

ffmpeg -i input.mp4 \
  -c:v libx264 -b:v 2000k \
  -c:a aac -b:a 128k \
  -hls_time 6 \
  -hls_playlist_type vod \
  -hls_key_info_file enc.keyinfo \
  -hls_segment_filename "segment_%03d.ts" \
  master.m3u8

The resulting master.m3u8 playlist will contain the #EXT-X-KEY tag instructing the player to decrypt each segment:

#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/keys/enc.key",IV=0x0123456789abcdef0123456789abcdef

Step 4: True Sample-Level Encryption (SAMPLE-AES Workflow)

If your client architecture strictly mandates METHOD=SAMPLE-AES (e.g., Apple FairPlay), FFmpeg's native hls_key_info_file will not suffice alone because it outputs METHOD=AES-128.

To achieve true sample-level encryption, encode to an intermediate fragmented MP4 using FFmpeg, then pass the file to a specialized packager:

# Encode to fMP4 with FFmpeg
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -movflags frag_keyframe+empty_moov intermediate.mp4

# Encrypt using Bento4 for SAMPLE-AES HLS packaging
mp4hls --encryption-mode=SAMPLE-AES \
  --encryption-key=0123456789abcdef0123456789abcdef:enc.key \
  --encryption-key-uri=https://example.com/keys/enc.key \
  intermediate.mp4

This ensures the manifest outputs #EXT-X-KEY:METHOD=SAMPLE-AES with payload-level encryption compatible with hardware-accelerated DRM frameworks.