C: thay thế bảng SubBytes AES Trin-197 bằng mã thời gian không đổi


17

Trong Trin-197 ( Tiêu chuẩn mã hóa nâng cao , được gọi là AES), nó được sử dụng rất nhiều SubBytes, có thể được triển khai như

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];}

Chức năng này không phải là tùy ý; nó là một ánh xạ đảo ngược, bao gồm một đảo ngược trong Trường Galois theo sau là một phép biến đổi affine. Tất cả các chi tiết nằm trong phần Trin-197 5.1.1 hoặc ở đây phần 4.2.1 (dưới một tên hơi khác).

Một vấn đề với việc thực hiện như một bảng là nó mở ra cái gọi là các cuộc tấn công thời gian bộ đệm .

Do đó, nhiệm vụ của bạn là đưa ra một sự thay thế chính xác cho SubBytes()chức năng trên , thể hiện hành vi không đổi thời gian; chúng tôi sẽ giả định đó là trường hợp khi không có gì phụ thuộc vào đầu vào xcủa SubBytesđược sử dụng một trong hai:

  • như một chỉ số mảng,
  • như kiểm soát toán hạng của if, while, for, case, hoặc nhà điều hành ?:;
  • như bất kỳ toán hạng của các nhà khai thác &&, ||, !, ==, !=, <, >, <=, >=, *, /, %;
  • là toán hạng bên phải của các nhà khai thác >>, <<, *=, /=, %=, <<=, >>=.

Thiết kế thắng cuộc sẽ là một với chi phí thấp nhất, thu được từ số lượng khai thác thực hiện trong đường dẫn dữ liệu đầu vào phụ thuộc, với trọng lượng 5 cho các nhà khai thác unary -~cũng như cho <<1, >>1, +1, -1; trọng số 7 cho tất cả các nhà khai thác khác, thay đổi với số lượng khác hoặc thêm / phụ của các hằng số khác (loại phôi và chương trình khuyến mãi là miễn phí). Theo nguyên tắc, chi phí đó không thay đổi bằng cách bỏ các vòng lặp (nếu có) và độc lập với đầu vào x. Là một break-breaker, câu trả lời với mã ngắn nhất sau khi xóa khoảng trắng và các bình luận sẽ thắng.

Tôi dự định chỉ định một mục là câu trả lời sớm nhất có thể trong năm 2013, UTC. Tôi sẽ xem xét các câu trả lời bằng các ngôn ngữ mà tôi có một số kiến ​​thức, xếp hạng chúng là bản dịch thẳng sang C không được tối ưu hóa cho kích thước.

Xin lỗi về sự thiếu sót ban đầu của +1-1trong các nhà khai thác ưa thích, các diễn viên và chương trình khuyến mãi miễn phí, và xếp hạng kích thước. Lưu ý rằng *bị cấm cả khi unary và nhân.


1
Đáng lưu ý rằng việc tra cứu là miễn phí vì bạn có thể đặt chúng dưới dạng hằng số.
Peter Taylor

"Đầu năm 2013, UTC" - ngày này có thú vị hơn múi giờ không?
Paŭlo Ebermann

@ PaŭloEbermann: Ý định của tôi bây giờ nên rõ ràng.
fgrieu

Câu trả lời:


13

Điểm: 940 933 926 910, cách tiếp cận tháp hiện trường

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
    }
}

Cấu trúc về cơ bản giống như triển khai của Boyar và Peralta - giảm nghịch đảo trong GF (2 ^ 8) thành đảo ngược trong GF (2 ^ 4), chia nó thành một đoạn mở đầu tuyến tính, cơ thể phi tuyến tính và một đoạn kết tuyến tính, và sau đó giảm thiểu chúng một cách riêng biệt. Tôi trả một số hình phạt cho việc trích xuất bit, nhưng tôi bù lại bằng cách có thể thực hiện các thao tác song song (với một số phần đệm hợp lý của các bit của fwd). Chi tiết hơn...

