How to Encrypt Video for HLS

In this post, we’ll look at what encryption HLS supports and how to encrypt your videos with ffmpeg.

Encryption is the process of encoding information in such a way that only authorised parties can read it. The encryption process requires some kind of secret (key) together with an encryption algorithm.

There are many different types of encryption algorithms but HLS only supports AES-128. The Advanced Encryption Standard (AES) is an example of a block cipher, which encrypts (and decrypts) data in fixed-size blocks. It’s a symmetric key algorithm, which means that the key that is used to encrypt data is also used to decrypt it. AES-128 uses a key length of 128 bits (16 bytes).

HLS uses AES in cipher block chaining (CBC) mode. This means each block is encrypted using the cipher text of the preceding block, but this gives us a problem: how do we encrypt the first block? There is no block before it! To get around this problem we use what is known as an initialisation vector (IV). In this instance, it’s a 16-byte random value that is used to intialize the encryption process. It doesn’t need to be kept secret for the encryption to be secure.

Before we can encrypt our videos, we need an encryption key. I’m going to use OpenSSL to create the key, which we can do like so:

$ openssl rand 16 > enc.key

This instructs OpenSSL to generate a random 16-byte value, which corresponds to the key length (128 bits).

The next step is to generate an IV. This step is optional. (If no value is provided, the segment sequence number will be used instead.)

$ openssl rand -hex 16
ecd0d06eaf884d8226c33928e87efa33

Make a note of the output as you’ll need it shortly.

To encrypt the video we need to tell ffmpeg what encryption key to use, the URI of the key, and so on. We do this with -hls_key_info_file option passing it the location of a key info file. The file must be in the following format:

Key URI
Path to key file
IV (optional)

The first line specifies the URI of the key, which will be written to the playlist. The second line is the path to the file containing the encryption key, and the (optional) third line contains the initialisation vector. Here’s an example (enc.keyinfo):

https://hlsbook.net/enc.key
enc.key
ecd0d06eaf884d8226c33928e87efa33

Now that we have everything we need, run the following command to encrypt the video segments:

ffmpeg -y \
    -i sample.mov \
    -hls_time 9 \
    -hls_key_info_file enc.keyinfo
    -hls_playlist_type vod \
    -hls_segment_filename "fileSequence%d.ts" \
    prog_index.m3u8

Take a look at the generated playlist (prog_index.m3u8). It should look something like this:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:9
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-KEY:METHOD=AES-128,URI="https://hlsbook.net/enc.key",IV=0xecd0d06eaf884d8226c33928e87efa33
#EXTINF:8.33333
fileSequence0.ts
#EXTINF:8.33333
fileSequence1.ts
#EXTINF:8.33333
fileSequence2.ts
#EXTINF:8.33333
fileSequence3.ts
#EXTINF:8.33333
fileSequence4.ts
#EXTINF:5.66667
fileSequence5.ts
#EXT-X-ENDLIST

Note the URI of the encryption key. The player will retrieve the key from this location to decrypt the media segments. To protect the key from eavesdroppers it should be served over HTTPS. You may also want to implement some of authentication mechanism to restrict who has access to the key. If you’re interested, the book goes into some detail about how to achieve this. Click here to buy a copy.

To verify that the segments really are encrypted, try playing them using a media player like QuickTime or VLC. You shouldn’t be able to. Now run the command above without the encryption and then try playing a segment. Notice the difference.

In this instance, all the segments are encrypted with the same key. It can be beneficial to periodically change the encryption keys to minimise the impact if a particular key is exposed. This is known as key rotation, and the amount of time between successive key generations is referred to as the key rotation period.

To enable key rotation, set the -hls_flags option to periodic_rekey. When enabled, the key info file will be checked periodically. If the file has changed, segments will then be encrypted with the new encryption key. However, we still need to come up with a way of generating new keys and updating the key info file. Here’s an example of how it could be done:

