C:AES FIPS-197 SubBytesテーブルを定数時間コードに置き換えます


17

FIPS-197高度暗号化標準 AESとしても知られているが)、これは、多用なるSubBytesように実装することができ、

unsigned char SubBytes(unsigned char x) {
static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};
return t[x];}

この関数は任意ではありません。これは可逆マッピングであり、ガロア体の反転とそれに続くアフィン変換で構成されます。詳細はすべて、FIPS-197セクション5.1.1またはここのセクション4.2.1(わずかに異なる名前の下)にあります。

テーブルとしての実装の1つの問題は、いわゆるキャッシュタイミング攻撃にさらされることです。

したがって、あなたの使命はSubBytes()、一定の動作を示す上記の機能の正確な代替を考案することです。の入力xに依存するものSubBytesが使用されない場合もそうだと仮定します。

  • 配列インデックスとして、
  • 制御オペランドとしてifwhileforcase、またはオペレータ?:;
  • オペレータの任意のオペランドとして&&||!==!=<><=>=*/%
  • 演算子の右オペランドとして>><<*=/=%=<<=>>=

勝ちのエントリでは、単項演算子のための5の重量と、入力に依存するデータパスで実行されるオペレータの数から得られた最も低いコストを有するもの、であろう-~十分ほど<<1>>1+1-1。他のすべての演算子の重み7、他のカウントとのシフト、または他の定数の加算/サブ(型キャストとプロモーションは無料)。原則として、そのコストはループを展開しても変化せず、入力には依存しませんx。タイブレーカーとして、空白とコメントを削除した後の最短コードでの答えが勝ちます。

UTCの2013年にできるだけ早くエントリを回答として指定する予定です。私はある程度の知識がある言語での回答を検討し、サイズに最適化されていないCへの単純な翻訳としてランク付けします。

好意的なオペレーター、無料のキャストとプロモーション、およびサイズのランキングの最初の省略+1および謝罪-1*単項の場合と乗算の両方で禁止されていることに注意してください。


1
ルックアップは定数としてインライン化できるため無料であることに注意してください。
ピーターテイラー

「2013年初頭、UTC」–日付はタイムゾーンよりも興味深いものではないでしょうか?
パエロエベルマン

@PaŭloEbermann:私の意図は今はっきりしているはずです。
fgrieu

回答:


13

スコア:940 933 926 910、フィールドタワーアプローチ

public class SBox2
{
    public static void main(String[] args)
    {
        for (int i = 0; i < 256; i++) {
            int s = SubBytes(i);
            System.out.format("%02x  ", s);
            if (i % 16 == 15) System.out.println();
        }
    }