Lý lịch

Như đã đề cập trong phần mô tả vấn đề, hộp S bao gồm một phép đảo ngược trong một triển khai cụ thể của trường Galois GF (2 ^ 8) theo sau là một phép biến đổi affine. Nếu bạn biết cả hai điều đó có nghĩa gì, hãy bỏ qua phần này.

Một phép biến đổi affine (hoặc tuyến tính) là một hàm f(x)tôn trọng f(x + y) = f(x) + f(y)f(a*x) = a*f(x).

Trường là một tập hợp Fcác phần tử có hai phần tử đặc biệt, chúng ta sẽ gọi 01, và hai toán tử, chúng ta sẽ gọi +*, trong đó tôn trọng các thuộc tính khác nhau. Trong phần này, giả sử rằng x, yznhững yếu tố của F.

  • Các yếu tố Fhình thành một nhóm abelian dưới +với 0danh tính: tức x + ylà một yếu tố của F; x + 0 = 0 + x = x; mỗi cái xcó một cái tương ứng -xsao cho x + (-x) = (-x) + x = 0; x + (y + z) = (x + y) + z; và x + y= y + x.
  • Các yếu tố Fkhác hơn là 0tạo thành một nhóm abelian dưới *với 1danh tính.
  • Phép nhân phân phối trên phép cộng : x * (y + z) = (x * y) + (x * z).

Nó chỉ ra rằng có một số hạn chế khá nghiêm trọng trên các lĩnh vực hữu hạn:

  • Họ phải có một số yếu tố là sức mạnh của một nguyên tố.
  • Chúng là đẳng cấu với tất cả các trường hữu hạn khác có cùng kích thước (nghĩa là thực sự chỉ có một trường hữu hạn có kích thước nhất định và bất kỳ trường nào khác chỉ là tương đối; gọi trường đó là GF (p ^ k) trong đó plà số nguyên tố và klà lũy thừa) .
  • Nhóm nhân F\{0}dưới theo *chu kỳ; tức là có ít nhất một yếu tố gsao cho mọi yếu tố đều là sức mạnh của g.
  • Đối với các lũy thừa lớn hơn 1, có một đại diện là đa thức đơn biến của thứ tự kcủa trường bậc nhất. Ví dụ: GF (2 ^ 8) có một đại diện về đa thức của xhơn GF (2). Trong thực tế thường có nhiều hơn một đại diện. Xem xét x^7 * xtrong GF (2 ^ 8); nó phải tương đương với một số đa thức bậc 7, nhưng cái nào? Có rất nhiều sự lựa chọn đưa ra cấu trúc phù hợp; AES chọn thực hiện x^8 = x^4 + x^3 + x + 1(đa thức nhỏ nhất về mặt từ vựng hoạt động).

Vậy làm thế nào để chúng ta tính toán một nghịch đảo trong đại diện cụ thể của GF (2 ^ 8)? Đó là một vấn đề quá khó khăn để giải quyết trực tiếp, vì vậy chúng tôi cần phải phá vỡ nó.

Tháp thực địa: đại diện cho GF (2 ^ 8) về mặt GF (2 ^ 4)

Thay vì đại diện cho GF (2 ^ 8) với đa thức 8 số hạng so với GF (2), chúng ta có thể biểu diễn nó với đa thức 2 thuật ngữ so với GF (2 ^ 4). Lần này chúng ta cần chọn một đa thức tuyến tính cho x^2. Giả sử chúng ta chọn x^2 = px + q. Sau đó (ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq).

Là dễ dàng hơn để tìm thấy một nghịch đảo trong đại diện này? Nếu (cx + d) = (ax + b)^-1chúng ta có được các phương trình đồng thời

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

