C: zastąp tabelę AES FIPS-197 SubBytes kodem o stałym czasie

17

W FIPS-197 ( Advanced Encryption Standard , znany jako AES), jest często używany SubBytes, który można zaimplementować jako

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

Ta funkcja nie jest arbitralna; jest odwracalnym odwzorowaniem, polegającym na odwróceniu w polu Galois, po którym następuje afiniczna transformacja. Wszystkie szczegóły znajdują się w FIPS-197 sekcja 5.1.1 lub tutaj sekcja 4.2.1 (pod nieco inną nazwą).

Jednym problemem z implementacją jako tabelą jest to, że otwiera się ona na tak zwane ataki synchronizacji pamięci podręcznej .

Zatem waszą misją jest wymyślenie dokładnego zamiennika powyższej SubBytes()funkcji, która przejawia zachowanie w czasie stałym; będziemy zakładać, że to przypadek, gdy nic w zależności od wejścia xw SubBytessłuży albo:

  • jako indeks tablicowy
  • jako kontrola operandu if, while, for, caselub operator ?:;
  • jak każdy operand operatorów &&, ||, !, ==, !=, <, >, <=, >=, *, /, %;
  • jako prawy operand operatorów >>, <<, *=, /=, %=, <<=, >>=.

Zwycięski projekt będzie jednym z najniższych kosztach, uzyskane od liczby wykonawców zawartych w ścieżce danych wejściowych zależne, o wadze 5 dla operatorów jednoargumentowych -i ~jak również <<1, >>1, +1, -1; waga 7 dla wszystkich innych operatorów, zmiany z innymi liczbami lub dodawanie / dodawanie innych stałych (rzutowania typu i promocje są bezpłatne). Zasadniczo koszt ten pozostaje niezmieniony przez rozwinięcie pętli (jeśli występują) i jest niezależny od danych wejściowych x. Jako remis wygrywa odpowiedź z najkrótszym kodem po usunięciu białych znaków i komentarzy.

Planuję wyznaczyć wpis jako odpowiedź tak wcześnie, jak to możliwe w roku 2013, UTC. Rozważę odpowiedzi w językach, o których wiem trochę, uszeregowując je jako proste tłumaczenie na C niezoptymalizowane pod względem wielkości.

Przeprosiny dla początkowej zaniechania +1i -1na uprzywilejowanych operatorów, wolnych odlewów i promocjach oraz rankingu wielkości. Należy pamiętać, że *jest to zabronione zarówno w przypadku jedności, jak i mnożenia.

fgrieu
źródło
1
Warto zauważyć, że wyszukiwania są bezpłatne, ponieważ można wstawić je jako stałe.
Peter Taylor,
„na początku 2013 roku, UTC” - czy data nie byłaby bardziej interesująca niż strefa czasowa?
Paŭlo Ebermann
@ PaŭloEbermann: Mój zamiar powinien być teraz jasny.
fgrieu
Zobacz także codegolf.stackexchange.com/questions/3766/…
Keith Randall

Odpowiedzi:

13

Wynik: 940 933 926 910, podejście do wieży polowej

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

Struktura jest zasadniczo taka sama jak implementacja Boyara i Peralty - zmniejsz inwersję w GF (2 ^ 8) do inwersji w GF (2 ^ 4), podziel ją na liniowy prolog, nieliniowy korpus i liniowy epilog, a następnie zminimalizować je osobno. Płacę pewne kary za ekstrakcję bitów, ale rekompensuję to, że mogę wykonywać operacje równolegle (z pewnym rozsądnym dopełnianiem bitów fwd). Bardziej szczegółowo...

tło

Jak wspomniano w opisie problemu, S-box składa się z inwersji w konkretnej implementacji pola Galois GF (2 ^ 8), po której następuje transformacja afiniczna. Jeśli wiesz, co oba oznaczają, pomiń tę sekcję.

Transformacja afiniczna (lub liniowa) jest funkcją, f(x)która szanuje f(x + y) = f(x) + f(y)i f(a*x) = a*f(x).

Pole to zestaw Felementów z dwoma specjalnymi elementami, które nazwiemy 0i 1oraz dwoma operatorami, które wywołamy +i *które szanują różne właściwości. W tej części zakładamy, że x, yi zsą elementami F.

  • Te elementy Ftworzą Abelowych grupą zgodnie +z 0jak tożsamość IE x + yjest elementem F; x + 0 = 0 + x = x; każdy xma odpowiedni -xtaki, że x + (-x) = (-x) + x = 0; x + (y + z) = (x + y) + z; i x + y= y + x.
  • Elementy Finne niż 0tworzą Abelowych grupą zgodnie *z 1jak tożsamość.
  • Mnożenie dystrybuuje ponad Ponadto: x * (y + z) = (x * y) + (x * z).

