RijndaelのSボックスを実装する


15

RijndaelのS-boxは、AES暗号化および復号化で頻繁に使用される操作です。通常、256バイトのルックアップテーブルとして実装されます。これは高速ですが、コード内で256バイトのルックアップテーブルを列挙する必要があることを意味します。基本的な数学的構造を考えると、この群衆の誰かがより少ないコードでそれを行うことができると確信しています。

RijndaelのSボックスを実装するお気に入りの言語で関数を記述します。最短のコードが優先されます。


1
結果の関数が一定時間である場合(つまり、データ依存のコードパスや配列アクセス、または言語がサポートするものが何もない場合)のボーナスポイント(私からの賛成)。
パエロエベルマン

@PaŭloEbermann配列アクセスは、多くの言語で一定の時間です(ポインターに(スケーリングされた)値を追加し、それを参照解除するため、ルックアップテーブルが非常に高速です)
ラチェットフリーク

@ratchetfreak配列アクセスはO(1)ですが、実際のアクセス時間はキャッシュのヒットまたはミスに依存します。たとえば、AESのサイドチャネル攻撃につながります。
パエロエベルマン

@PaŭloEbermann、ただし、短いコードを使用してルックアップテーブルを埋めることができます。これにより、メモリのページの下にうまく収まります。
ピーターテイラー

@PaŭloEbermannおよび256の長さのテーブルが(コンパイル時に生成されるenumとして)コードに沿って保存されている場合、キャッシュヒットをほぼ保証しました
ラチェットフリーク

回答:


6

ルビー、161文字

R=0..255
S=R.map{|t|t=b=R.select{|y|x=t;z=0;8.times{z^=y*(x&1);x/=2;y*=2};r=283<<8;8.times{r/=2;z^r<z/2&&z^=r};z==1}[0]||0;4.times{|r|t^=b<<1+r^b>>4+r};t&255^99}

出力を確認するには、次のコードを使用して表形式で印刷します。

S.map{|x|"%02x"%x}.each_slice(16){|l|puts l*' '}

7

GolfScript、60文字

{[[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されて最終出力が生成されます。


7

x86-64マシンコード-23 22 20 19バイト

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最初のバイトに影響を与えないため、逆にする必要はありません。


2
一度、x86_64は数学を引き出し、そのための組み込み機能を備えています。
moonheart08

6

対数を使用することにより、有限体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
ラチェットフリーク

@ratchetfreak:いいえ、0x11Bでなければなりません(試しました)。int型は、Javaで32ビットです。上位ビットをキャンセルする必要があります。
トーマスポーリン

ああ、それを知らなかった
ラチェットフリーク

4行目の>>ではなく>>>ですか?
ジョーZ.

@JoeZeng:両方とも機能します。Javaでは、「>>>」は「符号なしシフト」、「>>」は「符号付きシフト」です。符号ビットの処理方法が異なります。ここで、値は符号ビットがゼロ以外になるのに十分な幅になることはないため、実際の違いはありません。
トーマスポーリン

6

GolfScript(82文字)

{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行でもタイムアウトになります。


私の方が速い(そして短い;)。とにかく+1。
イルマリカロネン

4

Python、176文字

この答えは、関数を一定時間にすることについての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

定数時間の乗算はプラットフォームに依存します(32ビットプラットフォーム、たとえばARM Cortex M0でも)。この関連質問を
-fgrieu

1
@fgrieuもちろんですが、これらはすべて定数による乗算であり、シフトと加算を使用して一定時間で簡単に実装できます。
キースランドール

2

d

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のアルゴリズムを使用しました


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.