ハイパフォーマンスの AES (Advanced Encryption Standard) アプリケーションを実現する
IBM XL C/C++ および Fortran コンパイラーの IBM POWER8 組み込み機能を使用する方法
はじめに
米国政府の暗号化標準である AES は、米国政府機関や世界中の業界で、機密情報および機密性の高いデータを保護するために広く使用されています。C/C++ および Fortran 用の新しい IBM XL コンパイラーは、リトル・エンディアン・システムとビッグ・エンディアン・システムの両方で AES 組み込み関数のサポートを提供します。IBM POWER8 組み込み関数を使用することで、AES アプリケーションは IBM POWER8 プロセッサー上でハイパフォーマンスを実現することができます。
2014年に一般にリリースされた IBM XL コンパイラーは、POWER8 機能をサポートすることによって、最新の POWER8 プロセッサーの能力をフルに引き出します。アプリケーション・レベルで直接アーキテクチャーの命令を呼び出す新機能の中には、AES 用の POWER8 暗号化組み込み関数もあります。これらの組み込み関数は、主要な AES オペレーションのそれぞれを、POWER8 プロセッサー上のハードウェア・レベルで対応する命令に 1 対 1 でマッピングします。この記事では、IBM XL コンパイラーがサポートする AES の暗号化処理および復号処理を行うための、新しい暗号化組み込み関数について詳しく探ります。
AES 組み込み関数をサポートする IBM XL コンパイラーのバージョン
AES 組み込み関数は、以下の IBM XL コンパイラーでサポートされています。
- C/C++ バージョン 13 以降
- Fortran バージョン 15 以降
表 1 に、POWER8 機能および拡張機能をサポートしているコンパイラーのバージョンを、プラットフォーム別に記載します。
表 1. AES 組み込み関数をサポートする XL コンパイラーのバージョン
言語 | ビッグ・エンディアン | リトル・エンディアン | ||
---|---|---|---|---|
C/C++ | XL C/C++ V13.1 | OS: AIX, Linux | XL C/C++ V13.1.1 | OS: Linux |
Fortran | XL Fortran V15.1 | OS: AIX, Linux | XL Fortran V15.1.1 | OS: Linux |
AES 入門
AES が米国政府の暗号化標準になったのは、2002年 5月のことです。AES は、米国国家安全保障局 (NSA) によって承認された初めての、公に利用可能なオープン暗号化方式であり、その目的は、NSA 認定の暗号モジュールで使用される機密情報からトップ・シークレット・レベルの情報までを保護することにあります。AES は、テキスト・ブロックに対して換字と転字の両方を組み合わせて適用した後、鍵を適用して出力ブロックを生成するブロック暗号アルゴリズムです。各ブロック (「状態」とも呼ばれます) は、4x4 の列優先バイト行列 (128 ビット値) となっています。
注: 状態の列優先行列は、AES で規定されており、メモリー内のネイティブ・ストレージ順の言語実装からは独立しています。
ここで、AES がどのように機能し、このアーキテクチャーが特定の POWER8 組み込み関数にどのようにマッピングされるのかを見て行きましょう。
入力テキスト・ストリームの状態表現の一例を図 1 に示します。
図 1. テキスト・ストリームの状態表現

AES は、暗号化と復号のプロセスに同じ鍵を使用する、対称鍵アルゴリズムです。AES の鍵長として規定されている値は、バイト単位では 16、24、32 であり、ビット単位では 128、192、256 です。これらの値は、標準で規定されている鍵長 (Nk) の値 4、6、8 に相当します。AES アプリケーションで使用する鍵の長さによって、平文と暗号文との間で変換を行う際の変換ラウンド数が決まります。ラウンド数 (Nr) は、以下のように計算します。
ラウンド数 (Nr) = 10 + (バイト単位の鍵長 – 16) / 4
表 2 に、128 ビット、192 ビット、256 ビットの AES の鍵長、ブロック・サイズ、ラウンド数を記載します。
表 2. 128 ビット、192 ビット、256 ビットの AES の規定値
暗号 | 鍵長 (ビット単位) | 鍵長 (バイト単位) | 鍵長 (Nk ワード数) | ブロック・サイズ (Nb ワード数) | ラウンド数 (Nr) |
---|---|---|---|---|---|
128 ビットの AES | 128 | 16 | 4 | 4 | 10 |
192 ビットの AES | 192 | 24 | 6 | 4 | 12 |
256 ビットの AES | 256 | 32 | 8 | 4 | 14 |
各ラウンドの鍵は、特定の暗号鍵から、Rijndael (米国国立標準技術研究所 (NIST) が AES 用に選択したアルゴリズム) 鍵スケジュールを使用して派生されます。図 2 に示すように、使用している鍵の長さとは関係なく、各ラウンドの鍵は、4x4 の列優先バイト行列 (長さ 128 ビット) です。IBM XL コンパイラーは、鍵を拡張するための組み込み関数を提供していないことに注意してください。
図 2. AES 暗号化プロセスでの鍵の拡張とラウンド数の間の関係

