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

Michael Niedermayer michaelni at gmx.at
Wed Jan 7 21:17:15 CET 2015


On Wed, Jan 07, 2015 at 08:59:11AM -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. The
> second line specifies the path to the file containing the encryption
> key. An optional third line specifies an IV to use instead of the
> segment number. Changes to key_info_file will be reflected in segment
> encryption along with an entry in the playlist for the new key URI and
> IV.
> 
> Signed-off-by: Christian Suloway <csuloway at globaleagleent.com>

Please add a testcase/example to either the documentation or
commit message


> ---
>  doc/muxers.texi      |   9 ++++
>  libavformat/hlsenc.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 136 insertions(+), 3 deletions(-)
> 
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index a1264d2..f2ecf8b 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -263,6 +263,15 @@ 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. The optional third line specifies a hexidecimal
> +string for the initialization vector (IV) to be used instead of the segment
> +number. Changes to @var{file} will result in segment encryption with the new
> +key/IV and an entry in the playlist for the new key URI/IV.
> +
>  @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
> diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
> index f46e8d4..7f2bc96 100644
> --- a/libavformat/hlsenc.c
> +++ b/libavformat/hlsenc.c
> @@ -37,12 +37,18 @@
>  #include "internal.h"
>  #include "os_support.h"
>  
> +#define BLOCKSIZE 16
> +#define LINE_BUFFER_SIZE 1024
> +
>  typedef struct HLSSegment {
>      char filename[1024];
>      double duration; /* in seconds */
>      int64_t pos;
>      int64_t size;
>  
> +    char key_uri[LINE_BUFFER_SIZE + 1];
> +    char iv_string[BLOCKSIZE*2 + 1];
> +
>      struct HLSSegment *next;
>  } HLSSegment;
>  
> @@ -86,6 +92,12 @@ typedef struct HLSContext {
>      char *format_options_str;
>      AVDictionary *format_options;
>  
> +    char *key_info_file;
> +    char key_file[LINE_BUFFER_SIZE + 1];
> +    char key_uri[LINE_BUFFER_SIZE + 1];
> +    char key_string[BLOCKSIZE*2 + 1];
> +    char iv_string[BLOCKSIZE*2 + 1];
> +
>      AVIOContext *pb;
>  } HLSContext;
>  
> @@ -154,6 +166,62 @@ fail:
>      return ret;
>  }
>  
> +static int hls_encryption_start(AVFormatContext *s)
> +{
> +    HLSContext *hls = s->priv_data;
> +    int ret;
> +    AVIOContext *pb;
> +    uint8_t key[BLOCKSIZE];
> +
> +    if ((ret = avio_open2(&pb, hls->key_info_file, AVIO_FLAG_READ,
> +                           &s->interrupt_callback, NULL)) < 0) {
> +        av_log(hls, AV_LOG_ERROR,
> +                "error opening key info file %s\n", hls->key_info_file);
> +        return ret;
> +    }
> +

> +    ff_get_line(pb, hls->key_uri, sizeof(hls->key_uri));
> +    hls->key_uri[strcspn(hls->key_uri, "\r\n")] = '\0';
> +
> +    ff_get_line(pb, hls->key_file, sizeof(hls->key_file));
> +    hls->key_file[strcspn(hls->key_file, "\r\n")] = '\0';
> +
> +    ff_get_line(pb, hls->iv_string, sizeof(hls->iv_string));
> +    hls->iv_string[strcspn(hls->iv_string, "\r\n")] = '\0';

in what case are the strcspn() needed ?


> +
> +    avio_close(pb);
> +
> +    if (!*hls->key_uri) {
> +        av_log(hls, AV_LOG_ERROR, "no key URI specified in key info file\n");
> +        return AVERROR(EINVAL);
> +    }
> +    av_log(hls, AV_LOG_DEBUG, "key URI = %s\n", hls->key_uri);
> +
> +    if (!*hls->key_file) {
> +        av_log(hls, AV_LOG_ERROR, "no key file specified in key info file\n");
> +        return AVERROR(EINVAL);
> +    }
> +    av_log(hls, AV_LOG_DEBUG, "key file = %s\n", hls->key_file);
> +
> +    if ((ret = avio_open2(&pb, hls->key_file, AVIO_FLAG_READ,
> +                           &s->interrupt_callback, NULL)) < 0) {
> +        av_log(hls, AV_LOG_ERROR, "error opening key file %s\n", hls->key_file);
> +        return ret;
> +    }
> +
> +    ret = avio_read(pb, key, sizeof(key));
> +    avio_close(pb);
> +    if (ret != sizeof(key)) {
> +        av_log(hls, AV_LOG_ERROR, "error reading key file %s\n", hls->key_file);
> +        if (ret >= 0 || ret == AVERROR_EOF)
> +            ret = AVERROR(EINVAL);
> +        return ret;
> +    }
> +    ff_data_to_hex(hls->key_string, key, sizeof(key), 0);
> +
> +    return 0;
> +}
> +
>  static int hls_mux_init(AVFormatContext *s)
>  {
>      HLSContext *hls = s->priv_data;
> @@ -200,6 +268,11 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
>      en->size     = size;
>      en->next     = NULL;
>  
> +    if (hls->key_info_file) {
> +        av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri));
> +        av_strlcpy(en->iv_string, hls->iv_string, sizeof(en->iv_string));
> +    }
> +
>      if (!hls->segments)
>          hls->segments = en;
>      else
> @@ -237,6 +310,14 @@ static void hls_free_segments(HLSSegment *p)
>      }
>  }
>  
> +static void print_encryption_tag(HLSContext *hls, HLSSegment *en)
> +{
> +    avio_printf(hls->pb, "#EXT-X-KEY:METHOD=AES-128,URI=\"%s\"", en->key_uri);
> +    if (*en->iv_string)
> +        avio_printf(hls->pb, ",IV=0x%s", en->iv_string);
> +    avio_printf(hls->pb, "\n");
> +}
> +
>  static int hls_window(AVFormatContext *s, int last)
>  {
>      HLSContext *hls = s->priv_data;
> @@ -245,6 +326,8 @@ static int hls_window(AVFormatContext *s, int last)
>      int ret = 0;
>      int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries);
>      int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3;
> +    char *key_uri = NULL;
> +    char *iv_string = NULL;
>  
>      if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE,
>                            &s->interrupt_callback, NULL)) < 0)
> @@ -267,6 +350,13 @@ static int hls_window(AVFormatContext *s, int last)
>             sequence);
>  
>      for (en = hls->segments; en; en = en->next) {
> +        if (hls->key_info_file && (!key_uri || strcmp(en->key_uri, key_uri) ||
> +                                    av_strcasecmp(en->iv_string, iv_string))) {
> +            print_encryption_tag(hls, en);
> +            key_uri = en->key_uri;
> +            iv_string = en->iv_string;
> +        }
> +
>          avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration);
>          if (hls->flags & HLS_SINGLE_FILE)
>               avio_printf(hls->pb, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n",
> @@ -288,6 +378,10 @@ static int hls_start(AVFormatContext *s)
>  {
>      HLSContext *c = s->priv_data;
>      AVFormatContext *oc = c->avf;
> +    AVDictionary *options = NULL;
> +    const char *prefix = "crypto:";
> +    int filename_size;
> +    char *filename, iv_string[BLOCKSIZE*2 + 1];
>      int err = 0;
>  
>      if (c->flags & HLS_SINGLE_FILE)
> @@ -301,9 +395,38 @@ static int hls_start(AVFormatContext *s)
>          }
>      c->number++;
>  
> -    if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
> -                          &s->interrupt_callback, NULL)) < 0)
> -        return err;
> +    if (c->key_info_file) {
> +        if ((err = hls_encryption_start(s)) < 0)
> +            return err;
> +        av_log(c, AV_LOG_DEBUG, "key = 0x%s\n", c->key_string);
> +        if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0))
> +                < 0)
> +            return err;
> +        err = av_strlcpy(iv_string, c->iv_string, sizeof(iv_string));