#!/bin/bash
i=2
while true
do
    sleep 15
    tmpfile=`mktemp`
    openssl rand 16 > enc$i.key
    echo https://hlsbook.net/enc$i.key > $tmpfile
    echo enc$i.key >> $tmpfile
    echo `openssl rand -hex 16` >> $tmpfile
    mv $tmpfile enc.keyinfo
    let i++
done

The script uses the same values as before for the base URL (for each key) and key info file. It generates a new encryption key every 15 seconds, updating the key info file accordingly. (In practice you would choose a much longer key rotation period.)

To test it, run ffmpeg again but this time enable key rotation. At the same time, open a new terminal and run the script from the same directory. (The script will run forever until you terminate it.) When ffmpeg has finished, terminate the script.

Take a look at the playlist. You should now see a number of different key entries in the playlist. All the segments that follow a key tag (#EXT-X-KEY) are now encrypted using the key specified by the tag, instead of using one key to encrypt all segments like before.

Even though HLS supports encryption, which provides some sort of content protection, it isn’t a full DRM solution. If that kind of thing interests you then you may want to take a look at Apple’s FairPlay Streaming solution.

42 thoughts on “How to Encrypt Video for HLS

  1. Mehmood

    Hi Simon,
    Your post is great. I am new to HLS and your site is really helpful.
    I have a question. How can I use mediafilesegmenter to encrypt every segment of the file with different key ? Apple docs says such thing is supported but I couldn’t find any help regarding it.

    It would be great if you can guide about it, provide command of it.
    Thanks

    Reply
    1. sbuckle Post author

      Hi Mehmood,

      You can use the -key-rotation-period option of mediafilesegmenter. It takes as an argument a number that specifies how many segments to encrypt before generating a new key. If you want to encrypt every segment with a different key then I guess you will need to set it to 1.

      There’s more information in the Security chapter of the book if you are interested.

      Simon

      Reply
  2. Rj

    Hi Simon

    I am new to HLS AES encryption can i get the example of the above procedure in the book meaning a video file taken and encryption explained in step by step fashion including openssl and ffmpeg

    Thanks however for this informative article

    Regards

    Reply
  3. Steve

    Hi Simon, I hope this thread is still active. I’m new to ffmpeg and tried the commands you mentioned to encrypt HLS with an openssl key, and it all worked. But one basic question- how do I direct the all the encrypted HLS chunks to a separate folder? In your script they just end up in the folder where I ran the script, so its a little messy. Many thanks

    Reply
    1. Simon Post author

      Hi Steve,

      You can specify a relative or absolute path to the HLS segments. The following example will output the segments in a directory relative to the location where you run the command:

      ffmpeg -y \
          -i sample.mov \
          -hls_time 9 \
          -hls_key_info_file enc.keyinfo \
          -hls_playlist_type vod \
          -hls_segment_filename "hls/fileSequence%d.ts" \
          -use_localtime_mkdir 1 \
          prog_index.m3u8
      

      Make sure the directory you are writing the segments to exists. Take a look at the available options for more information.

      Reply
  4. ads1018

    Hey Simon, great post. I’m trying to implement an auth system so that when I stream a file, encrypted, over RTMP using ffmpeg you can only consume the the stream if they have a valid decryption key. Is this possible? If the decryption key is included in the generated playlist then *anyone* can consume it which isn’t ideal. Any thoughts, on how to achieve this? Ideally, I don’t have to use a crazy proprietary DRM solution with a license server.

    Reply
    1. Simon Post author

      Even though the reference to the key is embedded in an HLS playlist, you can protect access to it. I’m going to assume that the key(s) and/or the playlist are served over HTTPS. You can use something like basic authentication, which requires a username and password, or use client certificates and enable TLS client authentication to restrict access. That’s not going to prevent an authorised user from accessing the key, but it will prevent an unauthorised user from doing so. Other than that, you’re going to have to resort to a DRM solution.

      Reply
  5. Steve

    Hi Simon- With the help of your book, I have implemented HLS encryption successfully. The HLS encrypted chunks are stored on a protected server, and the embedded HLS player accesses the keys from the path in the playlist.m3u8 file. The keys are located on the same server that embeds the player, and viewers must be authenticated before accessing the player. To prevent just any site from accessing the key path, I have implemented CORS and only allowed the server where the player is embedded to access. Is there a better, more secure way to restrict access to the keys than CORS? Again, the player and the key store folder are both on the same site- both are off of the site root folder. Thanks for any advice you can give me.

    Reply
    1. Simon Post author

      Here’s one idea. You could try including some sort of authentication token in the playlist URL, e.g. /playlist.m3u8?token=ABCDEFG. As the viewer is authenticated, you could tie the token to their identity.

      You would then check on the server if the token is valid or not. If it is valid, you could then set a (session) cookie. Any subsequent requests to the server, e.g. for the key(s), will include the Cookie header that you can then use to determine if the request is allowed or not.

      An alternative approach to using a cookie could be to generate the playlist dynamically and include the token in the path to the encryption key. There’s an example in the book of how to generate a playlist with PHP that you could use as a starting point.

      However, as I mentioned in a comment above, it won’t prevent an authorised user from accessing the key.

      Reply
  6. Zulhilmi Zainudin

    Hi Simon,

    I have 2 questions here:

    1. What do you actually mean by “it won’t prevent an authorised user from accessing the key”? Could you give some examples?

    2. Does HLS works on Android and Windows? I mean, does it work in Android and Windows based web browsers i.e. native browser, Chrome, Firefox and etc?

    Thanks.

    Reply
    1. Simon Post author

      Let’s assume that you run some sort of video website and users can only watch your videos if they are logged in. (How authentication is implemented is not relevant in this example.) If you are authorized to watch a video, you can access the playlist from your browser. If you can view the playlist, you can see the URL of the decryption key. Type that URL into the browser and you can download the key, which means you can decrypt the individual media segments.

      Depends what version of Android you are using. Take a look at the supported media formats, specifically the supported network protocols. I’m not a Windows user but in my experience (on Mac and Linux) HLS works fine in modern versions of desktop browsers like Chrome and Firefox.

      Reply
      1. Hafsa Naz

        So if a user who can watch an encrypted videos, has the access to the URL of the decryption key to decrypt all individual segments, why to encrypt it in the first place? Is there are way to deal with it?

        Reply
  7. ssaguiar2Sergio

    How could we use this with segments, not hls, in ffmpeg?
    I have a problem that the subtitles are in dvbsub format and hls doesn’t work with this format, when using hls but encryption only works with hls.

    Any clues?

    Thank you very much

    Reply
  8. ssaguiar

    Your post is great, but wish to ask a question.
    I already have done it to encrypt the hls stream, as you have told and it works fine with ffmpeg.
    Now, as the key is exposed, I wish to implement the key rotation using ffmpeg.
    I purchased your book today but it shows only how too use mediafilesegmenter and mediastreamsegmenter to make it.

    Is ther any way to make this (key rotation) using ffmpeg?

    Thank you very much for any help

    Reply
    1. Simon Post author

      If -hls_flags periodic_rekey is enabled, the key info file will be checked periodically for changes. If the file has changed, segments will be encrypted with the new key. However, you will have to write a script (or something) to update the file with the new encryption key. I don’t think it’s possible to specify how often the file is checked.

      Reply
  9. Naga Sai

    Hi Simon,

    Thank you for the great post. I am able to encrypt the video and was able to play the videos using video.js.

    when i installed third party video downloader plugins for browsers and downloaded video. It started downloading in mp4 format and it was playing without any keys.

    Can you please tell on how to prevent the video from playing even if downloaded using browser plugins

    Thanks
    Nagasai

    Reply
    1. Simon Post author

      If the plugin thinks the video is MP4, check the MIME type is configured correctly. The Content-Type header for .ts files should be set to video/MP2T.

      You need to implement some form of authorization to restrict access to the key if you haven’t already (see my previous comment). If you don’t do this then anybody can download the key and decrypt the video segments, which I suspect is what is happening in your case.

      Reply
  10. Michael

    Hi Steve,
    great post and easy to understand, thanks.

    Currently I’m running a server where videos are streamed as unencrypted HLS and we have authorization to restrict access to distinct users.
    Now, my customer wants to switch to encrypted hls cause they think that logged in users might use a plugin in their browser to download the video and locally store it as MP4.

    But there are also hls downloaders that claim to be able to download and decrypt encrypted hls videos.
    As far as I understood your article encrypted hls has to send the encryption key to the client in one way or another.
    I think a browser plugin has the same access to the key as the browser itself.

    So my question is: does encrypted hls bring any advantages over non encrypted hls when we have a legitimated user using such a plugin?

    Thanks a lot
    Michael

    Reply
    1. Simon Post author

      You are correct about HLS downloaders. Coincidentally, I used one recently to test an encrypted video stream and it downloaded the video and stored it as an MP4. Interestingly, the one I tried didn’t work with the videos on this site.

      To answer your question, I would say no. If a user is authenticated and therefore has direct access to the key, then presumably the plugin also has the same access. It may be possible to thwart these kinds of tools but I don’t know enough about how they work.

      However, there are legitimate reasons why you would want to encrypt your videos. For example, if you distribute your videos via a CDN. In this scenario, the content is typically distributed globally on servers you have no control over – it depends on the CDN that you use – so it would make sense to encrypt the video segments.

      Reply
  11. ssaguiar

    Hi Simon

    I have implemented the encryption for my ffmpeg stream and it works very well.
    I have a question about the detection of a freezed video.

    I need to restart the ffmpeg process when the image is froozen.

    I am trying a complex filter as:

    -filter_complex “select=’not(mod(n,600))’,select=’lt(scene\,0.06)’,showinfo” \
    -vsync vfr /var/www/html/hls/live/CAM1/movement/scn_%03d.jpg \

    MOVEMENT is the ffmpeg copied and renamed.

    The above script works and it generates the jpeg images.
    The question is: how to use this to kill the ffmpeg instance?

    I apologise if this is not the place to ask such question, but I didn’t find another place.

    Thank you very much for your excelent tutorials and the amazing book.

    Reply
  12. Zoltán Tamási

    Hi,

    Thank you for the great article. What I’m wondering about is whether it’s possible to use a pass-phrase based key derivation. I’d like to implement a simple use-case where my goal is to protect some of my videos using a password. Is there any kind of built-in support for this scenario, or is the key file approach the only way?

    Thank you for your response in advance, regards,
    Zoltán

    Reply
    1. Simon Post author

      As far as I know, the key file is the only way. You could try enabling HTTP Basic/Digest authentication on the URL of the key. This will prompt the user for a password.

      Reply
  13. sol

    Hello! I’m trying to encrypt an hls video, but I don’t know how to create a key info file. So I’m stuck there. Can you explain me how can I do it? Thank you!

    Reply
      1. solcisdm

        Thanks. So I’m using the notepad now, but I’m still doing something wrong…This is my key info file “enc.keyinfo” :

        https:/probandoprueba.com/keys/key.file
        C:\Users\Dmar\Documents\Videos
        ecd0d06eaf884d8226c33928e87efa33

        The key.file, the file “enc.keyinfo” and my .mp4 video are located in the same folder.

        Then, I run this command:
        ffmpeg -y \
        -i pruebavideo.mp4 \
        -hls_time 9 \
        -hls_key_info_file enc.keyinfo
        -hls_playlist_type vod \
        -hls_segment_filename “fileSequence%d.ts” \
        prog_index.m3u8

        But I keep getting the following error:
        error opening key info file enc.keyinfo. Could not write header for output file #0 (incorrect codec parameters ?): No such file or directory. Error initializing output stream 0:1 — [aac @ 0000000002f900c0] Qavg: nan Conversion failed!

        Any ideas? Thank you in advance!

        Reply
        1. Simon Post author

          The second line of your key info file is missing the name of the file that contains the key. It should look like this:

          https:/probandoprueba.com/keys/key.file
          C:\Users\Dmar\Documents\Videos\key.file
          ecd0d06eaf884d8226c33928e87efa33

          As all of your files are in the same directory, you can omit the directory path and just include the name of the file. There’s an example in the post.

          Reply
  14. Mark

    I’m still unable to see the point of this. With the video streaming in the player, users can still download the video (although it will be in parts e.g. ts) using developer tools

    Reply
  15. James Eriksen

    Hi Simon, I have been reading you book but have not found a solution for this issue. with video.js I am getting
    VIDEOJS: – “ERROR:” – “(CODE:3 MEDIA_ERR_DECODE)” – “The media playback was aborted due to a corruption problem or because the media used features your browser did not support.”
    MediaError
    code: 3
    message: “The media playback was aborted due to a corruption problem or because the media used features your browser did not support.”

    But the same playlist and files are playing fine with hls.js I was wondering if you could offer any insight on what may be causing this issue.

    Reply
    1. Simon Post author

      Hi James,

      What version of video.js are you using? How are you encoding your video? If you can share your playlist, that would be helpful.

      There have been other comments elsewhere on the site recently regarding issues with the latest version of video.js, so it could be a bug in the version of the player you are using.

      Reply
  16. Jan

    I found his article few years back. It helped me a lot. There has been a lot discussion about safety measures. You could send incorrect decryption key – > then player would modify key to be correct. Users had no ability to decrypt stream unless they use your player or modify decryption key.

    Reply
  17. Vivek

    Hi Simon,

    Great Post. I was trying to achieve dynamic encryption using key rotation method as suggested in the blog. Although it generates the various key files in the transcoding server as suggested in the bash script. But the encryption of the content is happening via 1st generated key only. There could be below reasons:

    1. -hls_flags ‘Periodic_rekey’ in ffmpeg suggest that the ffmpeg will periodically check the *.keyinfo file for the changes in the key & IV. But, whats the duration of the periodic check? Its no where defined in the documentation, even in hlsenc.c file its not there.

    2. For this periodic_rekey to work, there should be an option of key rotation period, which is not yet accommodated by ffmpeg.

    3. Also, whenever we fork an ffmpeg transcoding process it takes all the value in the hard coded way & only at the time of the initiating the process. Even though the file name remains the same for -hls_key_file_info,

    Reply
    1. Simon Post author

      It works for me. I’m using version 4.3. What ffmpeg command are you running?

      From what I can tell looking at the source code (hlsenc.c), if periodic re-key is enabled, ffmpeg reads the key info file every time it writes a segment.

      You can set the key rotation period by specifying how often the script runs. In the example, I generate a new key every 15 seconds. If you want to rotate the keys every 5 minutes, run the script every 300 seconds, and so on.

      Reply
  18. Sergio

    I am able to encrypt the hls stream but only using AES128.
    My question is: can we use ffmpeg to encrypt an HLS stream with AES256 (or AES194)?
    Thank you.

    Reply
  19. Peter Parker

    I was able to encrypt the stream using methods mentioned in the post, expecting that VLC will have problems playing it.
    VLC is still able to play the encrypted stream. I even downloaded the stream as mp4.

    How can I prevent this from happening? Stream was served over HTTP.

    Reply
  20. holypient

    I’m trying your script to dynamically encrypt the keys but i get syntax error near unexpected token `done’. What does this mean? Im using linux on synology. I tried it on docker but no luck. Why is this error happening no matter what i try?

    Reply
  21. holypient

    I found a way around it by installing dos2unix and convert this script. But is there a way to clean the old segments and old keys? FFmpeg doesn’t allow -hls_flags delete_segments while using -hls_flags periodic_rekey. Is there a code to savely delete the old keys as part of the script? Thank you very much for everything.

    Reply

Leave a Reply