    private static int SubBytes(int x) {
        int fwd;
        fwd  = 0x010001 & -(x & 1); x >>= 1; //   7+5+7+5+ | 24+
        fwd ^= 0x1d010f & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0x4f020b & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0x450201 & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0xce080d & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0xa20f0f & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0xc60805 & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0x60070e & -x;                // 7+7+5+     | 19+

        // Running total so far: 229

        int p1;
        {
            int ma = fwd;
            int mb = fwd >> 16;         // 7+         | 7+
            p1  = ma & -(mb&1); ma<<=1; //   7+5+7+5+ | 24+
            p1 ^= ma & -(mb&2); ma<<=1; // 7+7+5+7+5+ | 31+
            p1 ^= ma & -(mb&4); ma<<=1; // 7+7+5+7+5+ | 31+
            p1 ^= ma & -(mb&8);         // 7+7+5+7+   | 26+
            int t = p1 >> 3;            // 7+         | 7+
            p1 ^= (t >> 1) ^ (t & 0xe); // 7+5+7+7+   | 26+
        }

        // Running total so far: 229 + 152 = 381

        int y3, y2, y1, y0;
        {
            int Kinv = (fwd >> 20) ^ p1;     // 7+7+
            int w0 = Kinv & 1; Kinv >>= 1;   // 7+5+
            int w1 = Kinv & 1; Kinv >>= 1;   // 7+5+
            int w2 = Kinv & 1; Kinv >>= 1;   // 7+5+
            int w3 = Kinv & 1;               // 7+

            int t0 = w1 ^ w0 ^ (w2 & w3);      // 7+7+7+
            int t1 = w2 ^ (w0 | w3);           // 7+7+
            int t2 = t0 ^ t1;                  // 7+

            y3 = t2 ^ (t1 & (w1 & w3));        // 7+7+7+
            y2 = t0 ^ (w0 | t2);               // 7+7+
            y1 = w0 ^ w3 ^ (t1 & t0);          // 7+7+7+
            y0 = w3 ^ (t0 | (w1 ^ (w0 | w2))); // 7+7+7+7


        }

        // Running total so far: 381 + 24*7 + 3*5 = 564

        int p2;
        {
            int ma = fwd;
            p2  = ma & -y0; ma<<=1;       //   7+5+5+ | 17+
            p2 ^= ma & -y1; ma<<=1;       // 7+7+5+5+ | 24+
            p2 ^= ma & -y2; ma<<=1;       // 7+7+5+5+ | 24+
            p2 ^= ma & -y3;               // 7+7+5+   | 19+
            int t = p2 >> 3;              // 7+       | 7+
            p2 ^= (t >> 1) ^ (t & 0xe0e); // 7+5+7+7+ | 26
        }

        // Running total so far: 564 + 117 = 681

        int inv8;
        inv8  =  31 & -(p2 & 1);           //   7+5+7+   | 19+
        inv8 ^= 178 & -(p2 & 2); p2 >>= 2; // 7+7+5+7+7+ | 33+
        inv8 ^= 171 & -(p2 & 1);           // 7+7+5+7+   | 26+
        inv8 ^=  54 & -(p2 & 2); p2 >>= 6; // 7+7+5+7+7+ | 33+
        inv8 ^= 188 & -(p2 & 1);           // 7+7+5+7+   | 26+
        inv8 ^=  76 & -(p2 & 2); p2 >>= 2; // 7+7+5+7+7+ | 33+
        inv8 ^= 127 & -(p2 & 1);           // 7+7+5+7+   | 26+
        inv8 ^= 222 & -(p2 & 2);           // 7+7+5+7    | 26+

        return inv8 ^ 0x63;                // 7+         | 7+

        // Grand total: 681 + 229 = 910
    }
}

構造は基本的にBoyarとPeraltaの実装と同じです-GF(2 ^ 8)の反転をGF(2 ^ 4)の反転に減らし、線形プロローグ、非線形ボディ、線形エピローグに分解します。そして、それらを個別に最小化します。私はビット抽出に対していくつかのペナルティを払っていますが、(ビットのいくつかの賢明なパディングでfwd)並行して操作を行うことができることで補償します。さらに詳細に...

バックグラウンド

問題の説明で述べたように、Sボックスはガロア体GF(2 ^ 8)の特定の実装の反転とアフィン変換で構成されます。これらの両方の意味がわかっている場合は、このセクションをスキップしてください。

アフィン(または線形)変換関数であるf(x)f(x + y) = f(x) + f(y)f(a*x) = a*f(x)

フィールドがセットされF、私たちが呼ぶでしょう二つの特別な要素を持つ要素の01我々は呼んでよ、と2つの事業者、+および*様々な性質を尊重しています、。このセクションでは、以下のことを想定xyおよびzの要素ですF

  • 要素F下形アーベル群+0同一のように:すなわちx + yの要素ですFx + 0 = 0 + x = x; それぞれxに対応する-xがありx + (-x) = (-x) + x = 0ます。x + (y + z) = (x + y) + z; およびx + y= y + x
  • 要素F以外0下形アーベル群*1同一です。
  • 乗算は加算で分配されます:x * (y + z) = (x * y) + (x * z)

有限体にはいくつかの非常に厳しい制限があることがわかります。

  • 彼らは素数の力である要素の数を持っている必要があります。
  • 彼らは、同じサイズ(;フィールドGF(P ^ k)は、そのコールすなわちそこに与えられた大きさの唯一の有限体が本当にだし、他のものがちょうどrelabellingsている他のすべての有限体と同型でp素数であるとkパワーです) 。
  • F\{0}下の乗法群*は周期的です。つまりg、すべての要素がのべき乗になるような要素が少なくとも1つありgます。
  • 1より大きい累乗の場合k、素数次の体の次数の単変量多項式として表現されます。たとえば、GF(2 ^ 8)は、xGF(2)を超える多項式の表現を持ちます。実際、通常は複数の表現があります。x^7 * xGF(2 ^ 8)で検討してください。それはいくつかの次数7の多項式と等価でなければなりませんが、どれですか?適切な構造を提供する多くの選択肢があります。AESは、作成することを選択しますx^8 = x^4 + x^3 + x + 1(機能する辞書編集的に最小の多項式)。

