[FFmpeg-devel] [PATCH] avformat/hlsenc: added HLS encryption

Michael Niedermayer michaelni at gmx.at
Sat Dec 13 04:57:17 CET 2014


On Thu, Dec 11, 2014 at 12:27:37PM -0600, Christian Suloway wrote:
> Added HLS encryption with -hls_key_info_file <key_info_file> option. The
> first line of key_info_file specifies the key URI for the playlist, and
> the second line specifies the path to the file containing the encryption
> key. Changes to key_info_file will be reflected in segment encryption
> along with an entry in the playlist for the new key URI.
> 
> Also added -hls_flags random_iv option flag to use a random IV for
> encryption instead of the segment number.
> 
> Signed-off-by: Christian Suloway <csuloway at globaleagleent.com>
> ---
>  doc/muxers.texi      |  11 +++
>  libavformat/hlsenc.c | 257 +++++++++++++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 262 insertions(+), 6 deletions(-)
> 
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index a1264d2..29a5de3 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -263,6 +263,13 @@ ffmpeg in.nut -hls_segment_filename 'file%03d.ts' out.m3u8
>  This example will produce the playlist, @file{out.m3u8}, and segment files:
>  @file{file000.ts}, @file{file001.ts}, @file{file002.ts}, etc.
>  
> + at item hls_key_info_file @var{file}
> +Use in the information in @var{file} for segment encryption. The first line of
> + at var{file} specifies the key URI for the playlist. The second line specifies
> +the path to the file containing the encryption key as a single packed array of
> +16 octets in binary format. Changes to @var{file} will result in segment
> +encryption with the new key and an entry in the playlist for the new key URI.
> +
>  @item hls_flags single_file
>  If this flag is set, the muxer will store all segments in a single MPEG-TS
>  file, and will use byte ranges in the playlist. HLS playlists generated with
> @@ -277,6 +284,10 @@ Will produce the playlist, @file{out.m3u8}, and a single segment file,
>  @item hls_flags delete_segments
>  Segment files removed from the playlist are deleted after a period of time
>  equal to the duration of the segment plus the duration of the playlist.
> +
> + at item hls_flags random_iv
> +Segment file encryption will use a random initialization vector (IV) instead of
> +the segment number.
>  @end table
>  
>  @anchor{ico}
> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
> index 79f3a23..5bde70e 100644
> --- a/libavformat/hlsenc.c
> +++ b/libavformat/hlsenc.c
> @@ -32,17 +32,24 @@
>  #include "libavutil/avstring.h"
>  #include "libavutil/opt.h"
>  #include "libavutil/log.h"
> +#include "libavutil/lfg.h"
> +#include "libavutil/random_seed.h"
>  
>  #include "avformat.h"
>  #include "internal.h"
>  #include "os_support.h"
>  
> +#define BLOCKSIZE 16
> +
>  typedef struct HLSSegment {
>      char filename[1024];
>      double duration; /* in seconds */
>      int64_t pos;
>      int64_t size;
>  
> +    char *key_uri;
> +    char *iv_string;
> +
>      struct HLSSegment *next;
>  } HLSSegment;
>  
> @@ -50,6 +57,7 @@ typedef enum HLSFlags {
>      // Generate a single media file and use byte ranges in the playlist.
>      HLS_SINGLE_FILE = (1 << 0),
>      HLS_DELETE_SEGMENTS = (1 << 1),
> +    HLS_RANDOM_IV = (1 << 2),
>  } HLSFlags;
>  
>  typedef struct HLSContext {
> @@ -86,9 +94,23 @@ typedef struct HLSContext {
>      char *format_options_str;
>      AVDictionary *format_options;
>  
> +    char *key_info_file;
> +    char *key_file;
> +    char *key_uri;
> +    char *key_string;
> +    char *iv_string;
> +    AVLFG *lfg;
> +
>      AVIOContext *pb;
>  } HLSContext;
>  
> +static void hls_free_segment(HLSSegment *en)
> +{
> +   av_freep(&en->key_uri);
> +   av_freep(&en->iv_string);
> +   av_freep(&en);
> +}
> +
>  static int hls_delete_old_segments(HLSContext *hls) {
>  
>      HLSSegment *segment, *previous_segment = NULL;
> @@ -145,7 +167,7 @@ static int hls_delete_old_segments(HLSContext *hls) {
>          av_free(path);
>          previous_segment = segment;
>          segment = previous_segment->next;
> -        av_free(previous_segment);
> +        hls_free_segment(previous_segment);
>      }
>  
>  fail:
> @@ -154,6 +176,157 @@ fail:
>      return ret;
>  }
>  
> +static int hls_encryption_init(AVFormatContext *s)
> +{
> +    HLSContext *hls = s->priv_data;
> +
> +    if (hls->flags & HLS_RANDOM_IV) {
> +        hls->lfg = av_malloc(sizeof(AVLFG));
> +        if (!hls->lfg)
> +            return AVERROR(ENOMEM);
> +        av_lfg_init(hls->lfg, av_get_random_seed());
> +    }
> +
> +    return 0;
> +}
> +
> +static int hls_encryption_start(HLSContext *hls)
> +{
> +
> +    int ret = 0, i, j, rotate_iv = 0;
> +    AVIOContext *pb = NULL;
> +    AVIOContext *dyn_buf = NULL;
> +    uint8_t buf[1024], *tmp = NULL, *key = NULL, *iv = NULL;
> +    char *p, *tstr, *saveptr = NULL, *key_string = NULL;
> +    unsigned int u;
> +
> +    if ((ret = avio_open(&pb, hls->key_info_file, AVIO_FLAG_READ)) < 0) {
> +        av_log(hls, AV_LOG_ERROR, "error opening key info file %s\n",
> +                                  hls->key_info_file);
> +        goto fail;
> +    }
> +
> +    ret = avio_open_dyn_buf(&dyn_buf);
> +    if (ret < 0) {
> +        avio_closep(&pb);
> +        goto fail;
> +    }
> +
> +    while ((ret = avio_read(pb, buf, sizeof(buf))) > 0)
> +        avio_write(dyn_buf, buf, ret);
> +    avio_closep(&pb);
> +    if (ret != AVERROR_EOF && ret < 0) {
> +        avio_close_dyn_buf(dyn_buf, &tmp);
> +        goto fail;
> +    }
> +
> +    avio_w8(dyn_buf, 0);
> +    if ((ret = avio_close_dyn_buf(dyn_buf, &tmp)) < 0)
> +        goto fail;
> +
> +    p = tmp;
> +    if (!(tstr = av_strtok(p, "\n", &saveptr)) || !*tstr) {
> +        av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file %s\n",
> +                                  hls->key_info_file);
> +        ret = AVERROR(EINVAL);
> +        goto fail;
> +    }
> +    p = NULL;
> +    av_free(hls->key_uri);
> +    hls->key_uri = av_strdup(tstr);
> +    if (!hls->key_uri) {
> +        ret = AVERROR(ENOMEM);
> +        goto fail;
> +    }
> +    if (!(tstr = av_strtok(p, "\n", &saveptr)) || !*tstr) {
> +        av_log(hls, AV_LOG_ERROR, "no key file specified in key info file %s\n",
> +                                  hls->key_info_file);
> +        ret = AVERROR(EINVAL);
> +        goto fail;
> +    }
> +    av_free(hls->key_file);
> +    hls->key_file = av_strdup(tstr);
> +    if (!hls->key_file) {
> +        ret = AVERROR(ENOMEM);
> +        goto fail;
> +    }
> +
> +    if ((ret = avio_open(&pb, hls->key_file, AVIO_FLAG_READ)) < 0) {
> +        av_log(hls, AV_LOG_ERROR, "error opening key file %s\n",
> +                                  hls->key_file);
> +        goto fail;
> +    }
> +
> +    key = av_malloc(BLOCKSIZE);
> +    if (!key) {
> +        ret = AVERROR(ENOMEM);
> +        goto fail;
> +    }
> +    ret = avio_read(pb, key, BLOCKSIZE);
> +    avio_closep(&pb);
> +    if (ret != BLOCKSIZE) {
> +        av_log(hls, AV_LOG_ERROR, "error reading key file %s\n",
> +                                  hls->key_file);
> +        if (ret >= 0 || ret == AVERROR_EOF)
> +            ret = AVERROR(EINVAL);
> +        goto fail;
> +    }
> +
> +    key_string = av_mallocz(BLOCKSIZE*2 + 1);
> +    if (!key_string) {
> +        ret = AVERROR(ENOMEM);
> +        goto fail;
> +    }
> +    ff_data_to_hex(key_string, key, BLOCKSIZE, 0);
> +    if (!hls->key_string || strncmp(key_string, hls->key_string, BLOCKSIZE*2)) {
> +        av_free(hls->key_string);
> +        hls->key_string = av_strdup(key_string);
> +        if (!hls->key_string) {
> +            ret = AVERROR(ENOMEM);
> +            goto fail;
> +        }
> +        rotate_iv = 1;
> +    }
> +
> +    if (!(hls->flags & HLS_RANDOM_IV)) {
> +        iv = av_mallocz(BLOCKSIZE);
> +        if (!iv) {
> +            ret = AVERROR(ENOMEM);
> +            goto fail;
> +        }
> +        for (i = 0; i < 8; i++)
> +            iv[BLOCKSIZE - 1 - i ] = (hls->sequence >> i*8) & 0xff;
> +    } else if (!hls->iv_string || rotate_iv) {
> +        iv = av_malloc(BLOCKSIZE);
> +        if (!iv) {
> +            ret = AVERROR(ENOMEM);
> +            goto fail;
> +        }

> +        for (i = 0; i < BLOCKSIZE >> 2; i++) {
> +            u = av_lfg_get(hls->lfg);
> +            for (j = 0; j < 4; j++)
> +                iv[i*4 + j] = (u >> j*8) & 0xff;
> +        }

combining the random seed with a LFG seems a bit odd
i would out of principle use something stronger, though i dont know
what the exact scenarios are this is intended to prevent, so maybe
its fine


[...]
-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

It is dangerous to be right in matters on which the established authorities
are wrong. -- Voltaire
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 181 bytes
Desc: Digital signature
URL: <https://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20141213/9b9b5594/attachment.asc>


More information about the ffmpeg-devel mailing list