Hãy để D = [b(b+ap) + a^2 q]và thiết lập c = a * D^-1; d = (b + ap) * D^-1. Vì vậy, chúng ta có thể thực hiện nghịch đảo trong GF (2 ^ 8) cho chi phí chuyển đổi thành GF (2 ^ 4), nghịch đảo và một vài bổ sung và nhân trong GF (2 ^ 4) và chuyển đổi trở lại. Ngay cả khi chúng tôi thực hiện nghịch đảo bằng bảng, chúng tôi đã giảm kích thước bảng từ 256 xuống 16.

Chi tiết thực hiện

Để xây dựng một đại diện của GF (4), chúng ta có thể chọn giữa ba đa thức để giảm x^4:

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

Sự khác biệt quan trọng nhất là trong việc thực hiện phép nhân. Đối với bất kỳ trong số ba (tương ứng với poly3, 9, f), những điều sau đây sẽ hoạt động:

// 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;
}

Tuy nhiên, nếu chúng ta chọn, poly = 3chúng ta có thể xử lý tràn hiệu quả hơn nhiều vì nó có cấu trúc đẹp: không có tràn kép, vì hai đầu vào đều là hình khối và x^6 = x^2 (x + 1)cũng là hình khối. Ngoài ra, chúng ta có thể lưu các thay đổi của b: vì chúng ta sẽ để tràn quá mức, a0 x^2không có bất kỳ bit nào được đặt tương ứng với xhoặc 1 và vì vậy chúng ta có thể che dấu nó bằng -4 thay vì -1. Kết quả là

// 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;
}

Chúng ta vẫn cần chọn các giá trị pqcho đại diện của GF (2 ^ 8) so với GF (2 ^ 4), nhưng chúng ta không có nhiều ràng buộc đối với chúng. Một điều quan trọng là chúng ta có thể có được một hàm tuyến tính từ các bit của biểu diễn ban đầu của chúng ta đến các bit của biểu diễn làm việc. Điều này quan trọng vì hai lý do: thứ nhất, thật dễ dàng để thực hiện các phép biến đổi tuyến tính, trong khi đó một phép biến đổi phi tuyến tính sẽ yêu cầu tối ưu hóa tương đương khó khăn để chỉ tối ưu hóa toàn bộ hộp chữ S; thứ hai, bởi vì chúng ta có thể nhận được một số lợi ích phụ. Tóm lại cấu trúc:

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
}

Nếu các bit của xx7 x6 ... x0sau đó kể từ khi chuyển đổi là tuyến tính chúng ta có được a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0}). Bình phương nó và chúng ta có được a^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2nơi hủy bỏ các điều khoản chéo (vì trong GF (2), 1 + 1 = 0). Vì vậy, a^2cũng có thể được tính là một hàm tuyến tính của x. Chúng ta có thể gia tăng biến đổi tuyến tính chuyển tiếp để có được:

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);
}

và chúng tôi xuống đến ba phép nhân và một phép cộng. Chúng ta cũng có thể mở rộng mã nhân để thực hiện hai phép nhân Dinvsong song. Vì vậy, tổng chi phí của chúng tôi là một chuyển đổi tuyến tính chuyển tiếp, một phép cộng, hai phép nhân, nghịch đảo trong GF (2 ^ 4) và một phép biến đổi tuyến tính ngược. Chúng ta có thể cuộn trong chuyển đổi tuyến tính sau nghịch đảo của hộp S và có được điều đó về cơ bản là miễn phí.

Việc tính toán các hệ số cho các phép biến đổi tuyến tính không thú vị lắm, và cũng không phải là tối ưu hóa vi mô để lưu mặt nạ ở đây và thay đổi ở đó. Phần thú vị còn lại là tối ưu hóainverse_GF16. Có 2 ^ 64 chức năng khác nhau từ 4 bit đến 4 bit, vì vậy việc tối ưu hóa trực tiếp đòi hỏi nhiều bộ nhớ và thời gian. Những gì tôi đã làm là xem xét 4 hàm từ 4 bit đến 1 bit, đặt giới hạn trên tổng chi phí được phép cho bất kỳ một hàm nào (với chi phí tối đa là 63 cho mỗi hàm tôi có thể liệt kê tất cả các biểu thức phù hợp trong vòng một phút), và đối với mỗi bộ chức năng thực hiện loại bỏ phổ biến phụ. Sau 25 phút giòn, tôi thấy rằng nghịch đảo tốt nhất có thể với nắp đó có tổng chi phí là 133 (trung bình 33,25 mỗi bit của đầu ra, điều này không tệ khi xem xét biểu thức rẻ nhất cho bất kỳ bit riêng lẻ nào là 35) .