AES 暗号化プロセスの初期ラウンドでは、AddRoundKey()
による変換しか行われません。この変換で、XOR 操作によってラウンドの鍵が状態に追加されます。最終ラウンドを除く他のすべてのラウンドでは、SubBytes()
、ShiftRows()
、MixColumns()
、および AddRoundKey()
による 4 つの変換が行われます。最終ラウンドは少し異なり、SubBytes()
、ShiftRows()
、および AddRoundKey()
による変換だけが行われます。
AES 復号プロセスは、AES 暗号化プロセスと同じ暗号鍵を使用するものの、プロセスは多少変わってきます。AES 復号プロセスの初期ラウンドでは、同じく AddRoundKey()
による変換しか行われませんが、中間のすべてのラウンドでは、InverseShiftRows()
、InverseSubBytes()
、AddRoundKey()
、および InverseMixColumns()
による変換が行われます。最終ラウンドでは、InverseShiftRows()
、InverseSubBytes()
、AddRoundKey()
による変換のみが行われます。
図 3 に、AES 暗号化プロセスと AES 復号プロセスでの対応する操作の対称性を示します。
図 3. AES 暗号化プロセスと AES 復号プロセスの対称性

IBM XL コンパイラーの AES 用組み込み関数
IBM POWER8 アーキテクチャーは、AES で使用する重要な関数のほとんどに対し、ハードウェア・レベルのサポートを提供します。IBM XL コンパイラーには、ハードウェア命令に 1 対 1 の関係でマッピングされる一連の暗号化組み込み関数が用意されています。これらの組み込み関数は、アセンブリー言語によるハードウェア・レジスターの管理に代わる手段となり、ソフトウェア・エンジニアがこれらの最適化された命令セットにアクセスできるようにします。IBM XL コンパイラーには、以下のAES 用組み込み関数があります。
- vcipher (state_array, round_key)
- vcipherlast (state_array, round_key)
- vncipher (state_array, round_key)
- vncipherlast (state_array, round_key)
- vsbox (state_array)
vcipher (state_array, round_key)
この組み込み関数は、渡された round_key を使用して、中間の state_array に対し、AES 暗号操作のいずれか 1 つのラウンドを行います。暗号化プロセスの間、初期ラウンドと最終ラウンドを除くすべてのラウンドでは、この関数が使用されます。以下に、C/C++、Fortran、およびアセンブリー言語での vcipher 組み込み関数のインターフェースを記載します。
- C/C++
vector unsigned char __vcipher (vector unsigned char state_array, vector unsigned char round_key)
- Fortran
VCIPHER (STATE_ARRAY, ROUND_KEY) ― ここで、引数と結果は両方とも kind が 1 の符号なしベクトルです。
- アセンブリー
vcipher VRT,VRA,VRB- VRT は結果を格納します。この結果は、暗号操作の新しい中間状態を表すものです。
- VRT は状態を格納します。この状態は、AES 暗号操作中の中間状態の配列を表すものです。
- VRB はラウンド鍵を格納します。
vcipherlast (state_array, round_key)
この組み込み関数は、渡された round_key を使用して、中間の state_array に対し、AES 暗号操作の最終ラウンドを行います。暗号化プロセスの最終ラウンドには、この関数が使用されます。関数の出力は、暗号化されたテキストです。以下に、C/C++、Fortran、およびアセンブリー言語での vcipherlast 組み込み関数のインターフェースを記載します。
- C/C++
vector unsigned char __vcipherlast (vector unsigned char state_array, vector unsigned char round_key)
- Fortran
VCIPHERLAST (STATE_ARRAY, ROUND_KEY) ― ここで、引数と結果は両方とも kind が 1 の符号なしベクトルです。
- アセンブリー
vcipherlast VRT,VRA,VRB- VRT は結果を格納します。この結果は、暗号操作の最終状態を表すものです。
- VRT は状態を格納します。この状態は、AES 暗号操作中の中間状態の配列を表すものです。
- VRB はラウンド鍵を格納します。
図 4 に、ユーザー定義関数を使用した場合と、POWER8 組み込み関数を使用した場合の AES 暗号化プロセスの擬似コードを記載します。
図 4. AES 暗号化プロセスでの AES 操作と AES 組み込み関数との関係

