[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