Tôi vẫn đang thử nghiệm các cách tiếp cận khác để giảm thiểu nghịch đảo trong GF (2 ^ 4), và cách tiếp cận xây dựng từ dưới lên thay vì từ trên xuống đã mang lại sự cải thiện từ 133 đến 126.


Tuyệt diệu! Tôi xác nhận nó hoạt động! Một chi tiết: số 8 & 1có thể được cắt bớt (đặc biệt nếu xunsigned char; CHAR_BITlà 8 trong codegolf).
fgrieu

@fgrieu, điểm tốt.
Peter Taylor

8

Điểm: 980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7, tối thiểu hóa Boyar và Peralta

Tôi đã tìm thấy Một kỹ thuật tối thiểu hóa logic tổ hợp mới với các ứng dụng cho mật mã học của Joan Boyar và René Peralta, (tiết kiệm cho chủ nghĩa hình thức C) thực hiện những gì cần thiết. Kỹ thuật được sử dụng để rút ra phương trình của họ được cấp bằng sáng chế không kém Hoa Kỳ. Tôi vừa thực hiện một bản dịch C thẳng của phương trình của họ , vui lòng liên kết ở đây .

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;}

Wow, thực sự hoạt động, và thực sự rẻ. Khi tháo rời, đó thực sự là 144 hướng dẫn, không bao gồm hướng dẫn mở đầu, ngoại truyện và di chuyển.
ugoren

5

Điểm: 10965

Điều này sử dụng cùng một nguyên tắc hủy bỏ việc tra cứu mảng. Có thể yêu cầu thêm phôi.

Cảm ơn ugoren đã chỉ ra làm thế nào để cải thiện 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
Đối với một số nguyên c, (c|-c)>>31là 0 cho 0 và -1 khác.
ugoren

@ugoren, trong các ngôn ngữ hợp lý, vâng. Trong C, dịch chuyển sang phải một kiểu không dấu là không xác định.
Peter Taylor

1
Tôi đoán bạn có nghĩa là đã ký. Nhưng trang web này không thực sự nổi tiếng vì tuân thủ tiêu chuẩn nghiêm ngặt. Ngoài ra, bạn c >> 4có vẻ như đã ký hợp đồng chuyển sang tôi. Và nếu bạn thực sự nhấn mạnh - ((unsigned int)(c|-c))>>31c?1:0.
ugoren

@ugoren, bạn nói đúng, ý tôi là đã ký. Các c >>4công trình có hoặc không có dấu hiệu mở rộng. Nhưng nắm bắt tốt khi sử dụng ca không dấu: sẽ chỉnh sửa khi tôi về nhà và có thể sử dụng máy tính phù hợp thay vì điện thoại. Cảm ơn.
Peter Taylor

3

Điểm: 9109, phương pháp đại số

Tôi sẽ rời khỏi phương pháp tra cứu trong trường hợp bất cứ ai cũng có thể cải thiện nó một cách quyết liệt, nhưng hóa ra phương pháp đại số tốt là có thể. Việc triển khai này tìm thấy nghịch đảo nhân với thuật toán của Euclid . Tôi đã viết nó bằng Java nhưng về nguyên tắc nó có thể được chuyển sang C - Tôi đã nhận xét các phần sẽ thay đổi nếu bạn muốn làm lại nó chỉ bằng các loại 8 bit.

Cảm ơn ugoren đã chỉ ra làm thế nào để rút ngắn is_nonzerokiểm tra trong một nhận xét về câu trả lời khác của tôi.

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