vncipher (state_array, round_key):
この組み込み関数は、渡された round_key を使用して、中間の state_array に対し、AES 復号操作のいずれか 1 つのラウンドを行います。復号プロセスの間、初期ラウンドと最終ラウンドを除くすべてのラウンドでは、この関数が使用されます。以下に、C/C++、Fortran、およびアセンブリー言語での vncipher 組み込み関数のインターフェースを記載します。
- C/C++
vector unsigned char __vncipher (vector unsigned char state_array, vector unsigned char round_key)
- Fortran
VNCIPHER (STATE_ARRAY, ROUND_KEY) ― ここで、引数と結果は両方とも kind が 1 の符号なしベクトルです。
- アセンブリー
vncipher VRT,VRA,VRB- VRT は結果を格納します。この結果は、復号操作の新しい中間状態を表すものです。
- VRT は状態を格納します。この状態は、AES 復号操作中の中間状態の配列を表すものです。
- VRB はラウンド鍵を格納します。
vncipherlast (state_array, round_key)
この関数は、渡された round_key を使用して、中間の state_array に対し、AES 復号操作の最終ラウンドを行います。復号プロセスの最終ラウンドには、この関数が使用されます。関数の出力は、元の平文です。以下に、C/C++、Fortran、およびアセンブリー言語での vncipherlast 組み込み関数のインターフェースを記載します。
- C/C++
vector unsigned char __vncipherlast(vector unsigned char state_array, vector unsigned char round_key)
- Fortran
VNCIPHERLAST (STATE_ARRAY, ROUND_KEY) ― ここで、引数と結果は両方とも kind が 1 の符号なしベクトルです。
- アセンブリー
vncipherlast VRT,VRA,VRB- VRT は状態を格納します。この状態は、AES 復号操作中の中間状態の配列を表すものです。
- VRB はラウンド鍵を格納します。
- VRT は結果を格納します。この結果は、復号操作の最終状態を表すものです。
図 5 に、ユーザー定義関数を使用した場合と、POWER8 組み込み関数を使用した場合の AES 復号プロセスの擬似コードを記載します。
図 5. AES 復号プロセスでの AES 操作と AES 組み込み関数との関係

図 6 に、POWER8 組み込み関数による AES 暗号化プロセスと AES 復号プロセスとの関係を示します。
図 6. AES 暗号化プロセスと AES 復号プロセスにおける AES 組み込み関数

上記の組み込み関数の他、FIPS-197 に定義されている SubBytes() 操作を state_array に対して実行する組み込み関数もあります。
vsbox (state_array)
この組み込み関数は、あらかじめ定義された換字表 (S-box) を用いて SubBytes() 操作を実行し、状態の各バイトに対して、それぞれ個別に非線形のバイト換字操作を適用します。以下に、C/C++、Fortran、およびアセンブリー言語での vsbox 組み込み関数のインターフェースを記載します。
- C/C++
vector unsigned char __vsbox(vector unsigned char state_array)
- Fortran
VSBOX(STATE_ARRAY) ― ここで、引数と結果は両方とも kind が 1 の符号なしベクトルです。
- アセンブリー
vsbox VRT,VRA- VRT は状態を格納します。この状態は、AES 暗号操作中の中間状態の配列を表すものです。
- VRT は、FIPS-197 に定義されている SubBytes() 変換を状態に適用した結果を格納します。
図 7. S-box ルックアップ操作

例えば、入力が {68} だとすると、換字操作後の値は 6 行目と 8 列目の交点によって決定されます。したがって、出力値は {45} になります。
IBM XL C/C++ V13.1 コンパイラーの暗号化組み込み関数を使用したテスト・プログラム
以下のスニペットでは、POWER8 アーキテクチャーに対して XL C/C++ V13.1 暗号化組み込み関数を使用しています。
- _vcipher を使用した AES 暗号化プロセスの中間ラウンド
- SubBytes( ) オプション。つまり、_vsbox を使用した S-box ルックアップ
__vcipher の例
入力は、Federal Information Processing Standards Publication 197(FIPS-197) の付録 B に記載されている中間ブロックとラウンド鍵です。
図 8. FIPS-197 の付録 B に基づく変換例

