Skip to content

MPEG CENC in CBCS mode

Tomasz Kantecki edited this page Nov 18, 2022 · 6 revisions

Using intel-ipsec-mb for MPEG Common Encryption (ISO 23001-7) in “cbcs” mode

Introduction

The intel-ipsec-mb library can be used to accelerate MPEG Common Encryption (CENC) in “cbcs” mode by leverage SIMD instructions to process multiple buffers in parallel. Currently SSE and AVX instruction sets are supported to encrypt / decrypt either 4 or 8 buffers concurrently using the 1:9 crypt:skip pattern.

Related Publications

Multi-buffer AVX-512 Accelerated Parallelization of CBCS Common Encryption Mode Combining crypto enhancements on the latest Intel® Xeon® Processors with optimized software implementations can dramatically accelerate MPEG DRM encryption. This reduces the processing resource requirements of the “packager” and reduces the total cost of ownership for an over-the-top service.

API Overview

AES-CBCS is done through the library’s asynchronous API (“job” API). The application submits multiple jobs to the library to be processed and jobs are returned in order. When the jobs are returned, depends on the underlying implementation and size of the jobs. Please refer to the Fast Multi-buffer IPsec Implementations on Intel® Architecture Processors whitepaper for more information on the job API and multi-buffer processing.

Encryption

The design of AES-CBC encryption specifies that every block to be encrypted has a dependency on the previous block. This means only single blocks can be encrypted at a time. In order to maximize throughput, multi-buffer job scheduling is used to encrypt multiple buffers in parallel. The library will wait until there are enough jobs (buffers to be processed) submitted before beginning encryption. As jobs are submitted, the multi-buffer manager (job scheduler) inserts each job into a “lane”. Until all lanes are populated, the API will return NULL. Once all lanes have been filled, jobs will start to be processed and for every new job submitted, a completed job will be returned. Jobs are returned in the same order that they were submitted. If there are no more jobs to be submitted, existing jobs can be flushed (i.e. forced to completion) from the multi-buffer manager.

Decryption

AES-CBC decryption does not have the same dependency as encryption. For this reason, decryption can be done using a single-buffer implementation to decrypt multiple blocks of a single buffer in parallel. Every job submitted is processed and returned immediately. No job scheduling is required.

API Usage

Below is a sample application to demonstrate how to use of AES-CBCS in 1:9 pattern and also to highlight the order that jobs are submitted and returned. In this example, the SSE implementation is used. This implementation supports processing 4 buffers in parallel for encryption by setting the following job (IMB_JOB structure) fields:

cipher_mode = IMB_CIPHER_CBCS_1_9 
cipher_direction = IMB_DIR_ENCRYPT 
chain_order = IMB_ORDER_CIPHER_HASH

For decryption, set the job fields to:

cipher_mode = IMB_CIPHER_CBCS_1_9
cipher_direction = IMB_DIR_DECRYPT
chain_order = IMB_ORDER_HASH_CIPHER  

Note: For versions after v0.55, the cipher_fields.CBCS.next_iv job field must also be set by the application.
This should point to a 16 byte buffer which is used by the library to store the last ciphertext block. This can then be used as the next IV to preserve context between library calls if needed.

Other IMB_JOB fields of note:
hash_alg set to IMB_AUTH_NULL if no authentication is being performed. In this case the chain_order field does not matter.
msg_len_to_cipher_in_bytes set to the full length of the buffer to be processed.
src pointer to first block to be encrypted/decrypted.
dst pointer to output buffer - CBCS assumes in-place processing (i.e. same in/out buffer)

Sample Application

#include <stdio.h>
#include <stdint.h>
#include <intel-ipsec-mb.h>