> +        if (!err)
> +            snprintf(iv_string, sizeof(iv_string), "%032llx", c->sequence);

libavformat/hlsenc.c:407:13: warning: format ‘%llx’ expects argument of type ‘long long unsigned int’, but argument 4 has type ‘int64_t’ [-Wformat]
libavformat/hlsenc.c:407:13: warning: format ‘%llx’ expects argument of type ‘long long unsigned int’, but argument 4 has type ‘int64_t’ [-Wformat]



> +        av_log(c, AV_LOG_DEBUG, "IV = 0x%s\n", iv_string);
> +        if ((err = av_dict_set(&options, "encryption_iv", iv_string, 0)) < 0)
> +            return err;
> +

> +        filename_size = strlen(prefix) + strlen(oc->filename) + 1;
> +        filename = av_malloc(filename_size);
> +        if (!filename) {
> +            av_dict_free(&options);
> +            return AVERROR(ENOMEM);
> +        }
> +        av_strlcpy(filename, prefix, filename_size);
> +        av_strlcat(filename, oc->filename, filename_size);

this looks like it can be simplified with av_asprintf()


> +        err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,
> +                         &s->interrupt_callback, &options);
> +        av_free(filename);
> +        av_dict_free(&options);
> +        if (err < 0)
> +            return err;
> +    } else
> +        if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
> +                              &s->interrupt_callback, NULL)) < 0)
> +            return err;
>  
>      if (oc->oformat->priv_class && oc->priv_data)
>          av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
> @@ -501,6 +624,7 @@ static const AVOption options[] = {
>      {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E},
>      {"hls_base_url",  "url to prepend to each playlist entry",   OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E},
>      {"hls_segment_filename", "filename template for segment files", OFFSET(segment_filename),   AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
> +    {"hls_key_info_file",    "file with key URI and key file path", OFFSET(key_info_file),      AV_OPT_TYPE_STRING, {.str = NULL},            0,       0,         E},
>      {"hls_flags",     "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"},
>      {"single_file",   "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX,   E, "flags"},
>      {"delete_segments", "delete segment files that are no longer part of the playlist", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0, UINT_MAX,   E, "flags"},
> -- 
> 1.9.3 (Apple Git-50)
> 
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> 

-- 
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

Everything should be made as simple as possible, but not simpler.
-- Albert Einstein
-------------- 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/20150107/56bf31cc/attachment.asc>


More information about the ffmpeg-devel mailing list