ラウンド 1 の開始時の入力は、以下のとおりです。
{ 0x19, 0x3d, 0xE3, 0xBE, 0xA0, 0xF4, 0xE2, 0x2B, 0x9A, 0xC6, 0x8D, 0x2A, 0xE9, 0xF8, 0x48, 0x08 }
このラウンドに対応するラウンド鍵は、以下のとおりです。
{ 0xA0, 0xFA, 0xFE, 0x17, 0x88, 0x54, 0x2C, 0xB1, 0x23, 0xA3, 0x39, 0x39, 0x2A, 0x6C, 0x76, 0x05 }
ラウンド 1 で SubBytes( )
、ShiftRows( )
、MixColumns( )
、および AddRoundKey( )
を実行した後の出力は、以下のようになります。
{ 0xA4, 0x9C, 0x7F, 0xF2, 0x68, 0x9F, 0x35, 0x2B, 0x6B, 0x5B, 0xEA, 0x43, 0x02, 0x6A, 0x50, 0x49 }
リスト 1: test__vcipher.c ファイルの内容
int main(){ /* Appendix B p.33 Round 1 */ vector unsigned char state = { 0x19, 0x3d, 0xE3, 0xBE, 0xA0, 0xF4, 0xE2, 0x2B, 0x9A, 0xC6, 0x8D, 0x2A, 0xE9, 0xF8, 0x48, 0x08 }; vector unsigned char roundKey = { 0xA0, 0xFA, 0xFE, 0x17, 0x88, 0x54, 0x2C, 0xB1, 0x23, 0xA3, 0x39, 0x39, 0x2A, 0x6C, 0x76, 0x05 }; vector unsigned char expect = { 0xA4, 0x9C, 0x7F, 0xF2, 0x68, 0x9F, 0x35, 0x2B, 0x6B, 0x5B, 0xEA, 0x43, 0x02, 0x6A, 0x50, 0x49 }; vector unsigned char answer =__vcipher(state, roundKey); return answer == expect ? 0 : 1; }
__vsbox の例
この例でも、入力は Federal Information Processing Standards Publication 197(FIPS-197) の付録 B に記載されている中間ブロックです。
図 9. FIPS-197 の付録 B に基づく SubByte 操作の例

ラウンド 1 の開始時の入力は、以下のとおりです。
{ 0x19, 0x3d, 0xE3, 0xBE, 0xA0, 0xF4, 0xE2, 0x2B, 0x9A, 0xC6, 0x8D, 0x2A, 0xE9, 0xF8, 0x48, 0x08 }
SubBytes( ) を実行した後の出力 (つまり、定義済み S-box ルックアップの結果) は、以下のようになります。
{ 0xD4, 0x27, 0x11, 0xAE, 0xE0, 0xBF, 0x98, 0xF1, 0xB8, 0xB4, 0x5D, 0xE5, 0x1E, 0x41, 0x52, 0x30 }
リスト 2. test__vsbox.c ファイルの内容
int main(){ /* Appendix B p.33 Round 1 */ vector unsigned char state = { 0x19, 0x3d, 0xE3, 0xBE, 0xA0, 0xF4, 0xE2, 0x2B, 0x9A, 0xC6, 0x8D, 0x2A, 0xE9, 0xF8, 0x48, 0x08 }; vector unsigned char expect = { 0xD4, 0x27, 0x11, 0xAE, 0xE0, 0xBF, 0x98, 0xF1, 0xB8, 0xB4, 0x5D, 0xE5, 0x1E, 0x41, 0x52, 0x30 }; vector unsigned char answer =__vsbox(state); return answer == expect ? 0 : 1; }
リスト 1 とリスト 2 のコードは、以下のコマンドを使用してコンパイルすることができます。
xlc –o test_vsbox –qarch=pwr8 -qaltivec ./test__vsbox.c
より高い最適化レベルでコンパイルする場合には、オプション –qtune=pwr8
を追加することをお勧めします。POWER8 固有のオプションである –qarch=pwr8
と –qtune=pwr8
が指定されていると、コンパイラーから生成されるコードが制御されます。これらのオプションはコンパイラーに対し、POWER8 プロセッサー上で最大限のパフォーマンスを実現するために、命令、スケジューリング、およびその他の最適化を調整するように指示します。
コンパイラーによって生成されるコード
以下に記載する 2 つのテスト・プログラムは同様ですが、一方は POWER8 暗号化組み込み関数を使用しており、もう一方は使用していません。
リスト 3. 組み込み関数を使用した test__vsbox.c ファイルの内容
int main(){ /* Appendix B p.33 Round 1 */ vector unsigned char state = { 0x19, 0x3d, 0xE3, 0xBE, 0xA0, 0xF4, 0xE2, 0x2B, 0x9A, 0xC6, 0x8D, 0x2A, 0xE9, 0xF8, 0x48, 0x08 }; vector unsigned char roundKey = { 0xA0, 0xFA, 0xFE, 0x17, 0x88, 0x54, 0x2C, 0xB1, 0x23, 0xA3, 0x39, 0x39, 0x2A, 0x6C, 0x76, 0x05 }; vector unsigned char expect = { 0xA4, 0x9C, 0x7F, 0xF2, 0x68, 0x9F, 0x35, 0x2B, 0x6B, 0x5B, 0xEA, 0x43, 0x02, 0x6A, 0x50, 0x49 }; vector unsigned char answer =__vcipher(state, roundKey); return answer == expect ? 0 : 1; }
両方のプログラムを –c および –qlist を指定してコンパイルすることで、ソース・コードをリスト表示したファイルを生成することができます (あるいは、–c と –S を指定してコンパイルすると、アセンブリー・ファイルが生成されます)。
図 10. 組み込み関数を使用したソース・コード・ファイルのリスト表示

