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

Christian Suloway csuloway at row44.com
Mon Dec 22 20:54:17 CET 2014


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>
---
 doc/muxers.texi      |   9 ++
 libavformat/hlsenc.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 238 insertions(+), 6 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..1dba60c 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -37,12 +37,18 @@
 #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;
+    int attribute_iv;
+
     struct HLSSegment *next;
 } HLSSegment;
 
@@ -86,9 +92,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;
+    int attribute_iv;
+
     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 +165,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 +174,134 @@ fail:
     return ret;
 }
 
+static int hls_encryption_start(HLSContext *hls)
+{
+
+    int ret = 0, i;
+    AVIOContext *pb = NULL, *dyn_buf = NULL;
+    uint8_t buf[1024], *tmp = NULL, *key = NULL, *iv = NULL;
+    char *p, *tstr, *saveptr = NULL, *key_string = NULL, *iv_string = NULL;
+
+    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;
+    }
+    av_free(hls->key_uri);
+    hls->key_uri = av_strdup(tstr);
+    if (!hls->key_uri) {
+        ret = AVERROR(ENOMEM);
+        goto fail;
+    }
+
+    if (!(tstr = av_strtok(NULL, "\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 (tstr = av_strtok(NULL, "\n", &saveptr)) {
+        if (ff_hex_to_data(NULL, tstr) != BLOCKSIZE) {
+            av_log(hls, AV_LOG_ERROR, "invalid length for IV\n");
+            ret = AVERROR(EINVAL);
+            goto fail;
+        }
+        iv_string = av_strdup(tstr);
+        if (!iv_string) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        hls->attribute_iv = 1;
+    } else {
+        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;
+        av_free(iv_string);
+        iv_string = av_mallocz(BLOCKSIZE*2 + 1);
+        if (!iv_string) {
+            ret = AVERROR(ENOMEM);
+            goto fail;
+        }
+        ff_data_to_hex(iv_string, iv, BLOCKSIZE, 0);
+        hls->attribute_iv = 0;
+    }
+    av_free(hls->iv_string);
+    hls->iv_string = iv_string;
+
+    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);
+    av_free(hls->key_string);
+    hls->key_string = key_string;
+
+fail:
+    av_free(tmp);
+    av_free(key);
+    av_free(iv);
+
+    return ret;
+}
+
 static int hls_mux_init(AVFormatContext *s)
 {
     HLSContext *hls = s->priv_data;
@@ -200,6 +348,19 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
     en->size     = size;
     en->next     = NULL;
 
+    if (hls->key_info_file) {
+        en->key_uri = av_strdup(hls->key_uri);
+        if (!en->key_uri)
+            return AVERROR(ENOMEM);
+        en->iv_string = av_strdup(hls->iv_string);
+        if (!en->iv_string)
+            return AVERROR(ENOMEM);
+        en->attribute_iv = hls->attribute_iv;
+    } else {
+        en->key_uri = NULL;
+        en->iv_string = NULL;
+    }
+
     if (!hls->segments)
         hls->segments = en;
     else
@@ -217,7 +378,7 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
             if ((ret = hls_delete_old_segments(hls)) < 0)
                 return ret;
         } else
-            av_free(en);
+            hls_free_segment(en);
     } else
         hls->nb_entries++;
 
@@ -233,10 +394,18 @@ static void hls_free_segments(HLSSegment *p)
     while(p) {
         en = p;
         p = p->next;
-        av_free(en);
+        hls_free_segment(en);
     }
 }
 
+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->attribute_iv)
+        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 +414,9 @@ 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;
+    int attribute_iv = 0;
 
     if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE,
                           &s->interrupt_callback, NULL)) < 0)
@@ -267,6 +439,18 @@ static int hls_window(AVFormatContext *s, int last)
            sequence);
 
     for (en = hls->segments; en; en = en->next) {
+        if (hls->key_info_file) {
+            if (!key_uri ||
+                en->attribute_iv != attribute_iv ||
+                av_strcasecmp(en->key_uri, key_uri) ||
+                en->attribute_iv && av_strcasecmp(en->iv_string, iv_string)) {
+                print_encryption_tag(hls, en);
+                key_uri = en->key_uri;
+                iv_string = en->iv_string;
+                attribute_iv = en->attribute_iv;
+            }
+        }
+
         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 +472,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;
     int err = 0;
 
     if (c->flags & HLS_SINGLE_FILE)
@@ -301,9 +489,34 @@ 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(c)) < 0)
+            return err;
+        if ((err = av_dict_set(&options, "encryption_key", c->key_string, 0))
+                < 0)
+            return err;
+        if ((err = av_dict_set(&options, "encryption_iv", c->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);
+        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);
@@ -399,6 +612,10 @@ fail:
         av_freep(&hls->basename);
         if (hls->avf)
             avformat_free_context(hls->avf);
+        av_freep(&hls->key_file);
+        av_freep(&hls->key_uri);
+        av_freep(&hls->key_string);
+        av_freep(&hls->iv_string);
     }
     return ret;
 }
@@ -484,6 +701,11 @@ static int hls_write_trailer(struct AVFormatContext *s)
     hls->avf = NULL;
     hls_window(s, 1);
 
+    av_freep(&hls->key_file);
+    av_freep(&hls->key_uri);
+    av_freep(&hls->key_string);
+    av_freep(&hls->iv_string);
+
     hls_free_segments(hls->segments);
     hls_free_segments(hls->old_segments);
     avio_close(hls->pb);
@@ -501,6 +723,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)



More information about the ffmpeg-devel mailing list