Skip to content

Commit

Permalink
IPTV HLS AES decryption with EVP functions
Browse files Browse the repository at this point in the history
Replace the deprecated AES encryption functions
AES_set_decrypt_key and AES_cbc_encrypt with
the OpenSSL 3.0 EVP functions.
This does fix compiler diagonistic messages
about use of deprecated functions.
No functional changes.

Refs #936
  • Loading branch information
kmdewaal committed Jan 30, 2025
1 parent ed5d7a8 commit a91faea
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 35 deletions.
117 changes: 90 additions & 27 deletions mythtv/libs/libmythtv/recorders/HLS/HLSStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
#include <array>
#include <utility>

#ifdef USING_LIBCRYPTO
#include <openssl/aes.h>
#include <openssl/evp.h>
#endif

#include "libmythbase/mythlogging.h"

#include "HLSReader.h"
#include "HLSStream.h"

#define LOC QString("%1 stream: ").arg(m_m3u8Url)
#define LOC QString("HLSRecstream: ")

HLSRecStream::HLSRecStream(int seq, uint64_t bitrate, QString m3u8_url, QString segment_base_url)
: m_id(seq),
Expand Down Expand Up @@ -37,7 +42,7 @@ QString HLSRecStream::toString(void) const

#ifdef USING_LIBCRYPTO
bool HLSRecStream::DownloadKey(MythSingleDownload& downloader,
const QString& keypath, AES_KEY* aeskey)
const QString& keypath, HLS_AES_KEY* aeskey)
{
QByteArray key;

Expand All @@ -47,7 +52,7 @@ bool HLSRecStream::DownloadKey(MythSingleDownload& downloader,
bool ret = downloader.DownloadURL(keypath, &key);
#endif

if (!ret || key.size() != AES_BLOCK_SIZE)
if (!ret || key.size() != AES128_KEY_SIZE)
{
if (ret)
{
Expand All @@ -62,29 +67,88 @@ bool HLSRecStream::DownloadKey(MythSingleDownload& downloader,
}
return false;
}
AES_set_decrypt_key((const unsigned char*)key.constData(),
128, aeskey);
memcpy(aeskey->key, key.constData(), AES128_KEY_SIZE);

LOG(VB_RECORD, LOG_DEBUG, LOC + "Downloaded AES key");

return true;
}

// AES decryption based on OpenSSL example found on
// https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption
//
int HLSRecStream::Decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
unsigned char *iv, unsigned char *plaintext)
{
EVP_CIPHER_CTX *ctx;

int len = 0;

int plaintext_len = 0;

/* Create and initialise the context */
if(!(ctx = EVP_CIPHER_CTX_new()))
{
LOG(VB_RECORD, LOG_ERR, LOC + "Failed to create and initialize cipher context");
return 0;
}

/*
* Initialise the decryption operation. IMPORTANT - ensure you use a key
* and IV size appropriate for your cipher
* In this example we are using 128 bit AES (i.e. a 128 bit key). The
* IV size for *most* modes is the same as the block size. For AES this
* is 128 bits
*/
if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv))
{
LOG(VB_RECORD, LOG_ERR, LOC + "Failed to initialize decryption operation");
return 0;
}

/*
* Provide the message to be decrypted, and obtain the plaintext output.
* EVP_DecryptUpdate can be called multiple times if necessary.
*/
if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
{
LOG(VB_RECORD, LOG_ERR, LOC + "Failed to decrypt");
return 0;
}
plaintext_len = len;

/*
* Finalise the decryption. Further plaintext bytes may be written at
* this stage.
*/
if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
{
LOG(VB_RECORD, LOG_ERR, LOC + "Failed to finalize decryption" +
QString(" len:%1").arg(len) +
QString(" plaintext_len:%1").arg(plaintext_len) );
return 0;
}
plaintext_len += len;

/* Clean up */
EVP_CIPHER_CTX_free(ctx);

return plaintext_len;
}