Điểm: 256 * (7+ (8 * (7 + 7 + 7) - (2 + 2)) + 5 + 7 + 7) = 48640 (giả sử các vòng lặp không được kiểm soát)

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;}

Giải trình:

Về cơ bản, việc tra cứu mảng được thực hiện lại bằng các toán tử bitwise và luôn xử lý toàn bộ mảng. Chúng tôi lặp qua mảng và xor xvới từng chỉ mục, sau đó sử dụng các toán tử bitwise để phủ định một cách hợp lý kết quả, vì vậy chúng tôi nhận được 255 khi x=ivà 0 nếu không. Chúng tôi bitwise - và giá trị này với giá trị mảng, sao cho giá trị được chọn không thay đổi và các giá trị khác trở thành 0. Sau đó, chúng tôi lấy bitwise - hoặc của mảng này, do đó chỉ rút ra giá trị được chọn.

Hai 1 << jhoạt động sẽ biến mất như một phần của việc hủy kiểm soát vòng lặp, thay thế chúng bằng các quyền hạn từ 2 đến 128.


Bây giờ để xem liệu có thể thực sự làm toán bằng các toán tử bitwise không.
lịch sử

Nhìn qua thuật toán ở đây , tôi nghi ngờ rằng sẽ có thể thực hiện phép đảo ngược đa thức mà không lặp qua tất cả các byte ít nhất một lần để thay thế cho một số bước thời gian đa thức. Vì vậy, điều này có thể đánh bại bất kỳ giải pháp "thông minh". Tôi nghi ngờ điều chỉnh tra cứu mảng thời gian liên tục này là một con đường hứa hẹn hơn.
lịch sử

Đẹp. Hàm rj_sbox trong aes.c ở đây có thể mang lại cảm hứng (mặc dù, như vậy, nó không phù hợp với câu hỏi).
fgrieu

Trường hợp -(2+2)tính toán tính điểm của bạn đến từ đâu? Chỉnh sửa: ah, nội tuyến tạo ra một <<1và a >>1.
Peter Taylor

0

Điểm số 1968 1692, sử dụng bảng tra cứu

Lưu ý: Giải pháp này không vượt qua các tiêu chí, bởi vì w >> b.

Sử dụng bảng tra cứu, nhưng đọc 8 byte mỗi lần.
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
}

Tôi không nghĩ rằng điều này đáp ứng định nghĩa về thời gian không đổi trong câu hỏi, bởi vì w>>bRHS đã tính từx
Peter Taylor

Có một số vi phạm: w >> bnơi bphụ thuộc vào đầu vào; cũng x/8, x%8*= (1-badi). Cái đầu tiên đặc biệt có khả năng suy biến thành sự phụ thuộc thời gian vào các CPU cấp thấp. Tuy nhiên, ý tưởng sử dụng các biến rộng chắc chắn có tiềm năng.
fgrieu

Tôi đã không đọc hướng dẫn đủ cẩn thận. Tôi có thể khắc phục hầu hết các sự cố, nhưng w >> bkhá cần thiết (tôi cần xem liệu có thể khắc phục được không mà không cần viết lại mọi thứ.
ugoren

0

Tra cứu bảng và mặt nạ, điểm = 256 * (5 * 7 + 1 * 5) = 10240

Sử dụng tra cứu bảng với mặt nạ để chọn ra kết quả mà chúng ta muốn. Sử dụng thực tế j|-jlà âm (khi i! = X) hoặc 0 (khi i == x). Dịch chuyển làm cho mặt nạ tất cả một hoặc tất cả được sử dụng để chỉ chọn ra mục chúng ta muốn.

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;
}

Đây không giống như câu trả lời thứ hai của tôi ngoại trừ ít được tối ưu hóa sao?
Peter Taylor

Đóng, tôi đoán. Tôi đang sử dụng ca đã ký nên cuối cùng tôi không phải thực hiện -1.
Keith Randall
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.