それでは、GF(2 ^ 8)の特定の表現でどのように逆行列を計算するのでしょうか?直接取り組むにはあまりにも大きな問題なので、それを分解する必要があります。

フィールドタワー:GF(2 ^ 4)でGF(2 ^ 8)を表す

GF(2 ^ 8)上の8項の多項式でGF(2 ^ 8)を表す代わりに、GF(2 ^ 4)上の2項の多項式で表すことができます。今回は、の線形多項式を選択する必要がありますx^2。を選択するとしますx^2 = px + q。それから(ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq)

この表現で逆行列を見つけるのは簡単ですか?(cx + d) = (ax + b)^-1連立方程式を取得する場合

  • ad + (b + ap)c = 0
  • bd + (aq)c = 1

させD = [b(b+ap) + a^2 q]て設定しc = a * D^-1ます。d = (b + ap) * D^-1。したがって、GF(2 ^ 4)への変換のコストに対してGF(2 ^ 8)で逆変換を行い、GF(2 ^ 4)で逆数回と数回の加算と乗算、および逆変換を行うことができます。テーブルを使用して逆を実行した場合でも、テーブルサイズを256から16に削減しました。

実装の詳細

GF(4)の表現を構築するには、3つの多項式から選択して、以下を削減できますx^4

  • x^4 = x + 1
  • x^4 = x^3 + 1
  • x^4 = x^3 + x^2 + x + 1

最も重要な違いは、乗算の実装です。3つ(poly3、9、fに対応)のいずれについても、以下が機能します。

// 14x &, 7x unary -, 3x <<1, 3x >>1, 3x >>3, 6x ^ gives score 226
int mul(int a, int b) {
    // Call current values a = a0, b = b0
    int p = a & -(b & 1);
    a = ((a << 1) ^ (poly & -(a >> 3))) & 15;
    b >>= 1;
    // Now p = a0 * (b0 mod x); a = a0 x; b = b0 div x

    p ^= a & -(b & 1);
    a = ((a << 1) ^ (poly & -(a >> 3))) & 15;
    b >>= 1;
    // Now p = a0 * (b0 mod x^2); a = a0 x^2; b = b0 div x^2

    p ^= a & -(b & 1);
    a = ((a << 1) ^ (poly & -(a >> 3))) & 15;
    b >>= 1;
    // Now p = a0 * (b0 mod x^3); a = a0 x^3; b = b0 div x^3

    p ^= a & -(b & 1);
    // p = a0 * b0

    return p;
}

ただし、poly = 3適切な構造を持っているため、オーバーフローをより効率的に処理できます。2つの入力が両方ともキュービックでx^6 = x^2 (x + 1)あり、キュービックであるため、ダブルオーバーフローはありません。さらに、b:のシフトを保存できます。これは、オーバーフローを最後まで残しているためa0 x^2xまたは1に対応するビットが設定されていないため、-1ではなく-4でマスクできます。結果は

// 10x &, 4x unary -, 3x <<1, 1x >>1, 1x >>3, 5x ^ gives score 152
int mul(int a, int b) {
    int p;
    p  = a & -(b & 1); a <<= 1;
    p ^= a & -(b & 2); a <<= 1;
    p ^= a & -(b & 4); a <<= 1;
    p ^= a & -(b & 8);
    // Here p = a0 * b0 but with overflow, which we need to bring back down.

    int t = p >> 3;
    p ^= (t >> 1) ^ (t & 0xe);
    return p & 15;
}

我々はまだの値を選択する必要があるpqGFオーバーGF(2 ^ 8)の表現(2 ^ 4)のために、私たちは彼らに多くの制約を持っていません。重要なことは、元の表現のビットから作業用表現のビットまで線形関数を取得できることです。これは2つの理由で重要です。1つ目は、線形変換を行うのが簡単なことです。一方、非線形変換では、Sボックス全体を最適化するのと同等の最適化が必要になります。第二に、いくつかの副次的な利益を得ることができるからです。構造を要約するには:

