[FFmpeg-cvslog] avformat/hca: add support for decryption

Paul B Mahol git at videolan.org
Fri Sep 22 09:53:46 EEST 2023


ffmpeg | branch: master | Paul B Mahol <onemda at gmail.com> | Thu Sep 21 23:20:20 2023 +0200| [035d187c4d5d96cf6d15237df0c0a20be4e933f1] | committer: Paul B Mahol

avformat/hca: add support for decryption

> http://git.videolan.org/gitweb.cgi/ffmpeg.git/?a=commit;h=035d187c4d5d96cf6d15237df0c0a20be4e933f1
---

 libavcodec/hcadec.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++---
 libavformat/hca.c   |  51 +++++++++++++++++++---
 2 files changed, 159 insertions(+), 12 deletions(-)

diff --git a/libavcodec/hcadec.c b/libavcodec/hcadec.c
index ebc9d8a8cd..6f277afb96 100644
--- a/libavcodec/hcadec.c
+++ b/libavcodec/hcadec.c
@@ -28,6 +28,7 @@
 #include "get_bits.h"
 #include "hca_data.h"
 
+#define HCA_MASK 0x7f7f7f7f
 #define MAX_CHANNELS 16
 
 typedef struct ChannelContext {
@@ -50,8 +51,12 @@ typedef struct HCAContext {
     ChannelContext ch[MAX_CHANNELS];
 
     uint8_t ath[128];
+    uint8_t cipher[256];
+    uint64_t key;
+    uint16_t subkey;
 
     int     ath_type;
+    int     ciph_type;
     unsigned hfr_group_count;
     uint8_t track_count;
     uint8_t channel_config;
@@ -65,6 +70,93 @@ typedef struct HCAContext {
     AVFloatDSPContext *fdsp;
 } HCAContext;
 
+static void cipher_init56_create_table(uint8_t *r, uint8_t key)
+{
+    const int mul = ((key & 1) << 3) | 5;
+    const int add = (key & 0xE) | 1;
+
+    key >>= 4;
+    for (int i = 0; i < 16; i++) {
+        key = (key * mul + add) & 0xF;
+        r[i] = key;
+    }
+}
+
+static void cipher_init56(uint8_t *cipher, uint64_t keycode)
+{
+    uint8_t base[256], base_r[16], base_c[16], kc[8], seed[16];
+
+    /* 56bit keycode encryption (given as a uint64_t number, but upper 8b aren't used) */
+    /* keycode = keycode - 1 */
+    if (keycode != 0)
+        keycode--;
+
+    /* init keycode table */
+    for (int r = 0; r < (8-1); r++) {
+        kc[r] = keycode & 0xFF;
+        keycode = keycode >> 8;
+    }
+
+    /* init seed table */
+    seed[ 0] = kc[1];
+    seed[ 1] = kc[1] ^ kc[6];
+    seed[ 2] = kc[2] ^ kc[3];
+    seed[ 3] = kc[2];
+    seed[ 4] = kc[2] ^ kc[1];
+    seed[ 5] = kc[3] ^ kc[4];
+    seed[ 6] = kc[3];
+    seed[ 7] = kc[3] ^ kc[2];
+    seed[ 8] = kc[4] ^ kc[5];
+    seed[ 9] = kc[4];
+    seed[10] = kc[4] ^ kc[3];
+    seed[11] = kc[5] ^ kc[6];
+    seed[12] = kc[5];
+    seed[13] = kc[5] ^ kc[4];
+    seed[14] = kc[6] ^ kc[1];
+    seed[15] = kc[6];
+
+    /* init base table */
+    cipher_init56_create_table(base_r, kc[0]);
+    for (int r = 0; r < 16; r++) {
+        uint8_t nb;
+        cipher_init56_create_table(base_c, seed[r]);
+        nb = base_r[r] << 4;
+        for (int c = 0; c < 16; c++)
+            base[r*16 + c] = nb | base_c[c]; /* combine nibbles */
+    }
+
+    /* final shuffle table */
+    {
+        unsigned x = 0;
+        unsigned pos = 1;
+
+        for (int i = 0; i < 256; i++) {
+            x = (x + 17) & 0xFF;
+            if (base[x] != 0 && base[x] != 0xFF)
+                cipher[pos++] = base[x];
+        }
+        cipher[0] = 0;
+        cipher[0xFF] = 0xFF;
+    }
+}
+
+static void cipher_init(uint8_t *cipher, int type, uint64_t keycode, uint16_t subkey)
+{
+    switch (type) {
+    case 56:
+        if (keycode) {
+            if (subkey)
+                keycode = keycode * (((uint64_t)subkey<<16u)|((uint16_t)~subkey+2u));
+            cipher_init56(cipher, keycode);
+        }
+        break;
+    case 0:
+        for (int i = 0; i < 256; i++)
+            cipher[i] = i;
+        break;
+    }
+}
+
 static void ath_init1(uint8_t *ath, int sample_rate)
 {
     unsigned int index;
@@ -124,13 +216,13 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
 
     c->ath_type = version >= 0x200 ? 0 : 1;
 
-    if (bytestream2_get_be32u(gb) != MKBETAG('f', 'm', 't', 0))
+    if ((bytestream2_get_be32u(gb) & HCA_MASK) != MKBETAG('f', 'm', 't', 0))
         return AVERROR_INVALIDDATA;
     bytestream2_skipu(gb, 4);
     bytestream2_skipu(gb, 4);
     bytestream2_skipu(gb, 4);
 
-    chunk = bytestream2_get_be32u(gb);
+    chunk = bytestream2_get_be32u(gb) & HCA_MASK;
     if (chunk == MKBETAG('c', 'o', 'm', 'p')) {
         bytestream2_skipu(gb, 2);
         bytestream2_skipu(gb, 1);
@@ -160,7 +252,7 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
         return AVERROR_INVALIDDATA;
 
     while (bytestream2_get_bytes_left(gb) >= 4) {
-        chunk = bytestream2_get_be32u(gb);
+        chunk = bytestream2_get_be32u(gb) & HCA_MASK;
         if (chunk == MKBETAG('v', 'b', 'r', 0)) {
             bytestream2_skip(gb, 2 + 2);
         } else if (chunk == MKBETAG('a', 't', 'h', 0)) {
@@ -170,7 +262,7 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
         } else if (chunk == MKBETAG('c', 'o', 'm', 'm')) {
             bytestream2_skip(gb, bytestream2_get_byte(gb) * 8);
         } else if (chunk == MKBETAG('c', 'i', 'p', 'h')) {
-            bytestream2_skip(gb, 2);
+            c->ciph_type = bytestream2_get_be16(gb);
         } else if (chunk == MKBETAG('l', 'o', 'o', 'p')) {
             bytestream2_skip(gb, 4 + 4 + 2 + 2);
         } else if (chunk == MKBETAG('p', 'a', 'd', 0)) {
@@ -180,6 +272,14 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
         }
     }
 
+    if (bytestream2_get_bytes_left(gb) >= 10) {
+        bytestream2_skip(gb, bytestream2_get_bytes_left(gb) - 10);
+        c->key = bytestream2_get_be64u(gb);
+        c->subkey = bytestream2_get_be16u(gb);
+    }
+
+    cipher_init(c->cipher, c->ciph_type, c->key, c->subkey);
+
     ret = ath_init(c->ath, c->ath_type, avctx->sample_rate);
     if (ret < 0)
         return ret;
@@ -420,7 +520,7 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
         return AVERROR_INVALIDDATA;
 
     if (AV_RN16(avpkt->data) != 0xFFFF) {
-        if (AV_RL32(avpkt->data) != MKTAG('H','C','A',0)) {
+        if ((AV_RL32(avpkt->data)) != MKTAG('H','C','A',0)) {
             return AVERROR_INVALIDDATA;
         } else if (AV_RB16(avpkt->data + 6) <= avpkt->size) {
             ret = init_hca(avctx, avpkt->data, AV_RB16(avpkt->data + 6));
@@ -434,6 +534,16 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
         }
     }
 
+    if (c->key || c->subkey) {
+        uint8_t *data, *cipher = c->cipher;
+
+        if ((ret = av_packet_make_writable(avpkt)) < 0)
+            return ret;
+        data = avpkt->data;
+        for (int n = 0; n < avpkt->size; n++)
+            data[n] = cipher[data[n]];
+    }
+
     if (avctx->err_recognition & AV_EF_CRCCHECK) {
         if (av_crc(c->crc_table, 0, avpkt->data + offset, avpkt->size - offset))
             return AVERROR_INVALIDDATA;
diff --git a/libavformat/hca.c b/libavformat/hca.c
index 74ac00acc0..e796512a62 100644
--- a/libavformat/hca.c
+++ b/libavformat/hca.c
@@ -19,18 +19,28 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include "libavutil/opt.h"
 #include "libavutil/intreadwrite.h"
 #include "libavcodec/bytestream.h"
 
 #include "avformat.h"
 #include "internal.h"
 
+#define HCA_MASK 0x7f7f7f7f
+
+typedef struct HCADemuxContext {
+    AVClass *class;
+    int64_t keyl;
+    int64_t keyh;
+    int subkey;
+} HCADemuxContext;
+
 static int hca_probe(const AVProbeData *p)
 {
-    if (AV_RL32(p->buf) != MKTAG('H', 'C', 'A', 0))
+    if ((AV_RL32(p->buf) & HCA_MASK) != MKTAG('H', 'C', 'A', 0))
         return 0;
 
-    if (AV_RL32(p->buf + 8) != MKTAG('f', 'm', 't', 0))
+    if ((AV_RL32(p->buf + 8) & HCA_MASK) != MKTAG('f', 'm', 't', 0))
         return 0;
 
     return AVPROBE_SCORE_MAX / 3;
@@ -38,6 +48,7 @@ static int hca_probe(const AVProbeData *p)
 
 static int hca_read_header(AVFormatContext *s)
 {
+    HCADemuxContext *hca = s->priv_data;
     AVCodecParameters *par;
     GetByteContext gb;
     AVIOContext *pb = s->pb;
@@ -60,20 +71,23 @@ static int hca_read_header(AVFormatContext *s)
         return AVERROR(ENOMEM);
 
     par = st->codecpar;
-    ret = ff_alloc_extradata(par, data_offset);
+    ret = ff_alloc_extradata(par, data_offset + 10);
     if (ret < 0)
         return ret;
 
-    ret = avio_read(pb, par->extradata + 8, par->extradata_size - 8);
-    if (ret < par->extradata_size - 8)
+    ret = avio_read(pb, par->extradata + 8, par->extradata_size - 8 - 10);
+    if (ret < par->extradata_size - 8 - 10)
         return AVERROR(EIO);
     AV_WL32(par->extradata, MKTAG('H', 'C', 'A', 0));
     AV_WB16(par->extradata + 4, version);
     AV_WB16(par->extradata + 6, data_offset);
+    AV_WB32(par->extradata + par->extradata_size - 10, hca->keyh);
+    AV_WB32(par->extradata + par->extradata_size -  6, hca->keyl);
+    AV_WB16(par->extradata + par->extradata_size -  2, hca->subkey);
 
     bytestream2_init(&gb, par->extradata + 8, par->extradata_size - 8);
 
-    if (bytestream2_get_le32(&gb) != MKTAG('f', 'm', 't', 0))
+    if ((bytestream2_get_le32(&gb) & HCA_MASK) != MKTAG('f', 'm', 't', 0))
         return AVERROR_INVALIDDATA;
 
     par->codec_type  = AVMEDIA_TYPE_AUDIO;
@@ -83,7 +97,7 @@ static int hca_read_header(AVFormatContext *s)
     par->sample_rate = bytestream2_get_be24(&gb);
     block_count      = bytestream2_get_be32(&gb);
     bytestream2_skip(&gb, 4);
-    chunk = bytestream2_get_le32(&gb);
+    chunk = bytestream2_get_le32(&gb) & HCA_MASK;
     if (chunk == MKTAG('c', 'o', 'm', 'p')) {
         block_size = bytestream2_get_be16(&gb);
     } else if (chunk == MKTAG('d', 'e', 'c', 0)) {
@@ -113,9 +127,32 @@ static int hca_read_packet(AVFormatContext *s, AVPacket *pkt)
     return ret;
 }
 
+#define OFFSET(x) offsetof(HCADemuxContext, x)
+static const AVOption hca_options[] = {
+    { "hca_lowkey",
+        "Low key used for handling CRI HCA files", OFFSET(keyl),
+        AV_OPT_TYPE_INT64, {.i64=0}, .min = 0, .max = UINT32_MAX, .flags = AV_OPT_FLAG_DECODING_PARAM, },
+    { "hca_highkey",
+        "High key used for handling CRI HCA files", OFFSET(keyh),
+        AV_OPT_TYPE_INT64, {.i64=0}, .min = 0, .max = UINT32_MAX, .flags = AV_OPT_FLAG_DECODING_PARAM, },
+    { "hca_subkey",
+        "Subkey used for handling CRI HCA files", OFFSET(subkey),
+        AV_OPT_TYPE_INT, {.i64=0}, .min = 0, .max = UINT16_MAX, .flags = AV_OPT_FLAG_DECODING_PARAM },
+    { NULL },
+};
+
+static const AVClass hca_class = {
+    .class_name = "hca",
+    .item_name  = av_default_item_name,
+    .option     = hca_options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
 const AVInputFormat ff_hca_demuxer = {
     .name           = "hca",
     .long_name      = NULL_IF_CONFIG_SMALL("CRI HCA"),
+    .priv_class     = &hca_class,
+    .priv_data_size = sizeof(HCADemuxContext),
     .read_probe     = hca_probe,
     .read_header    = hca_read_header,
     .read_packet    = hca_read_packet,



More information about the ffmpeg-cvslog mailing list