Monday, August 17, 2015

Android - Using Crypto extensions

I had some problems compiling ASM code as part of an Android kernel module.

As part of my system, I need to encrypt/decrypt data inside the kernel and I decided to use AES CBC with 128 bits keys. I am also using a Nexus 9 (arm64 v8), which means that I have access to crypto extensions, including instructions for AES operations :-)

Considering this scenario, I implemented 3 options (of course, I picked the most efficient one at the end):
  1. A "standard" AES CBC implementation in C (the worst performance);
  2. The Linux kernel Crypto lib with crypto extensions set to ON (good performance and very easy to use);
  3. The AES CBC asm implementation from the OpenSSL lib (good performance and it took me extra time to integrate it into my code);
At the end, I took the option #03. Both #02 and #03 have good performance, but #03 is slightly better.

Let's discuss #02 and #03 in this post.

As the very first step, check if your kernel has the proper options enabled for the crypto extension usage. The following ones should be set to "y":

CONFIG_CRYPTO_HW
CONFIG_ARM64_CRYPTO
CONFIG_CRYPTO_AES_ARM64_CE
CONFIG_CRYPTO_AES_ARM64_CE_CCM
CONFIG_CRYPTO_AES_ARM64_CE_BLK

The Crypto lib

The Linux kernel provides the Crypto library, with several cryptography operations, such as AES, including different block/streams ciphers. The following code use AES 128-bits CBC, and a 0-ed IV (don't do that!)

static int pc_aes_encrypt_kernel(const void *key, int key_len,
                                        void *dst, size_t *dst_len,
                                        const void *src, size_t src_len)
{
        struct crypto_blkcipher *blkcipher = NULL;
        char *cipher = "__cbc-aes-ce";
        char iv[AES_BLOCK_SIZE];
        char *charkey = (unsigned char *) key;

        unsigned int ivsize = 0;
        char *scratchpad_in = NULL, *scratchpad_out = NULL; 
        struct scatterlist sg_in, sg_out;
        struct blkcipher_desc desc;
        int ret = -EFAULT;
        int len = ((int)(src_len/16) + 1) * 16;

        memset(iv, 0x00, AES_BLOCK_SIZE); // set the iv to a proper value!!

        blkcipher = crypto_alloc_blkcipher(cipher, 0, 0);
        if (IS_ERR(blkcipher)) {
                PC_LOGV("could not allocate blkcipher handle for %s\n", cipher);
                return -PTR_ERR(blkcipher);
        }

        if (crypto_blkcipher_setkey(blkcipher, charkey, key_len)) {
                PC_LOGV("key could not be set\n");
                ret = -EAGAIN;
                goto out;
        }

        ivsize = crypto_blkcipher_ivsize(blkcipher);
        if (ivsize) {
                if (ivsize != AES_BLOCK_SIZE)
                        PC_LOGV("IV length differs from expected length. It should be : %d\n",ivsize);
                crypto_blkcipher_set_iv(blkcipher, iv, ivsize);
        }

        scratchpad_in = kmalloc(src_len, GFP_KERNEL);
        if (!scratchpad_in) {
                PC_LOGV("could not allocate scratchpad_in for %s\n", cipher);
                goto out;
        }

        scratchpad_out = kmalloc(len, GFP_KERNEL);
        if (!scratchpad_out) {
                PC_LOGV("could not allocate scratchpad_in for %s\n", cipher);
                goto out;
        }

        memcpy(scratchpad_in,src,src_len);

        desc.flags = 0;
        desc.tfm = blkcipher;
        sg_init_one(&sg_in, scratchpad_in, src_len);
        sg_init_one(&sg_out, scratchpad_out, len);

        crypto_blkcipher_encrypt(&desc, &sg_out, &sg_in, src_len);

        // for decryption, use the following
        // crypto_blkcipher_decrypt(&desc, &sg_out, &sg_in, src_len);

        memcpy(dst,scratchpad_out,sg_out.length);

        *dst_len = sg_out.length;

        ret = 0;
        goto out;

out:
        if (blkcipher)
                crypto_free_blkcipher(blkcipher);
        if (scratchpad_out)
                kzfree(scratchpad_out);
        if (scratchpad_in)
                kzfree(scratchpad_in);

        return ret;

}

For decryption, you can use the same code, but use the function crypto_blkcipher_decrypt instead.

Integrating a *.S file as part if your module built process

As said, I used an existing implementation from OpenSSL, which uses the crypto extensions of arm64-v8. I only had to change the "includes" of the asm file. In addition, I included the object file into my Makefile, like this:

pc_module-objs := ...
                  src/pc_utility.o src/asm/aesv8-armx-64.o \
                  src/crypto/aes.o src/crypto/crypto.o


However, I had some problems with different asm files that I was testing. For example, for the OpenSSL library, some of them will not compile if you use GCC 4.8/4.9. The point is that they use a different architectural syntax (Apple) and you'll see several "Error: unknown mnemonic" error messages.

So, you can use LLVM to compile the asm files with the Apple architectural syntax. LLVM is available in the Android NDK. Then, you can copy the *.o files into your code and build your project. The symbols should match as a charm.

The following code shows the usage of the functions defined in the file aesv8-armx-64.S (available into the folder /external/openssl/)

static int pc_aes_encrypt_hw(const void *key, int key_len,
                                        void *dst, size_t *dst_len,
                                        const void *src, size_t src_len)
{
        AES_KEY enc_key;

        unsigned char enc_out[src_len];
        unsigned char iv[AES_BLOCK_SIZE];
        unsigned char *aes_input = (unsigned char *) src;
        unsigned char *aes_key = (unsigned char *) key;

        memset(iv, 0x00, AES_BLOCK_SIZE);

        HWAES_set_encrypt_key(aes_key, key_len*8, &enc_key);
        HWAES_cbc_encrypt(aes_input, enc_out, src_len, &enc_key, iv, AES_ENCRYPT);

        *dst_len = src_len;
        memcpy(dst,&enc_out[0],src_len);

        return 0;
}

static int pc_aes_decrypt_hw(const void *key, int key_len,
                                        void *dst, size_t *dst_len,
                                        const void *src, size_t src_len)
{
        AES_KEY dec_key;

        unsigned char iv[AES_BLOCK_SIZE];
        unsigned char dec_out[src_len];
        unsigned char *aes_input = (unsigned char *) src;
        unsigned char *aes_key = (unsigned char *) key;

        memset(iv, 0x00, AES_BLOCK_SIZE);

        HWAES_set_decrypt_key(aes_key, key_len*8, &dec_key);
        HWAES_cbc_encrypt(aes_input, dec_out, src_len, &dec_key, iv, AES_DECRYPT);

        *dst_len = src_len;
        memcpy(dst,&dec_out[0],src_len);

        return 0;
}


The functions HWAES_set_encrypt_key, HWAES_set_decrypt_key, HWAES_cbc_encrypt and HWAES_cbc_decrypt are implemented in a asm file (aesv8-armx-64.S) and defined in a header file (aes_cbc.h). Again, I used AES 128-bits CBC, with 0-ed IV (don't do that!).