Okazuje się, że istnieją pewne dość poważne ograniczenia pól skończonych:

  • Muszą mieć wiele elementów, które są siłą liczb pierwszych.
  • Są izomorficzne z wszystkimi innymi skończonymi polami o tym samym rozmiarze (tzn. Tak naprawdę jest tylko jedno skończone pole o danym rozmiarze, a wszelkie inne są tylko etykietami; nazwij to pole GF (p ^ k) gdzie pjest liczbą pierwszą i kjest mocą) .
  • Multiplikatywna grupa F\{0}pod *cykliczny; tzn. istnieje co najmniej jeden element gtaki, że każdy element ma moc g.
  • Dla potęg większych niż 1 istnieje reprezentacja jako jednowymiarowe wielomiany rzędu kpola pierwszego rzędu. Np. GF (2 ^ 8) ma reprezentację w postaci wielomianów xponad GF (2). W rzeczywistości jest zwykle więcej niż jedna reprezentacja. Rozważ x^7 * xw GF (2 ^ 8); musi to być odpowiednik jakiegoś wielomianu rzędu 7, ale który? Istnieje wiele wyborów, które dają odpowiednią strukturę; AES decyduje się na wykonanie x^8 = x^4 + x^3 + x + 1(leksykograficznie najmniejszy wielomian, który działa).

Jak więc obliczyć odwrotność w tej konkretnej reprezentacji GF (2 ^ 8)? Jest to zbyt masywny problem, aby go rozwiązać bezpośrednio, więc musimy go rozwiązać.

Wieże polowe: reprezentujące GF (2 ^ 8) pod względem GF (2 ^ 4)

Zamiast reprezentować GF (2 ^ 8) z wielomianami 8 terminów nad GF (2), możemy przedstawić go wielomianami 2 terminów nad GF (2 ^ 4). Tym razem musimy wybrać liniowy wielomian dla x^2. Załóżmy, że wybieramy x^2 = px + q. Potem (ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq).

Czy łatwiej jest znaleźć odwrotność w tej reprezentacji? Jeśli (cx + d) = (ax + b)^-1otrzymamy równoczesne równania

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

Pozwól D = [b(b+ap) + a^2 q]i ustaw c = a * D^-1; d = (b + ap) * D^-1. Możemy więc wykonać odwrotność w GF (2 ^ 8) dla kosztu konwersji na GF (2 ^ 4), odwrotność oraz kilka dodatków i mnożenia w GF (2 ^ 4) i powrót z powrotem. Nawet jeśli wykonujemy odwrotność za pomocą tabeli, zmniejszyliśmy rozmiar tabeli z 256 do 16.

Szczegóły dotyczące wdrożenia

Aby skonstruować reprezentację GF (4), możemy wybrać jeden z trzech wielomianów w celu zmniejszenia x^4:

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

Najważniejszą różnicą jest implementacja mnożenia. Dla każdego z trzech (odpowiadających poly3, 9, f) będą działać następujące:

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

Jeśli jednak wybierzemy, poly = 3możemy znacznie wydajniej obsłużyć przepełnienie, ponieważ ma ładną strukturę: nie ma podwójnego przepełnienia, ponieważ oba wejścia są zarówno sześcienne, jak i x^6 = x^2 (x + 1)sześcienne. Ponadto możemy zapisać zmiany b: ponieważ pozostawiamy przepełnienie do końca, a0 x^2nie ma żadnych ustawionych bitów odpowiadających xlub 1, więc możemy zamaskować go za pomocą -4 zamiast -1. Wynik to

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

Nadal musimy wybrać wartości pi qdla reprezentacji GF (2 ^ 8) zamiast GF (2 ^ 4), ale nie mamy na nich wielu ograniczeń. Liczy się tylko to, że możemy uzyskać funkcję liniową od bitów naszej oryginalnej reprezentacji do bitów reprezentacji roboczej. Jest to ważne z dwóch powodów: po pierwsze, łatwo jest wykonać transformacje liniowe, podczas gdy transformacja nieliniowa wymagałaby optymalizacji równoważnej z trudnością do optymalizacji całej S-box; po drugie, ponieważ możemy uzyskać dodatkowe korzyści. Aby podsumować strukturę:

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
}