GF256 SubBytes(GF256 x) {
    GF16 a, b, t, D, Dinv, c, d;

    (a, b) = f(x); // f is linear

    t = b + a * p;
    D = b * t + a * a * q;
    Dinv = inverse_GF16(D);
    c = a * Dinv;
    d = t * Dinv;

    return finv(c, d); // finv is also linear
}

ビットがいる場合xですx7 x6 ... x0我々が得る線形変換であるため、その後a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0})。それを二乗するとa^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2、クロスタームがキャンセルされる場所がわかります(GF(2)で1 + 1 = 0)。のa^2線形関数として計算することもできxます。フォワード線形変換を強化して、以下を取得できます。

GF256 SubBytes(GF256 x) {
    GF16 a, b, t, a2q, D, Dinv, c, d;

    (a, b, t, a2q) = faug(x);

    D = b * t + a2q;
    Dinv = inverse_GF16(D);
    c = a * Dinv;
    d = t * Dinv;

    return finv(c, d);
}

そして、3回の乗算と1回の加算になります。乗算コードを拡張して、2つの乗算Dinvを並列に実行することもできます。したがって、総コストは、前方線形変換、加算、2つの乗算、GF(2 ^ 4)の逆数、および後方線形変換です。S-boxのポスト逆線形変換を実行して、基本的に無料で取得できます。

線形変換の係数の計算はあまり面白くありません。また、ここでマスクとシフトを保存するためのマイクロ最適化も行われません。残りの興味深い部分は、inverse_GF16。4ビットから4ビットまでの2 ^ 64種類の関数があるため、直接最適化するには多くのメモリと時間が必要です。私がやったことは、4ビットから1ビットまでの4つの関数を考慮し、1つの関数に許可される総コストに上限を設定することです(関数あたり最大コスト63で、1分以内にすべての適切な式を列挙できます)、関数の各タプルに対して、共通の部分式の削除を行います。25分間処理した後、そのキャップで可能な限り最高の逆数の合計コストは133であることがわかります(出力のビットあたり平均33.25、個々のビットの最も安い表現が35であることを考えると悪くはありません) 。

私はまだGF(2 ^ 4)の反転を最小化するために他のアプローチを試しています。トップダウンではなくボトムアップを構築するアプローチは133から126に改善をもたらしました。


素晴らしい!動作することを確認しました!詳細:8は、& 1(ESP場合。トリミングすることが可能xであるunsigned charCHAR_BITcodegolf 8です)。
fgrieu

@fgrieu、良い点。
ピーターテイラー

8

スコア:980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7、ボイヤーとペラルタの最小化

Joan BoyarとRenéPeraltaによる暗号への応用を伴う新しい組み合わせ論理最小化手法を見つけまし。これは(C形式を保存します)必要なことを行います。それらの方程式を導き出すために使用される技術は、米国だけで特許を取得しています。ここで親切にリンクされた彼らの方程式のまっすぐなC変換をしまし