test__vcipher.c ファイル (POWER8 組み込み関数を使用したプログラム) をリスト表示することにより、POWER8 命令 vcipher が呼び出されると、プログラムが即時に終了されることが明らかになります。命令カウントは 35 です。
一方、POWER8 組み込み関数を使用していないテスト・プログラムをリスト 4 に記載します。
リスト 4. 組み込み関数を使用していない test__vsbox.c ファイルの内容
extern vector unsigned char mySubBytes(vector unsigned char); extern vector unsigned char myShiftRows(vector unsigned char); extern vector unsigned char myMixColumns(vector unsigned char); extern vector unsigned char myAddRoundKey(vector unsigned char, vector unsigned char); int main(){ /* Appendix B p.33 Round 1 */ vector unsigned char state = { 0x19, 0x3d, 0xE3, 0xBE, 0xA0, 0xF4, 0xE2, 0x2B, 0x9A, 0xC6, 0x8D, 0x2A, 0xE9, 0xF8, 0x48, 0x08 }; vector unsigned char roundKey = { 0xA0, 0xFA, 0xFE, 0x17, 0x88, 0x54, 0x2C, 0xB1, 0x23, 0xA3, 0x39, 0x39, 0x2A, 0x6C, 0x76, 0x05 }; vector unsigned char expect = { 0xA4, 0x9C, 0x7F, 0xF2, 0x68, 0x9F, 0x35, 0x2B, 0x6B, 0x5B, 0xEA, 0x43, 0x02, 0x6A, 0x50, 0x49 }; vector unsigned char answer = mySubBytes(state); answer = myShiftRows(answer); answer = myMixColumns(answer); answer = myAddRoundKey(answer, roundKey); return answer == expect ? 0 : 1; }
図 11. 組み込み関数を使用していないソース・コード・ファイルのリスト表示

この場合のソース・コード・ファイルのリスト表示からは、プログラムがサポート関数に対して 4 つの呼び出しを行っていることがわかります。関数呼び出しは、関連作業としてコール・スタックをセットアップする作業と管理する作業を伴うことから、パフォーマンス・コストの点で高くつきます。パフォーマンスを向上させるために、最適化によってこれらの関数呼び出しをインライン化することにしたとしても、インライン・コードには数千とはいかないまでも、数百の命令が含まれることになります。組み込み関数と比べ、ユーザー定義関数の実行には長い時間がかかる上に、生成されるバイナリーのフットプリントも大きくなります。
AES 組み込み関数を使用することが極めて重要となる、実用上のもう 1 つの理由には、プログラムで実装する動作モードもあります。動作モードとして連鎖モード (例えば、CBC または PCBC) あるいはフィードバック・モード (例えば、CFB または OFB) が選択されると、暗号化プロセスまたは復号プロセス、あるいはその両方を並列化することができなくなります。その場合、パフォーマンスの点で、シーケンシャルな動作がボトルネックになります。こうしたことから、ハイパフォーマンス・アプリケーションを実現するには組み込み関数の使用が不可欠となります。
参考文献 (C/C++ 関連)
- XL C/C++ for Linux や XL C/C++ for AIX の製品ページにアクセスして、詳しい情報を入手してください。
- XL C/C++ for Linux の無料の試用版をダウンロードして入手してください。
- 豊富な情報に基づいた C/C++ Cafe コミュニティーに参加して、他の開発者とつながってください。
参考文献 (その他)
- 「Announcing the ADVANCED ENCRYPTION STANDARD (AES)」(Federal Information Processing Standards Publication 197、United States National Institute of Standards and Technology (NIST)、2001年11月26日 (2014年9月1日改訂)。
- コンパイラー・リファレンス > コンパイラー組み込み関数 > 暗号化組み込み関数 (IBM Corporation、2014年 (2014年9月1日改訂)。
- 「Block cipher mode of operation」(2014年9月1日改訂)。