[FFmpeg-devel] [PATCH 3/3] avformat/hlsenc: added HLS encryption
Christian Suloway
csuloway at row44.com
Fri Dec 5 00:05:26 CET 2014
HLS encryption with key URL & key containing file specified with
-hls_key_info_file (key info updates when modified). Option for IV from
segment number or random generation (-hls_random_iv).
Signed-off-by: Christian Suloway <csuloway at globaleagleent.com>
---
libavformat/hlsenc.c | 225 +++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 218 insertions(+), 7 deletions(-)
diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c
index 11817a9..c7bd1be 100644
--- a/libavformat/hlsenc.c
+++ b/libavformat/hlsenc.c
@@ -33,16 +33,23 @@
#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"
+#define BLOCKSIZE 16
+
typedef struct HLSSegment {
char *filename;
double duration; /* in seconds */
int64_t pos;
int64_t size;
+ char *key_uri;
+ char *iv_string;
+
struct HLSSegment *next;
} HLSSegment;
@@ -88,12 +95,25 @@ typedef struct HLSContext {
char *segment_filename;
+ char *key_info_file;
+ int random_iv;
+ int encrypt;
+
+ 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->filename);
+ av_freep(&en->key_uri);
+ av_freep(&en->iv_string);
av_freep(&en);
}
@@ -143,6 +163,137 @@ static int hls_delete_old_segments(HLSContext *hls) {
return 0;
}
+static int hls_encryption_init(AVFormatContext *s)
+{
+ HLSContext *hls = s->priv_data;
+
+ hls->key_file = NULL;
+ hls->key_uri = NULL;
+ hls->key_string = NULL;
+ hls->iv_string = NULL;
+
+ if (hls->key_info_file) {
+ hls->encrypt = 1;
+ } else {
+ hls->encrypt = 0;
+ return 0;
+ }
+
+ if (hls->random_iv) {
+ hls->lfg = av_malloc(sizeof(AVLFG));
+ av_lfg_init(hls->lfg, av_get_random_seed());
+ } else
+ hls->lfg = NULL;
+
+ return 0;
+}
+
+static int hls_encryption_start(HLSContext *hls) {
+
+ uint8_t *iv = NULL;
+ int ret, i, j, rotate_iv = 0;
+ unsigned int u;
+ AVIOContext *pb = NULL;
+ AVIOContext *dyn_buf = NULL;
+ uint8_t buf[1024], *tmp;
+ char *p, *tstr, *saveptr = NULL;
+ char key[BLOCKSIZE], *key_string;
+
+ 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);
+ return ret;
+ }
+ ret = avio_open_dyn_buf(&dyn_buf);
+ if (ret < 0) {
+ avio_closep(&pb);
+ return ret;
+ }
+
+ while ((ret = avio_read(pb, buf, sizeof(buf))) > 0)
+ avio_write(dyn_buf, buf, ret);
+ avio_w8(dyn_buf, 0);
+ avio_closep(&pb);
+
+ if ((ret = avio_close_dyn_buf(dyn_buf, &tmp)) < 0)
+ return ret;
+
+ p = tmp;
+ if (!(tstr = av_strtok(p, "\n", &saveptr)) || !*tstr) {
+ av_log(hls, AV_LOG_ERROR, "no key URL specified in key info file %s\n",
+ hls->key_info_file);
+ return AVERROR(EINVAL);
+ }
+ p = NULL;
+ av_free(hls->key_uri);
+ hls->key_uri = av_strdup(tstr);
+ if (!hls->key_uri)
+ return AVERROR(ENOMEM);
+ 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);
+ return AVERROR(EINVAL);
+ }
+ av_free(hls->key_file);
+ hls->key_file = av_strdup(tstr);
+ if (!hls->key_file)
+ return AVERROR(ENOMEM);
+ av_free(tmp);
+
+ 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);
+ return ret;
+ }
+
+ ret = avio_read(pb, key, sizeof(key));
+ avio_closep(&pb);
+ if (ret < sizeof(key)) {
+ av_log(hls, AV_LOG_ERROR, "error reading key file %s\n",
+ hls->key_file);
+ return ret;
+ }
+
+ key_string = av_mallocz(BLOCKSIZE*2 + 1);
+ if (!key_string)
+ return AVERROR(ENOMEM);
+ 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 = key_string;
+ rotate_iv = 1;
+ } else {
+ av_free(key_string);
+ }
+
+ if (!hls->random_iv) {
+ iv = av_mallocz(BLOCKSIZE);
+ if (!iv)
+ return AVERROR(ENOMEM);
+ 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)
+ return AVERROR(ENOMEM);
+ for (i = 0; i < BLOCKSIZE/4; i++) {
+ u = av_lfg_get(hls->lfg);
+ for (j = 0; j < 4; j++)
+ iv[i*4 + j] = (u >> j*8) & 0xff;
+ }
+ }
+ if (iv) {
+ av_free(hls->iv_string);
+ hls->iv_string = av_mallocz(BLOCKSIZE*2 + 1);
+ if (!hls->iv_string)
+ return AVERROR(ENOMEM);
+ ff_data_to_hex(hls->iv_string, iv, BLOCKSIZE, 0);
+ av_free(iv);
+ }
+
+ return 0;
+}
+
static int hls_mux_init(AVFormatContext *s)
{
HLSContext *hls = s->priv_data;
@@ -191,6 +342,18 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos,
en->size = size;
en->next = NULL;
+ if (hls->encrypt) {
+ 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);
+ } else {
+ en->key_uri = NULL;
+ en->iv_string = NULL;
+ }
+
if (!hls->segments)
hls->segments = en;
else
@@ -228,6 +391,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 (hls->random_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;
@@ -236,6 +407,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)
@@ -258,6 +431,16 @@ static int hls_window(AVFormatContext *s, int last)
sequence);
for (en = hls->segments; en; en = en->next) {
+ if (hls->encrypt) {
+ if (!key_uri || av_strcasecmp(en->key_uri, key_uri) ||
+ hls->random_iv &&
+ (!iv_string || 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",
@@ -280,6 +463,8 @@ static int hls_start(AVFormatContext *s)
HLSContext *c = s->priv_data;
AVFormatContext *oc = c->avf;
int err = 0;
+ AVDictionary *options = NULL;
+ const char *prefix = "crypto:";
int filename_size;
char *filename;
@@ -294,17 +479,32 @@ static int hls_start(AVFormatContext *s)
}
c->number++;
- filename_size = strlen(c->dirname) + strlen(oc->filename) + 1;
- filename = av_malloc(filename_size);
- if (!filename)
- return AVERROR(ENOMEM);
- *filename = '\0';
+ if (c->encrypt) {
+ if ((err = hls_encryption_start(c)) < 0)
+ return err;
+ av_dict_set(&options, "encryption_key", c->key_string, 0);
+ av_dict_set(&options, "encryption_iv", c->iv_string, 0);
+
+ filename_size = strlen(prefix) + strlen(c->dirname) \
+ + strlen(oc->filename) + 1;
+ filename = av_malloc(filename_size);
+ if (!filename)
+ return AVERROR(ENOMEM);
+ av_strlcpy(filename, prefix, filename_size);
+ } else {
+ filename_size = strlen(c->dirname) + strlen(oc->filename) + 1;
+ filename = av_malloc(filename_size);
+ if (!filename)
+ return AVERROR(ENOMEM);
+ *filename = '\0';
+ }
av_strlcat(filename, c->dirname, filename_size);
av_strlcat(filename, oc->filename, filename_size);
err = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE,
- &s->interrupt_callback, NULL);
+ &s->interrupt_callback, &options);
av_free(filename);
+ av_dict_free(&options);
if (err < 0)
return err;
@@ -392,6 +592,9 @@ static int hls_write_header(AVFormatContext *s)
*basename = '\0';
+ if ((ret = hls_encryption_init(s)) < 0)
+ goto fail;
+
if ((ret = hls_mux_init(s)) < 0)
goto fail;
@@ -502,9 +705,15 @@ static int hls_write_trailer(struct AVFormatContext *s)
hls->avf = NULL;
hls_window(s, 1);
+ av_free(hls->dirname);
+ av_free(hls->key_file);
+ av_free(hls->key_uri);
+ av_free(hls->key_string);
+ av_free(hls->iv_string);
+ av_free(hls->lfg);
+
hls_free_segments(hls->segments);
hls_free_segments(hls->old_segments);
- av_free(hls->dirname);
avio_close(hls->pb);
@@ -525,6 +734,8 @@ static const AVOption options[] = {
{"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"},
{"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 URL and key file path", OFFSET(key_info_file), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E},
+ {"hls_random_iv", "randomize initialization vector", OFFSET(random_iv), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E},
{ NULL },
};
--
1.9.3 (Apple Git-50)
More information about the ffmpeg-devel
mailing list