Jeśli bity xsą, x7 x6 ... x0ponieważ transformacja jest liniowa, otrzymujemy a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0}). Kwadrat to i dostajemy się a^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2tam, gdzie krzyżują się terminy (ponieważ w GF (2), 1 + 1 = 0). a^2Można więc również obliczyć jako funkcję liniową x. Możemy rozszerzyć przednią transformację liniową, aby uzyskać:

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

i jesteśmy zredukowani do trzech mnożeń i jednego dodatku. Możemy również rozszerzyć kod mnożenia, aby wykonać dwa mnożenia Dinvrównolegle. Zatem nasz całkowity koszt to transformacja liniowa do przodu, suma, dwa mnożenia, odwrotność w GF (2 ^ 4) i wsteczna transformacja liniowa. Możemy przetoczyć post-odwrotną transformację liniową S-boxa i uzyskać ją zasadniczo za darmo.

Obliczanie współczynników transformacji liniowych nie jest zbyt interesujące, podobnie jak mikrooptymalizacja w celu zapisania tutaj maski i przesunięcia. Pozostała interesująca część to optymalizacjainverse_GF16. Istnieją 2 ^ 64 różnych funkcji od 4 do 4 bitów, więc bezpośrednia optymalizacja wymaga dużej ilości pamięci i czasu. To, co zrobiłem, to rozważenie 4 funkcji od 4 bitów do 1 bitu, nałożenie limitu na całkowity dopuszczalny koszt każdej funkcji (przy maksymalnym koszcie 63 na funkcję mogę wyliczyć wszystkie odpowiednie wyrażenia w mniej niż minutę), i dla każdej krotki funkcji wykonaj wspólną eliminację podwyrażeń. Po 25 minutach chrupania stwierdzam, że najlepszy możliwy odwrotność z tym ograniczeniem ma całkowity koszt 133 (średnio 33,25 na bit wyjścia, co nie jest złe, biorąc pod uwagę, że najtańszym wyrażeniem dla każdego pojedynczego bitu jest 35) .

Wciąż eksperymentuję z innymi podejściami, aby zminimalizować inwersję w GF (2 ^ 4), a podejście, które opiera się na podejściu oddolnym, a nie odgórnym, przyniosło poprawę z 133 do 126.

Peter Taylor
źródło
Fantastyczny! Potwierdzam, że to działa! Szczegół: 8 & 1można przyciąć (szczególnie jeśli xjest unsigned char; CHAR_BITma 8 w codegolf).
fgrieu
@fgrieu, dobry punkt.
Peter Taylor
8

Wynik: 980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7, minimalizacja Boyara i Peralty

Znalazłem nową kombinacyjną technikę minimalizacji logiki z aplikacjami do kryptologii autorstwa Joan Boyar i René Peralta, która (z wyjątkiem formalizmu C) robi to, co jest wymagane. Technika wykorzystywana do wyprowadzania ich równań jest opatentowana przez nie mniej niż USA. Właśnie wykonałem proste tłumaczenie ich równań w języku C , uprzejmie tutaj połączone .

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;}
fgrieu
źródło
Wow, naprawdę działa i naprawdę tanie. Podczas dezasemblacji jest to rzeczywiście 144 instrukcje, z wyłączeniem instrukcji prologu, epilogii i ruchów.
ugoren,
5

Wynik: 10965

Korzysta z tej samej zasady rozwijania wyszukiwania tablicy. Może wymagać dodatkowych rzutów.

Dzięki ugoren za wskazanie, jak poprawić 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;
}
Peter Taylor
źródło
2
Dla liczby całkowitej c (c|-c)>>31wynosi 0 dla 0 i -1 w przeciwnym razie.
ugoren
@ugoren, w rozsądnych językach, tak. W C przesunięcie w prawo niepodpisanego typu jest nieokreślone.
Peter Taylor,
1
Chyba masz na myśli podpisany. Ale ta witryna nie jest znana z ścisłej zgodności z normami. Poza tym c >> 4wydaje mi się, że masz podpisaną prawą zmianę do mnie. A jeśli naprawdę nalegasz - ((unsigned int)(c|-c))>>31jest c?1:0.
ugoren
@ugoren, masz rację, miałem na myśli podpisany. c >>4Współpracuje z lub bez rozszerzenia migowego. Ale dobrze złap się na używaniu niepodpisanej zmiany: edytuję, kiedy wrócę do domu i mogę korzystać z odpowiedniego komputera, a nie telefonu. Dzięki.
Peter Taylor,
3

