Does ARM and Intel intrinsics use the same subkey schedule for AES?

Viewed 557

I'm trying to cut-in an AES implementation on ARMv8 using intrinsics. I have a C++ implementation, and I have an Intel intrinsics implementation.

The implementations are supposed to be equivalent, so I am trying to use Intel as a blueprint for ARMv8. There are some differences, but they are accounted for. The problem is, I'm getting different results.

void AES_encrypt(const Byte *in, Byte *out, const RoundKey *rdkeys, unsigned int rounds)
{
#if defined(__ARM_FEATURE_CRYPTO)

    uint8x16_t data = vld1q_u8(in);

    // AES encryption with ARM intrinsics:
    // rnds-1 (9 for AES128) cycles of AES:
    // (Add, Shift, Sub) plus Mix Columns
    unsigned int i;
    for (i=0; i<rounds; ++i)
    {
        // AES single round encryption
        data = vaeseq_u8(data, rdkeys[i]);
        // AES mix columns
        data = vaesmcq_u8(data);
    }
    // One round of encryption: AES, no Mix Columns
    data = vaeseq_u8(data, rdkeys[i++]);
    // Final Add (bitwise Xor)
    data = veorq_u8(data, rdkeys[i]);
    vst1q_u8(out, data);

#elif defined(__AES__)

    __m128i data = _mm_loadu_si128((const __m128i*)in);
    data = _mm_xor_si128(data, rdkeys[0]);
    for (unsigned int i=1; i<rounds-1; ++i)
    {
        data = _mm_aesenc_si128(data, rdkeys[i]);
    }
    data = _mm_aesenc_si128(data, rdkeys[rounds-1]);
    data = _mm_aesenclast_si128(data, rdkeys[rounds]);
    _mm_storeu_si128((__m128i*)out, data);

#endif
}

At this point, I am trying to side step subkey calculation. I use the same set of round keys for both implementations:

#if defined(__ARM_FEATURE_CRYPTO)
typedef uint8x16_t RoundKey;
typedef uint8_t Byte;
#elif defined(__AES__)
typedef __m128i RoundKey;
typedef uint8_t Byte;
#endif

// Avoid subkey scheduling at this point
RoundKey rdkeys[ROUNDS+1];
for (size_t i=0; i<COUNTOF(rdkeys); ++i)
    memset(&rdkeys[i], (i<<4)|i, sizeof(RoundKey));

However, I am arriving at different results. Here's what the dumps produced:

Intel AES-NI:

In: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
...
Key: 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11
Data: 07 07 07 07 07 07 07 07 07 07 07 07 07 07 07 07
...
Key: 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
Data: 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33
Key: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA
Data: 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69
...

Out: 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69 69

ARMv8 AES:

In: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
...
Key: 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11
Data: C5 C5 C5 C5 C5 C5 C5 C5 C5 C5 C5 C5 C5 C5 C5 C5
...
Key: 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99 99
Data: C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3 C3
Key: AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA
Data: F9 F9 F9 F9 F9 F9 F9 F9 F9 F9 F9 F9 F9 F9 F9 F9
...
Out: F9 F9 F9 F9 F9 F9 F9 F9 B1 FF B9 F9 F9 F9 F9 F9

I keep scratching my head over the results. Adding more printf's is not helping identify the problem. I'm beginning to think Intel and ARM intrinsics use a different subkey schedule.

Does ARM and Intel intrinsics use the same subkey schedule for AES?


The image below is from a paper by Cynthia Crutchfield. It examines the mappings of Intel intrinsics and ARM intrinsics.

enter image description here


Below is the full program. The command lines to build them are also listed.

Intel:

g++ -Wall -maes aes-test.cxx -o aes-test.exe

AEMv8:

 g++ -Wall -march=armv8-a+crc+crypto -mtune=cortex-a53 aes-test.cxx -o aes-test.exe