unsigned char SubBytes_Boyar_Peralta(unsigned char x7){
  unsigned char 
  x6=x7>>1,x5=x6>>1,x4=x5>>1,x3=x4>>1,x2=x3>>1,x1=x2>>1,x0=x1>>1,
  y14=x3^x5,y13=x0^x6,y9=x0^x3,y8=x0^x5,t0=x1^x2,y1=t0^x7,y4=y1^x3,y12=y13^y14,y2=y1^x0,
  y5=y1^x6,y3=y5^y8,t1=x4^y12,y15=t1^x5,y20=t1^x1,y6=y15^x7,y10=y15^t0,y11=y20^y9,y7=x7^y11,
  y17=y10^y11,y19=y10^y8,y16=t0^y11,y21=y13^y16,y18=x0^y16,t2=y12&y15,t3=y3&y6,t4=t3^t2,
  t5=y4&x7,t6=t5^t2,t7=y13&y16,t8=y5&y1,t9=t8^t7,t10=y2&y7,t11=t10^t7,t12=y9&y11,
  t13=y14&y17,t14=t13^t12,t15=y8&y10,t16=t15^t12,t17=t4^t14,t18=t6^t16,t19=t9^t14,
  t20=t11^t16,t21=t17^y20,t22=t18^y19,t23=t19^y21,t24=t20^y18,t25=t21^t22,t26=t21&t23,
  t27=t24^t26,t28=t25&t27,t29=t28^t22,t30=t23^t24,t31=t22^t26,t32=t31&t30,t33=t32^t24,
  t34=t23^t33,t35=t27^t33,t36=t24&t35,t37=t36^t34,t38=t27^t36,t39=t29&t38,t40=t25^t39,
  t41=t40^t37,t42=t29^t33,t43=t29^t40,t44=t33^t37,t45=t42^t41,z0=t44&y15,z1=t37&y6,
  z2=t33&x7,z3=t43&y16,z4=t40&y1,z5=t29&y7,z6=t42&y11,z7=t45&y17,z8=t41&y10,z9=t44&y12,
  z10=t37&y3,z11=t33&y4,z12=t43&y13,z13=t40&y5,z14=t29&y2,z15=t42&y9,z16=t45&y14,z17=t41&y8,
  t46=z15^z16,t47=z10^z11,t48=z5^z13,t49=z9^z10,t50=z2^z12,t51=z2^z5,t52=z7^z8,t53=z0^z3,
  t54=z6^z7,t55=z16^z17,t56=z12^t48,t57=t50^t53,t58=z4^t46,t59=z3^t54,t60=t46^t57,
  t61=z14^t57,t62=t52^t58,t63=t49^t58,t64=z4^t59,t65=t61^t62,t66=z1^t63,s0=t59^t63,
  s6=t56^t62,s7=t48^t60,t67=t64^t65,s3=t53^t66,s4=t51^t66,s5=t47^t65,s1=t64^s3,s2=t55^t67;
  return (((((((s0<<1|s1&1)<<1|s2&1)<<1|s3&1)<<1|s4&1)<<1|s5&1)<<1|s6&1)<<1|s7&1)^0x63;}

うわー、本当に機能し、本当に安い。分解するとき、プロローグ、エピロージ、ムーブの指示を除いて、実際には144の指示です。
ウゴレン

5

スコア:10965

これは、配列ルックアップを展開する同じ原理を使用します。追加のキャストが必要になる場合があります。

改善方法を指摘してくれたugorenに感謝しis_zeroます。

// Cost: 255 * (5+7+24+7) = 10965
unsigned char SubBytes(unsigned char x) {
    unsigned char r = 0x63;
    char c = (char)x;
    c--; r ^= is_zero(c) & (0x63^0x7c); // 5+7+24+7 inlining the final xor
    c--; r ^= is_zero(c) & (0x63^0x77); // 5+7+24+7
    // ...
    c--; r ^= is_zero(c) & (0x63^0x16); // 5+7+24+7
    return r;
}

// Cost: 24
// Returns (unsigned char)-1 when input is 0 and 0 otherwise
unsigned char is_zero(char c) {
    // Shifting a signed number right is unspecified, so use unsigned
    unsigned char u;
    c |= -c;               // 7+5+
    u = (unsigned char)c;
    u >>= (CHAR_BITS - 1); // 7+
    c = (char)u;
    // c is 0 if we want -1 and 1 otherwise.
    c--;                   // 5
    return (unsigned char)c;
}

2
整数cの場合、(c|-c)>>310の場合は0、それ以外の場合は-1です。
ウゴレン

@ugoren、賢明な言語で、はい。Cでは、符号なしの型の右シフトは指定されていません。
ピーターテイラー

1
私はあなたが署名したことを意味すると思います。しかし、このサイトは厳密な標準準拠で有名ではありません。また、あなたc >> 4は私に署名された右シフトのようです。そして、あなたが本当に主張する場合- ((unsigned int)(c|-c))>>31ですc?1:0
ウゴレン

@ugoren、あなたは正しい、私は署名を意味した。c >>4または符号拡張せずに動作します。しかし、符号なしのシフトを使用すると、家に帰ると編集され、電話ではなく適切なコンピューターを使用できます。ありがとう。
ピーターテイラー

3

スコア:9109、代数的アプローチ

誰でも大幅に改善できる場合に備えて、ルックアップアプローチは残しておきますが、良い代数的アプローチが可能であることがわかります。この実装は、ユークリッドのアルゴリズムを使用して乗法的逆数見つけます。Javaで作成しましたが、原則としてCに移植できます。8ビットタイプのみを使用して修正したい場合に変更される部分についてコメントしました。

短縮方法を指摘してくれたugorenに感謝 is_nonzero私の他の答えのコメントでチェックます。