int main(int argc, char **argv)
{
    IMB_MGR *p_mgr = NULL;
    IMB_JOB *job = NULL;
    IMB_ARCH arch;
    uint64_t flags = 0;
    uint32_t enc_keys[11*4] __attribute__((aligned(16))); // 16 byte keys * 11 rounds
    uint32_t dec_keys[11*4] __attribute__((aligned(16)));
    uint8_t buf[1024];
    int i, num_jobs = 10, jobs_rx = 0;
    uint8_t aes_key[16] = {0}; // 128b key
    uint8_t iv[16] = {0}; // 128b key
#if IMB_VERSION_NUM > IMB_VERSION(0, 55, 0)
    uint8_t next_iv[16] = {0}; // buffer to store last cipher block                                                                                                                                                                                
                               // to be used as next IV  
                               // unused in this example                                                                                                                                                                                          
#endif

    /* allocate multi-buffer manager */
    p_mgr = alloc_mb_mgr(flags);
    if (p_mgr == NULL)
    {
        printf("Error allocating MB_MGR structure!\n");
        return EXIT_FAILURE;
    }

    /* initialize mb_mgr */
    init_mb_mgr_auto(p_mgr, &arch);
    switch(arch) {
    case IMB_ARCH_NOAESNI:
            printf("Using NOAESNI interface\n");
            break;
    case IMB_ARCH_SSE:
            printf("Using SSE interface\n");
            break;
    case IMB_ARCH_AVX:
            printf("Using AVX interface\n");
            break;
    case IMB_ARCH_AVX2:
            printf("Using AVX2 interface\n");
            break;
    case IMB_ARCH_AVX512:
            printf("Using AVX512 interface\n");
            break;
    default:
            printf("Error: No supported interface detected!\n");
            return EXIT_FAILURE;
    }

    /* expand aes-128 key */
    IMB_AES_KEYEXP_128(p_mgr, aes_key, enc_keys, dec_keys);

    /* flush the scheduler */
    while ((job = IMB_FLUSH_JOB(p_mgr))!= NULL)
        ;

    /* submit jobs */
    printf("Submitting %d jobs...\n", num_jobs);

    for (i = 0; i < num_jobs; i++)
    {
        job = IMB_GET_NEXT_JOB(p_mgr);
        job->cipher_direction = IMB_DIR_ENCRYPT; // or IMB_DIR_DECRYPT
        job->chain_order = IMB_ORDER_CIPHER_HASH; //HASH_CIPHER for DECRYPT
        job->dst = buf;
        job->src = buf;
        job->cipher_mode = IMB_CIPHER_CBCS_1_9;
        job->enc_keys = enc_keys;
        job->dec_keys = dec_keys;
        job->key_len_in_bytes = 16;

        job->iv = iv;
        job->iv_len_in_bytes = 16;
        job->cipher_start_src_offset_in_bytes = 0;
        job->msg_len_to_cipher_in_bytes = sizeof(buf);
        job->hash_alg = IMB_AUTH_NULL;
#if IMB_VERSION_NUM > IMB_VERSION(0, 55, 0)
        job->cipher_fields.CBCS.next_iv = next_iv;
#endif

        printf("Submitting job %i\n", i);
        job = IMB_SUBMIT_JOB(p_mgr);
        if (job == NULL) {
                printf("Recieved NULL\n");
        } else {
                jobs_rx++;
                printf("Recieved job!\n");
        }
    }

    /* flush the scheduler */
    printf("Flushing scheduler...\n");

    while ((job = IMB_FLUSH_JOB(p_mgr)) != NULL) {
            jobs_rx++;
            printf("Got flushed job\n");
    }

    printf("Received %d jobs total!\n", jobs_rx);

    free_mb_mgr(p_mgr);
}

Output

The above application outputs the following:

                       | ENCRYPTION              | DECRYPTION              |
                       |-------------------------|-------------------------|
                       | Using SSE interface     | Using SSE interface     |
                       | Submitting 10 jobs...   | Submitting 10 jobs...   |
                       | Submitting job 0        | Submitting job 0        |
                       | Recieved NULL           | Recieved job!           | <- First job back
                       | Submitting job 1        | Submitting job 1        |
                       | Recieved NULL           | Recieved job!           |
                       | Submitting job 2        | Submitting job 2        |
                       | Recieved NULL           | Recieved job!           |
                       | Submitting job 3        | Submitting job 3        |
     First job back -> | Recieved job!           | Recieved job!           |
                       | Submitting job 4        | Submitting job 4        |
                       | Recieved job!           | Recieved job!           |
                       | Submitting job 5        | Submitting job 5        |
                       | Recieved job!           | Recieved job!           |
                       | Submitting job 6        | Submitting job 6        |
                       | Recieved job!           | Recieved job!           |
                       | Submitting job 7        | Submitting job 7        |
                       | Recieved job!           | Recieved job!           |
                       | Submitting job 8        | Submitting job 8        |
                       | Recieved job!           | Recieved job!           |
                       | Submitting job 9        | Submitting job 9        |
                       | Recieved job!           | Recieved job!           |
                       | Flushing scheduler...   | Flushing scheduler...   |
                       | Got flushed job         | Received 10 jobs total! |
                       | Got flushed job         |                         |
                       | Got flushed job         |                         |
                       | Received 10 jobs total! |                         |