Wynik: 9109, podejście algebraiczne

Opuszczę podejście do wyszukiwania na wypadek, gdyby ktoś mógł go znacznie poprawić, ale okazuje się, że możliwe jest dobre podejście algebraiczne. Ta implementacja znajduje multiplikatywną odwrotność za pomocą algorytmu Euclid . Napisałem go w Javie, ale w zasadzie można go przenieść do C - skomentowałem części, które zmieniłyby się, gdybyś chciał przerobić go tylko przy użyciu typów 8-bitowych.

Dzięki ugoren za wskazanie, jak skrócić is_nonzeroczek w komentarzu do mojej drugiej odpowiedzi.

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;
    }
}
Peter Taylor
źródło
2

Wynik: 256 * (7+ (8 * (7 + 7 + 7) - (2 + 2)) + 5 + 7 + 7) = 48640 (przy założeniu rozwinięcia pętli)

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

Wyjaśnienie:

Zasadniczo wyszukiwanie tablicy zostało ponownie zaimplementowane przy użyciu operatorów bitowych i zawsze przetwarza całą tablicę. Pętlimy przez tablicę i xor xz każdym indeksem, a następnie używamy operatorów bitowych, aby logicznie negować wynik, więc otrzymujemy 255 kiedy x=ii 0 w przeciwnym razie. Bitowo - i to z wartością tablicową, tak że wybrana wartość pozostaje niezmieniona, a pozostałe stają się 0. Następnie bierzemy bitową - lub tej tablicy, wyciągając w ten sposób tylko wybraną wartość.

Dwie 1 << joperacje znikną w ramach rozwijania pętli, zastępując je mocami 2 od 1 do 128.

histocrat
źródło
Teraz sprawdź, czy jest możliwe wykonanie matematyki za pomocą operatorów bitowych.
histocrat
Patrząc tutaj na algorytm , wątpię, czy możliwe będzie zaimplementowanie inwersji wielomianowej bez zapętlania wszystkich bajtów przynajmniej raz jako substytut niektórych kroków czasu wielomianowego. To może więc pokonać wszelkie „inteligentne” rozwiązania. Podejrzewam, że dostrojenie tego wyszukiwania tablic o stałym czasie jest bardziej obiecującą drogą.
histocrat
Ładny. Funkcja rj_sbox w aes.c tutaj może dać inspirację (choć jak na razie nie pasuje do pytania).
fgrieu
Skąd -(2+2)bierze się twoje obliczenie punktacji? Edycja: ah, wstawianie tworzy a <<1i a >>1.
Peter Taylor,
0

Ocena 1968 1692 za pomocą tabeli odnośników

Uwaga: To rozwiązanie nie spełnia kryteriów z powodu w >> b.

Korzystanie z tabeli odnośników, ale czytanie 8 bajtów jednocześnie.
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
}
ugoren
źródło
Nie sądzę, aby spełniało to definicję stałego czasu w pytaniu, ponieważ w>>bRHS obliczono na podstawiex
Peter Taylor,
Istnieje kilka naruszeń: w >> bgdzie bzależy od danych wejściowych; również x/8, x%8i *= (1-badi). Pierwszy z nich najprawdopodobniej przerodzi się w zależność czasową od niskiej klasy procesorów. Jednak pomysł użycia szerokich zmiennych z pewnością ma potencjał.
fgrieu,
Nie przeczytałem dokładnie instrukcji. Mogę naprawić większość problemów, ale w >> bjest to bardzo istotne (muszę sprawdzić, czy można to naprawić bez przepisywania wszystkiego.
ugoren 26.12.12
0

Wyszukiwanie i maska ​​tabeli, wynik = 256 * (5 * 7 + 1 * 5) = 10240

Używa wyszukiwania tabeli z maską, aby wybrać tylko pożądany wynik. Wykorzystuje fakt, że j|-jjest on ujemny (gdy i! = X) lub zero (gdy i == x). Przesunięcie tworzy maskę „wszystko jedno” lub „zero”, która służy do wybrania tylko pożądanego wpisu.

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;
}
Keith Randall
źródło
Czy to nie to samo, co moja druga odpowiedź, z wyjątkiem mniej zoptymalizowanej?
Peter Taylor
Myślę, że blisko. Używam podpisanej zmiany, więc nie muszę robić -1 na końcu.
Keith Randall