public class SBox
{
    public static void main(String[] args)
    {
        for (int i = 0; i < 256; i++) {
            int s = SubBytes(i);
            System.out.format("%02x  ", s);
            if (i % 16 == 15) System.out.println();
        }
    }

    // Total cost: 9109
    public static int SubBytes(int x)
    {
        x = inv_euclid(x); // 9041
        x = affine(x);     // 68
        return x;
    }

    // Total cost: 68
    private static int affine(int s0) {
        int s = s0;
        s ^= (s0 << 1) ^ (s0 >> 7); // 5 + 7
        s ^= (s0 << 2) ^ (s0 >> 6); // 7 + 7
        s ^= (s0 << 3) ^ (s0 >> 5); // 7 + 7
        s ^= (s0 << 4) ^ (s0 >> 4); // 7 + 7
        return (s ^ 0x63) & 0xff;   // 7 + 7
    }

    // Does the inverse in the Galois field for a total cost of 24 + 9010 + 7 = 9041
    private static int inv_euclid(int s) {
        // The first part of handling the special case: cost of 24
        int zeromask = is_nonzero(s);

        // NB the special value of r would complicate the unrolling slightly with unsigned bytes
        int r = 0x11b, a = 0, b = 1;

        // Total cost of loop: 7*(29+233+566+503+28) - 503 = 9010
        for (int depth = 0; depth < 7; depth++) { // 7*(
            // Calculate mask to fake out when we're looping further than necessary: cost 29
            int mask = is_nonzero(s >> 1);

            // Cost: 233
            int ord = polynomial_order(s);

            // This next block does div/rem at a total cost of 6*(24+49) + 69 + 59 = 566
            int quot = 0, rem = r;
            for (int i = 7; i > 1; i--) {                   // 6*(
                int divmask = is_nonzero(ord & (rem >> i)); // 24+7+7
                quot ^= (1 << i) & divmask;                 // 7+0+7+ since 1<<i is inlined on unrolling
                rem ^= (s << i) & divmask;                  // 7+7+7) +
            }
            int divmask1 = is_nonzero(ord & (rem >> 1));    // 24+7+5
            quot ^= 2 & divmask1;                           // 7+7+
            rem ^= (s << 1) & divmask1;                     // 7+5+7+
            int divmask0 = is_nonzero(ord & rem);           // 24+7
            quot ^= 1 & divmask0;                           // 7+7+
            rem ^= s & divmask0;                            // 7+7

            // This next block does the rest for the cost of a mul (503) plus 28
            // When depth = 0, b = 1 so we can skip the mul on unrolling
            r = s;
            s = rem;
            quot = mul(quot, b) ^ a;
            a = b;
            b ^= (quot ^ b) & mask;
        }

        // The rest of handling the special case: cost 7
        return b & zeromask;
    }

    // Gets the highest set bit in the input. Assumes that it's always at least 1<<1
    // Cost: 233
    private static int polynomial_order(int s) {
        int ord = 2;
        ord ^= 6 & -((s >> 2) & 1);           // 7+7+5+7+7 = 33 +
        ord ^= (ord ^ 8) & -((s >> 3) & 1);   // 7+7+7+5+7+7 = 40 +
        ord ^= (ord ^ 16) & -((s >> 4) & 1);  // 40 +
        ord ^= (ord ^ 32) & -((s >> 5) & 1);  // 40 +
        ord ^= (ord ^ 64) & -((s >> 6) & 1);  // 40 +
        ord ^= (ord ^ 128) & -((s >> 7) & 1); // 40
        return ord;
    }

    // Returns 0 if c is 0 and -1 otherwise
    // Cost: 24
    private static int is_nonzero(int c) {
        c |= -c;   // 7+5+
        c >>>= 31; // 7+ (simulating a cast to unsigned and right shift by CHAR_BIT)
        c = -c;    // 5+ (could be saved assuming a particular implementation of signed right shift)
        return c;
    }

    // Performs a multiplication in the Rijndael finite field
    // Cost: 503 (496 if working with unsigned bytes)
    private static int mul(int a, int b) {
        int p = 0;
        for (int counter = 0; counter < 8; counter++) { // 8*(_+_
            p ^= a & -(b & 1);                          // +7+7+5+7
            a = (a << 1) ^ (0x1b & -(a >> 7));          // +5+7+7+5+7
            b >>= 1;                                    // +5)
        }
        p &= 0xff;                                      // +7 avoidable with unsigned bytes
        return p;
    }
}