Program:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#if defined(__ARM_FEATURE_CRYPTO)
# include <arm_neon.h>
# include <arm_acle.h>
#elif defined(__AES__)
# include <wmmintrin.h>
# include <emmintrin.h>
#endif

#if defined(__ARM_FEATURE_CRYPTO)
typedef uint8x16_t RoundKey;
typedef uint8_t Byte;
#elif defined(__AES__)
typedef __m128i RoundKey;
typedef uint8_t Byte;
#endif

#define COUNTOF(x) (sizeof(x)/(sizeof(x)[0]))

static const unsigned int ROUNDS=10;
void AES_encrypt(const Byte *in, Byte *out, const RoundKey *rdkeys, unsigned int rounds);
void AES_decrypt(const Byte *in, Byte *out, const RoundKey *rdkeys, unsigned int rounds);

void Print(const char* label, const Byte *in, size_t len, bool lf=false)
{
    if (label)
        printf("%s: ", label);

    for (size_t i=0; in && i<len; ++i)
        printf("%02X ", in[i]);    
    printf("\n");

    if (lf)
        printf("\n");
}

int main(int argc, char* argv[])
{
    Byte cipher[16], recover[16];
    const Byte plain[16] = {
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
    };

    // Avoid subkey scheduling at this point
    RoundKey rdkeys[ROUNDS+1];
    for (size_t i=0; i<COUNTOF(rdkeys); ++i)
        memset(&rdkeys[i], (i<<4)|i, sizeof(rdkeys[i]));

    AES_encrypt(plain, cipher, rdkeys, ROUNDS);

    return 0;
}

void AES_encrypt(const Byte *in, Byte *out, const RoundKey *rdkeys, unsigned int rounds)
{
    Print("In", in, 16);

#if defined(__ARM_FEATURE_CRYPTO)

    // Load the block
    uint8x16_t data = vld1q_u8(in);

    Print("Data (in)", (Byte*)&data, 16, true);

    // AES encryption with ARM intrinsics:
    // rnds-1 (9 for AES128) cycles of AES:
    // (Add, Shift, Sub) plus Mix Columns
    unsigned int i;
    for (i=0; i<rounds; ++i)
    {
        // AES single round encryption
        data = vaeseq_u8(data, rdkeys[i]);
        // AES mix columns
        data = vaesmcq_u8(data);

        Print("Key", (Byte*)&rdkeys[i], 16);
        Print("Data", (Byte*)&data, 16, true);
    }

    Print("Key", (Byte*)&rdkeys[i], 16);

    // One round of encryption: AES, no Mix Columns
    data = vaeseq_u8(data, rdkeys[i++]);

    Print("Data", (Byte*)&data, 16, true);

    // Final Add (bitwise Xor)
    data = veorq_u8(data, rdkeys[i]);

    Print("Data (xor)", (Byte*)&data, 16);

    // Store the output data
    vst1q_u8(out, data);

#elif defined(__AES__)

    __m128i data = _mm_loadu_si128((const __m128i*)in);

    Print("Data (in)", (Byte*)&data, 16);

    data = _mm_xor_si128(data, rdkeys[0]);

    Print("Key", (Byte*)&rdkeys[0], 16);
    Print("Data (xor)", (Byte*)&data, 16, true);

    for (unsigned int i=1; i<rounds-1; ++i)
    {
        data = _mm_aesenc_si128(data, rdkeys[i]);

        Print("Key", (Byte*)&rdkeys[i], 16);
        Print("Data", (Byte*)&data, 16, true);
    }
    data = _mm_aesenc_si128(data, rdkeys[rounds-1]);

    Print("Key", (Byte*)&rdkeys[rounds-1], 16);
    Print("Data", (Byte*)&data, 16, true);

    data = _mm_aesenclast_si128(data, rdkeys[rounds]);

    Print("Key", (Byte*)&rdkeys[rounds], 16);
    Print("Data", (Byte*)&data, 16, true);

    _mm_storeu_si128((__m128i*)out, data);

#endif

    Print("Out", out, 16);
}
1 Answers
Related