RijndaelのS-boxは、AES暗号化および復号化で頻繁に使用される操作です。通常、256バイトのルックアップテーブルとして実装されます。これは高速ですが、コード内で256バイトのルックアップテーブルを列挙する必要があることを意味します。基本的な数学的構造を考えると、この群衆の誰かがより少ないコードでそれを行うことができると確信しています。
RijndaelのSボックスを実装するお気に入りの言語で関数を記述します。最短のコードが優先されます。
RijndaelのS-boxは、AES暗号化および復号化で頻繁に使用される操作です。通常、256バイトのルックアップテーブルとして実装されます。これは高速ですが、コード内で256バイトのルックアップテーブルを列挙する必要があることを意味します。基本的な数学的構造を考えると、この群衆の誰かがより少ないコードでそれを行うことができると確信しています。
RijndaelのSボックスを実装するお気に入りの言語で関数を記述します。最短のコードが優先されます。
回答:
{[[0 1{.283{1$2*.255>@*^}:r~^}255*].@?~)={257r}4*99]{^}*}:S;
このコードは、S
バイトを取り込んでRijndael S-boxを適用するという名前の関数を定義します。(またr
、いくつかの文字を保存するために名前が付けられた内部ヘルパー関数を使用します。)
この実装では、Thomas Porninが提案するように、対数表を使用してGF(2 8)逆行列を計算します。いくつかの文字を保存するために、入力バイトごとに対数表全体が再計算されます。それでも、GolfScriptは一般的に非常に遅い言語ですが、このコードは私の古いラップトップで1バイトを処理するのにたった10ミリ秒しかかかりません。対数表(L
)を事前計算すると、バイトあたり約0.5ミリ秒まで速度が上がりますが、さらに3文字のコストがかかります。
[0 1{.283{1$2*.255>@*^}:r~^}255*]:L;{[L?~)L={257r}4*99]{^}*}:S;
便宜上、S
上記で定義したように、Wikipediaのように 16進数でSボックス全体を計算して印刷するための関数を呼び出す簡単なテストハーネスを次に示します。
"0123456789abcdef"1/:h; 256, {S .16/h= \16%h= " "++ }% 16/ n*
(オンラインデモでは、時間がかかりすぎないように対数表が事前計算されます。それでも、オンラインGolfScriptサイトはランダムにタイムアウトすることがあります。これはサイトの既知の問題であり、通常はリロードにより修正されます。)
対数表の計算、特にヘルパー関数から始めましょうr
:
{1$2*.255>@*^}:r
この関数は、スタック上で2つの入力を受け取ります。バイトとリダクションビットマスク(256〜511の定数)です。入力バイトを複製し、コピーに2を掛けます。結果が255を超える場合、ビットマスクとXORして256未満に戻します。
ログテーブル生成コード内で、関数r
はリダクションビットマスク283 = 0x11b(Rijndael GF(2 8)リダクション多項式 x 8 + x 4 + x 3 + x + 1に対応)で呼び出され、結果はXORされます元のバイトを使用して、Rijndael有限体で効果的に3(多項式としてx + 1)を乗算します。この乗算は、バイト1から255回繰り返され、結果(および最初のゼロバイト)がL
、次のような257要素の配列に収集されます(中央部分は省略)。
[0 1 3 5 15 17 51 85 255 26 46 ... 180 199 82 246 1]
257個の要素がある理由は、先頭に0が追加され、1が2回出現するため、この配列内の(ゼロベースの)インデックスを検索し、それを無効にして、同じ配列内の否定インデックスのバイトをアップします。(他の多くのプログラミング言語と同様に、GolfScriptでは、負の配列インデックスは配列の末尾から逆方向にカウントされます。)実際、これはL?~)L=
関数の先頭のコードが行うこととまったく同じS
です。
残りのコードは、r
リダクションビットマスク257 = 2 8 + 1でヘルパー関数を4回呼び出して、反転した入力バイトの4つのビット回転コピーを作成します。これらはすべて定数99 = 0x63とともに配列に収集され、XORされて最終出力が生成されます。
AES-NI命令セットを使用します
66 0F 6E C1 movd xmm0,ecx
66 0F 38 DD C1 aesenclast xmm0,xmm1
0F 57 C1 xorps xmm0,xmm1
66 0F 3A 14 C0 00 pextrb eax,xmm0,0
C3 ret
Windows呼び出し規約を使用して、1バイトを取り込んで1バイトを出力します。ShiftRows
最初のバイトに影響を与えないため、逆にする必要はありません。
対数を使用することにより、有限体GF(256)で逆行列を計算せずにテーブルを生成できます。次のようになります(Javaコード、int
署名付きbyte
タイプの問題を回避するために使用):
int[] t = new int[256];
for (int i = 0, x = 1; i < 256; i ++) {
t[i] = x;
x ^= (x << 1) ^ ((x >>> 7) * 0x11B);
}
int[] S = new int[256];
S[0] = 0x63;
for (int i = 0; i < 255; i ++) {
int x = t[255 - i];
x |= x << 8;
x ^= (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7);
S[t[i]] = (x ^ 0x63) & 0xFF;
}
3はGF(256)*の乗法生成器であるという考え方です。テーブルt[]
はt[x]
3 xに等しいものです。3 255 = 1なので、1 /(3 x)= 3 255-xが得られます。
0x1B
代わりに(六角リテラルの一方1)0x11B
int
型は、Javaで32ビットです。上位ビットをキャンセルする必要があります。
{256:B,{0\2${@1$3$1&*^@2/@2*.B/283*^}8*;;1=},\+0=B)*:A.2*^4A*^8A*^128/A^99^B(&}:S;
グローバル変数A
とを使用し、グローバル変数B
として関数を作成しますS
。
ガロア反転は総当たりです。mul
反転後のアフィン変換に再利用できる別の関数を使用して実験しましたが、オーバーフローの動作が異なるため、より高価になることがわかりました。
これは、オンラインデモには遅すぎます。テーブルの最初の2行でもタイムアウトになります。
この答えは、関数を一定時間にすることについてのPa functionlo Ebermannのコメント質問に対するものです。このコードは法案に適合します。
def S(x):
i=0
for y in range(256):
p,a,b=0,x,y
for j in range(8):p^=b%2*a;a*=2;a^=a/256*283;b/=2
m=(p^1)-1>>8;i=y&m|i&~m
i|=i*256;return(i^i/16^i/32^i/64^i/128^99)&255
ubyte[256] getLookup(){
ubyte[256] t=void;
foreach(i;0..256){
t[i] = x;
x ^= (x << 1) ^ ((x >>> 7) * 0x1B);
}
ubyte[256] S=void;
S[0] = 0x63;
foreach(i;0..255){
int x = t[255 - i];
x |= x << 8;
x ^= (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7);
S[t[i]] = cast(ubyte)(x & 0xFF) ^ 0x63 ;
}
return S;
}
これにより、コンパイル時にルックアップテーブルが生成される可能性があります。ubyteを汎用パラメーターにすることで一部を節約できます。
直接編集ubyte
するubyte
配列の検索、分岐のない、完全にunrollableループなし
B[256] S(B:ubyte)(B i){
B mulInv(B x){
B r;
foreach(i;0..256){
B p=0,h,a=i,b=x;
foreach(c;0..8){
p^=(b&1)*a;
h=a>>>7;
a<<=1;
a^=h*0x1b;//h is 0 or 1
b>>=1;
}
if(p==1)r=i;//happens 1 or less times over 256 iterations
}
return r;
}
B s= x=mulInv(i);
foreach(j,0..4){
x^=(s=s<<1|(s>>>7));
}
return x^99;
}
edit2はルックアップテーブルの作成に@Thomasのアルゴリズムを使用しました
ë■ÿÆ♂◄º5hUø√!«mr¿¡ƒR3Å←ç≥#/$taJkαn∩╓▒ÿ╔τy╫π§╪S♫╔┴H╔║Ö
私はSボックスについて特に理解していません。これは、Thomas Pornin(8歳!)ソリューションの変換です。