2017-08-06 4 views
3

私はintrinsicsを使用してARMv8上でAES実装をカットインしようとしています。私にはC++の実装があり、インテルイントリンシックの実装があります。ARMおよびインテル®コンパイラ組み込み関数は、AESに同じサブキースケジュールを使用しますか?

インプリメンテーションは同等と見なされるため、インテルをARMv8の青写真として使用しようとしています。いくつかの違いがありますが、それらは説明されています。問題は、私は別の結果を得ているということです。

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 
} 

この時点で、私はサイドステップのサブキーの計算を試みています。両方の実装で同じラウンドキーセットを使用します:

#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)); 

しかし、私は異なる結果に到着しています。ここではダンプが生成何:

インテル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 

私は結果の上に頭を悩ま続けます。より多くのprintfを追加することは、問題の特定に役立たない。私はインテルとARMの組み込み関数が異なるサブキースケジュールを使用していると考え始めています。

ARMおよびインテル®コンパイラ組み込み関数は、AESに同じサブキースケジュールを使用しますか?


下記の画像はpaper by Cynthia Crutchfieldです。インテルイントリンシクスとARMイントリンシックスのマッピングを検証します。以下は

enter image description here


完全なプログラムです。それらをビルドするコマンドラインもリストされています。

インテル

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 

プログラム

#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

が、私は例えば0xF9(...データ値の差が0x90を(90)と0xBE(190)のいずれかであるように思わ気づいた - 0x69の、0xC3 - 0x33の、。など)おそらく、問題を絞り込むのに役立ちます... ARMのための外出はちょっと変わったようです。 –

答えて

2

ARMとIntelの組み込み関数は、AESのための同じサブキーのスケジュールを使用していますか?

答えは「はい」と思われます。実際のキースケジューリングをテストする必要がありますが、IntelとARMv8の両方の組み込み関数で同じキースケジュールを使用して同じ結果を生成できました。

Crutchfieldのリファレンス実装にはオフバイワンがあるようです。ループ制御としてrounds-1を使用していて、roundsを使用していないはずです。つまり、私は11ラウンドでARMv8をテストしていましたが、10ではなく、F9 F9 ... F9 F9の代わりにF9 F9 F9 F9 F9 F9 F9 F9 B1 FF B9 F9 F9 F9 F9 F9というARMv8コードを生成したときに疑わしいはずでした。

ここで更新されたコードです:

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); 

    unsigned int i; 
    for (i=0; i<rounds-1; ++i) 
    { 
     data = vaeseq_u8(data, rdkeys[i]); 
     data = vaesmcq_u8(data); 
    } 

    data = vaeseq_u8(data, rdkeys[i++]); 
    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]); 

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

    data = _mm_aesenc_si128(data, rdkeys[i++]); 
    data = _mm_aesenclast_si128(data, rdkeys[i]); 
    _mm_storeu_si128((__m128i*)out, data); 

#endif 
} 
+0

ARMコードは今実行されていますか? –

+0

@MaartenBodewes - はい、コードが実行されています。私は、[Fix ARMv8 AES Encryption](https://github.com/noloader/cryptopp/commit/701ec3aa1f45a754)と[Fix ARMv8 AES Decryption](https://github.com/noloader/)でテストブランチをチェックしました。 cryptopp/commit/14590423249d)。私はそれがテストを経た後にマスターにプッシュします。その約2.8から3.0 cpbで実行されます。約1.0 cpbは他のトップパフォーマーよりも遅いですが、C++では30-45 cpbです。 – jww

関連する問題