2

スコア:256 *(7+(8 *(7 + 7 + 7)-(2 + 2))+ 5 + 7 + 7)= 48640(ループが展開されていると仮定)

unsigned char SubBytes(unsigned char x) {
static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};

unsigned char ret_val = 0;
int i,j;
for(i=0;i<256;i++) {
  unsigned char is_index = (x ^ ((unsigned char) i));
  for(j=0;j<8;j++) {
   is_index |= (is_index << (1 << j)) | (is_index >> (1 << j));
  }
  is_index = ~is_index;
  ret_val |= is_index & t[i];
}

return ret_val;}

説明:

基本的に、ビットごとの演算子を使用して、常に配列全体を処理する配列ルックアップを再実装しました。配列をループし、xorx各インデックスでを実行し、ビットごとの演算子を使用して結果を論理的に否定しx=iます。選択された値は変更されず、他は0になります。その後、この配列のビット単位のORを取得し、選択された値のみを引き出します。

2つの1 << j操作は、ループの展開の一部としてなくなり、1から128までの2の累乗に置き換えられます。


次に、ビットごとの演算子を使用して実際に数学を実行できるかどうかを確認します。
histocrat

ここでアルゴリズムに目を通すと、いくつかの多項式時間ステップの代わりに、少なくとも一度はすべてのバイトをループすることなく、多項式反転を実装することが可能になるのではないでしょうか。そのため、これは「スマート」ソリューションに勝る可能性があります。この一定時間の配列ルックアップのチューニングは、より有望な方法だと思います。
histocrat

いいね ここの aes.cの関数rj_sbox は、インスピレーションを与える可能性があります(ただし、そのままでは質問に一致しません)。
fgrieu

どこん-(2+2)あなたの得点計算ではから来ますか?編集:ああ、インライン化が作成されます<<1>>1
ピーターテイラー

0

ルックアップテーブルを使用して、スコア1968 1692

注:のため、このソリューションは基準に合格しませんw >> b

ルックアップテーブルを使用しますが、一度に8バイトを読み取ります。
3 * 7 + 32 *(6 * 7 + 2 * 5)+ 7 = 692

unsigned char SubBytes(unsigned char x){

static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};

  unsigned long long *t2 = (unsigned long long *)t;
  int a = x>>3, b=(x&7)<<3;                       // 7+7+7
  int i;
  int ret = 0;
  for (i=0;i<256/8;i++) {                         // 32 *
      unsigned long long w = t2[i];
      int badi = ((unsigned int)(a|-a))>>31;      // 7+7+5
      w &= (badi-1);                              // +7+7
      a--;                                        // +5
      ret |= w >> b;                              // +7+7
  }
  return ret & 0xff;                              // +7
}

ので、私は、これが問題の時定数の定義を満たしているとは思わないw>>bRHSから算出していますx
ピーター・テイラー

いくつかの違反があります。入力w >> bb依存する場所。またx/8x%8、と*= (1-badi)。最初のものは、特にローエンドCPUのタイミング依存性に縮退する可能性があります。ただし、ワイド変数を使用するというアイデアには確かに可能性があります。
fgrieu

私は指示を十分に注意深く読みませんでした。ほとんどの問題を修正できますが、w >> b非常に重要です(すべてを書き直さずに修正できるかどうかを確認する必要があります。
ugoren12年

0

テーブル検索とマスク、スコア= 256 *(5 * 7 + 1 * 5)= 10240

マスクを使用したテーブル検索を使用して、必要な結果のみを選択します。j|-j負の値(i!= xの場合)またはゼロ(i == xの場合)のいずれかを使用します。シフトすると、必要なエントリのみを選択するために使用される、オールワンまたはオールゼロのマスクが作成されます。

static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};

unsigned char SubBytes(unsigned char x) {
  unsigned char r = 255;
  for (int i = 0; i < 256; i++) {
    int j = i - x;
    r &= t[i] | ((j | -j) >> 31);
  }
  return r;
}

最適化されていないことを除けば、これは2番目の回答と同じではありませんか
ピーターテイラー

近いですね。符号付きシフトを使用しているため、最後に-1を実行する必要はありません。
キースランドール
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.