x86/x64 SIMD命令によるAES暗号処理

x86/x64のAESNI命令群の使い方を解説します。

基本

AES暗号については、米国の政府機関が出しているFIPS 197という文書がオリジナルの仕様になりますのでそちらを見ながら読んでください。

ブロックサイズ、キーサイズ

AES暗号ではキーサイズは128ビット、192ビット、256ビットの3種類があり、AES-128、AES-192、AES-256と呼ばれます。

各キーサイズごとに固有の定数Nk(キー長)、Nb(ブロック長)、Nr(ラウンド回数)があり下のように決まっています。NkとNbの単位はwordで、AESでは「word」は32ビットです。


(FIPS 197より抜粋)

煩わしいので以下Nbは4と書きます。

AES暗号ではどのキー長でもブロック長は4ワード(16バイト)固定です。暗号化では16バイトの平文を入力して16バイトの暗号文を得ます。復号では16バイトの暗号文を入力して16バイトの平文を得ます。

state配列

state配列は4*4バイトの二次元配列で、ここに平文を入れたあと、roundと呼ばれる処理を繰り返して加工することで暗号文を作ります。roundはNr回繰り返します。


(FIPS 197より抜粋)

AESNIでは、XMMWORDひとつにこの配列を丸ごと入れて処理します。

AESNI命令の使用の際には、バイトオーダー(エンディアン)の変換を行う必要はありません。

w配列

w配列は、Nkワードのキーをもとに、あらかじめKeyExpansionという処理をして作っておく4 * (Nr + 1)ワードの一次元配列です。round 1回ごとに4ワードずつ使っていきます。roundの周回に入る前に下処理でひとつ使うのでNr+1回分が必要です。

AESNIでは、XMMWORDひとつにround 1回分(4ワード)の要素を丸ごと入れて処理します(AESのワードは32ビットです)。

 

暗号化

以下はFIPS 197に書いてある暗号化のアルゴリズムです。色は解説の都合上私がつけたものです。AESNI命令ではの部分を行います。のAddRoundKey()はxorしているだけなのでpxor命令でできます。

AESENC - AES ENCrypt
AESENCLAST -AES ENCrypt LAST

AESENC xmm1, xmm2/m128
__m128i  _mm_aesenc_si128(__m128i state, __m128i w);
AESENCLAST xmm1, xmm2/m128
__m128i  _mm_aesenclast_si128(__m128i state, __m128i w);

VAESENC xmm1, xmm2, xmm3/m128
VAESENCLAST xmm1, xmm2, xmm3/m128

AESENCは、の4つの関数を呼び出している部分(1ラウンド分)を1命令で処理します。

は最終ラウンドですが、この回だけ呼び出す関数がひとつ少ないので、AESENCLAST命令で処理します。

①にラウンド前のstate配列、②にそのラウンドで使うw配列の要素を入れて実行すると、③にラウンド後のstate配列が返ります。

命令の中で具体的に何をやっているかはFIPS 197の各関数の説明をご覧ください。

w配列(round用キー)を生成する

暗号化の前にKey Expansionで暗号キー(Nkワードのデータ)からw配列を作ります。

以下はFIPS 197のKeyExpansionのアルゴリズムです。

Rcon配列はFIPS 197で定義された方法で計算された定数の配列で、具体的な値は以下のようになります。AES暗号で実際に使われるのは[1]~[10]の範囲だけです。

static const BYTE Rcon[] = {
 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
};

keyをw配列にコピーする際に、バイトオーダー(エンディアン)の変換を行う必要はありません。

AESKEYGENASSIST - AES KEY GENeration ASSIST

AESKEYGENASSIST xmm1, xmm2/m128, imm8
VAESKEYGENASSIST xmm1, xmm2/m128, imm8
__m128i  _mm_aeskeygenassist_si128(__m128i temp, const int Rcon);

AESKEYGENASSISTは、①にtempの値を入れて、②にRcon[i/Nk]の値を指定して実行すると、の2つの式の値を計算して③に返してくれます。

AESKEYGENASSIST命令は2つの値を返してきますが、周回ごとに使うのはどちらか一方だけなので、もう一方は捨てることになります。また、2セット同時に計算してくれますが、これは2つの別の暗号キーを同時に処理するのでなければ使いようがないと思います。

なお、の値を使うのはAES-256の場合だけです。

復号

FIPS 197では、復号のアルゴリズムとして、暗号化の手順を逆にしただけの「InvCipher」と、それと等価な結果を得られる「EqInvCipher」という2つが定義されていますが、AESNIがサポートするのは後者のみです。

EqInvCipherでは、w配列の生成処理に後処理をちょっと追加して作ったdw配列を使います。dw配列の作り方は後述します。

AESDEC - AES DECrypt
AESDECLAST -AES DECrypt LAST

AESDEC xmm1, xmm2/m128
__m128i  _mm_aesdec_si128(__m128i state, __m128i dw);
AESDECLAST xmm1, xmm2/m128
__m128i  _mm_aesdeclast_si128(__m128i state, __m128i dw);

VAESDEC xmm1, xmm2, xmm3/m128
VAESDECLAST xmm1, xmm2, xmm3/m128

AESDECは、の4つの関数を呼び出している部分(1ラウンド分)を1命令で処理します。

は最終ラウンドですが、この回だけ呼び出す関数がひとつ少ないので、AESDECLAST命令で処理します。

①にラウンド前のstate配列、②にそのラウンドで使うdw配列の要素を入れて実行すると、③にラウンド後のstate配列が返ります。

dw配列を生成する

EqInvCipher用のdw配列は、前述のKeyExpansion処理で作ったw配列に、以下の後処理をすることで作ります。

AESIMC - AES InvMixColumns

AESIMC xmm1, xmm2/m128
VAESIMC xmm1, xmm2/m128
__m128i  _mm_aesimc_si128(__m128i w);

上のInvMixColumns()の部分を処理する命令です。w配列の要素を①に入れて実行すると、後処理後のdw配列の要素が得られます。


x86/x64 SIMD命令一覧表  フィードバック

ホームページ http://www.officedaytime.com/