bool HLSRecStream::DecodeData(MythSingleDownload& downloader,
const QByteArray& IV, const QString& keypath,
QByteArray& data, int64_t sequence)
{
LOG(VB_RECORD, LOG_DEBUG, QString("HLSRecStream::DecodeData ") +
QString(" %1").arg(!IV.isEmpty() ?
QString(" IV[%1]:%2").arg(IV.size()-1).arg(IV[IV.size()-1]) : QString(" IV isEmpty")) +
LOG(VB_RECORD, LOG_INFO, LOC + "DecodeData:" +
QString(" IV.size():%1").arg(IV.size()) +
QString(" keypath:%1..%2").arg(keypath.left(20), keypath.right(20)) +
QString(" keypath:%1..%2").arg(keypath.left(20),keypath.right(20)) +
QString(" sequence:%1").arg(sequence));

AESKeyMap::iterator Ikey = m_aesKeys.find(keypath);
if (Ikey == m_aesKeys.end())
{
auto* key = new AES_KEY;
auto* key = new HLS_AES_KEY;
DownloadKey(downloader, keypath, key);
Ikey = m_aesKeys.insert(keypath, key);
if (Ikey == m_aesKeys.end())
Expand All @@ -96,7 +160,6 @@ bool HLSRecStream::DecodeData(MythSingleDownload& downloader,
}

/* Decrypt data using AES-128 */
int aeslen = data.size() & ~0xf;
std::array<uint8_t,AES_BLOCK_SIZE> iv {};
auto *decrypted_data = new uint8_t[data.size()];
if (IV.isEmpty())
Expand All @@ -118,28 +181,28 @@ bool HLSRecStream::DecodeData(MythSingleDownload& downloader,
{
std::copy(IV.cbegin(), IV.cend(), iv.data());
}
AES_cbc_encrypt((unsigned char*)data.constData(),
decrypted_data, aeslen,
*Ikey, iv.data(), AES_DECRYPT);
std::copy(data.cbegin() + aeslen, data.cend(), decrypted_data + aeslen);

// remove the PKCS#7 padding from the buffer
int pad = decrypted_data[data.size()-1];
if (pad <= 0 || pad > AES_BLOCK_SIZE)

int aeslen = data.size() & ~0xf;
if (aeslen != data.size())
{
LOG(VB_RECORD, LOG_ERR, LOC +
QString("bad padding character (0x%1)")
.arg(pad, 0, 16, QLatin1Char('0')));
delete[] decrypted_data;
return false;
LOG(VB_RECORD, LOG_WARNING, LOC +
QString("Data size %1 not multiple of 16 bytes, rounding to %2")
.arg(data.size()).arg(aeslen));
}
aeslen = data.size() - pad;
data = QByteArray(reinterpret_cast<char*>(decrypted_data), aeslen);

int plaintext_len = Decrypt((unsigned char*)data.constData(), aeslen, (*Ikey)->key,
iv.data(), decrypted_data);

LOG(VB_RECORD, LOG_INFO, LOC +
QString("Segment data.size()):%1 plaintext_len:%2")
.arg(data.size()).arg(plaintext_len));

data = QByteArray(reinterpret_cast<char*>(decrypted_data), plaintext_len);
delete[] decrypted_data;

return true;
}
#endif // USING_LIBCRYPTO
#endif // USING_LIBCRYPTO

void HLSRecStream::AverageBandwidth(int64_t bandwidth)
{
Expand Down
20 changes: 12 additions & 8 deletions mythtv/libs/libmythtv/recorders/HLS/HLSStream.h
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
#ifndef HLS_STREAM_
#define HLS_STREAM_

#ifdef USING_LIBCRYPTO
// encryption related stuff
#include <openssl/aes.h>
#endif // USING_LIBCRYPTO

#include <QMap>
#include <QQueue>

#include "libmythbase/mythsingledownload.h"
#include "HLSSegment.h"

// 128-bit AES key for HLS segment decryption
#define AES128_KEY_SIZE 16
struct hls_aes_key_st {
unsigned char key[AES128_KEY_SIZE];
};
typedef struct hls_aes_key_st HLS_AES_KEY;

class HLSRecStream
{
friend class HLSReader;

public:
#ifdef USING_LIBCRYPTO
using AESKeyMap = QMap<QString, AES_KEY* >;
#endif
using AESKeyMap = QMap<QString, HLS_AES_KEY* >;
#endif // USING_LIBCRYPTO

HLSRecStream(int seq, uint64_t bitrate, QString m3u8_url, QString segment_base_url);
~HLSRecStream(void);
Expand Down Expand Up @@ -65,8 +67,10 @@ class HLSRecStream

#ifdef USING_LIBCRYPTO
protected:
int Decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
unsigned char *iv, unsigned char *plaintext);
bool DownloadKey(MythSingleDownload& downloader,
const QString& keypath, AES_KEY* aeskey);
const QString& keypath, HLS_AES_KEY* aeskey);
bool DecodeData(MythSingleDownload& downloader,
const QByteArray& IV, const QString& keypath,
QByteArray& data, int64_t sequence);
Expand Down

0 comments on commit a91faea

Please sign in to comment.