C: Ersetzen Sie die AES FIPS-197 SubBytes-Tabelle durch einen Code mit konstanter Zeit


17

In FIPS-197 (dem Advanced Encryption Standard , bekannt als AES) wird er stark genutzt SubBytes, was als implementiert werden könnte

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

Diese Funktion ist nicht beliebig. es ist eine reversible Abbildung, bestehend aus einer Inversion in einem Galoisfeld, gefolgt von einer affinen Transformation. Alle Details finden Sie in FIPS-197, Abschnitt 5.1.1 oder hier Abschnitt 4.2.1 (unter einem etwas anderen Namen).

Ein Problem bei der Implementierung als Tabelle besteht darin, dass so genannte Cache-Timing-Angriffe möglich sind .

Ihre Mission ist es daher, einen genauen Ersatz für die obige SubBytes()Funktion zu finden, der ein zeitlich konstantes Verhalten aufweist. Wir gehen davon aus, dass dies der Fall ist, wenn entweder nichts abhängig von der Eingabe xvon SubBytesverwendet wird:

  • als Array-Index,
  • da der Steueroperanden if, while, for, case, oder Betreiber ?:;
  • wie jeder Operand von Operatoren &&, ||, !, ==, !=, <, >, <=, >=, *, /, %;
  • als rechter Operand der Operatoren >>, <<, *=, /=, %=, <<=, >>=.

Der Gewinner wird eines mit den niedrigsten Kosten, aus der Anzahl der Operatoren in dem Eingangsabhängigen Datenweg ausgeführt erhalten wurde, mit einem Gewicht von 5 für unäre Operatoren -und ~sowie für <<1, >>1, +1, -1; Ein Gewicht von 7 für alle anderen Operatoren, Verschiebungen mit anderen Zählungen oder Hinzufügungen / Untersetzungen anderer Konstanten (Typumwandlungen und Werbeaktionen sind kostenlos). Im Prinzip bleiben diese Kosten durch das Abrollen von Schleifen (falls vorhanden) unverändert und sind von der Eingabe unabhängig x. Als Tie-Breaker gewinnt die Antwort mit dem kürzesten Code nach dem Entfernen von Leerzeichen und Kommentaren.

Ich plane, einen Eintrag so früh wie möglich als Antwort zu kennzeichnen, und zwar in Jahr 2013, UTC. Ich werde Antworten in Sprachen berücksichtigen, die ich kenne, und sie als direkte Übersetzung in C einstufen, die nicht für die Größe optimiert ist.

Entschuldigung für das anfängliche Auslassen von +1und -1in den bevorzugten Betreibern, für freie Besetzungen und Werbeaktionen sowie für die Rangfolge der Größe. Beachten Sie, dass *dies sowohl bei Einzahl als auch bei Multiplikation verboten ist.


1
Beachten Sie, dass Lookups kostenlos sind, da Sie sie als Konstanten einbetten können.
Peter Taylor

"Anfang 2013, UTC" - Wäre das Datum nicht interessanter als die Zeitzone?
Paŭlo Ebermann

@ PaŭloEbermann: Meine Absicht sollte jetzt klar sein.
Fgrieu

Antworten:


13

Spielergebnis: 940 933 926 910, Feldturmannäherung

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

Die Struktur entspricht im Wesentlichen der Implementierung von Boyar und Peralta - reduzieren Sie die Inversion in GF (2 ^ 8) auf die Inversion in GF (2 ^ 4), zerlegen Sie sie in einen linearen Prolog, einen nichtlinearen Körper und einen linearen Epilog. und minimieren Sie sie dann separat. Ich zahle einige Strafen für die Bit-Extraktion, aber ich kompensiere dies, indem ich in der Lage bin, Operationen parallel auszuführen (mit einigem vernünftigen Auffüllen der Bits von fwd). Ausführlicher...

Hintergrund

Wie in der Problembeschreibung erwähnt, besteht die S-Box aus einer Inversion in einer bestimmten Implementierung des Galois-Feldes GF (2 ^ 8), gefolgt von einer affinen Transformation. Wenn Sie wissen, was beide bedeuten, überspringen Sie diesen Abschnitt.

Eine affine (oder lineare) Transformation ist eine Funktion, f(x)die f(x + y) = f(x) + f(y)und respektiert f(a*x) = a*f(x).

Ein Feld ist eine Reihe Fvon Elementen mit zwei speziellen Elementen, die wir nennen wollen 0und 1und zwei Operatoren, die wir nennen wollen +und *, die verschiedenen Eigenschaften respektieren. In diesem Abschnitt wird angenommen , dass x, yund zsind Elemente F.

  • Die Elemente Fbilden eine abelsche Gruppe +mit 0als Identität: dh x + yist ein Element von F; x + 0 = 0 + x = x; jeder xhat ein entsprechendes -xsolches x + (-x) = (-x) + x = 0; x + (y + z) = (x + y) + z; und x + y= y + x.
  • Die Elemente Fanderer als 0bilden eine abelsche Gruppe *mit 1als Identität.
  • Multiplikation verteilt über Zusatz: x * (y + z) = (x * y) + (x * z).

Es stellt sich heraus, dass es für endliche Felder einige ziemlich strenge Einschränkungen gibt:

  • Sie müssen eine Anzahl von Elementen haben, die eine Kraft einer Primzahl ist.
  • Sie sind mit allen anderen endlichen Feldern der gleichen Größe isomorph (dh es gibt wirklich nur ein endliches Feld einer bestimmten Größe, und alle anderen sind nur Umbeschriftungen. Nennen Sie dieses Feld GF (p ^ k), wo pdas Prim und istk die Potenz ist). .
  • Die multiplikative Gruppe F\{0}unter *ist zyklisch; dh es gibt mindestens ein Element g, von dem jedes Element eine Potenz ist g.
  • Für Potenzen größer als 1 gibt es eine Darstellung als univariate Polynome der Ordnung kdes Feldes der Primordnung. ZB hat GF (2 ^ 8) eine Darstellung in Form von Polynomen von xüber GF (2). Tatsächlich gibt es normalerweise mehr als eine Darstellung. Betrachten Sie x^7 * xin GF (2 ^ 8); es muss einem Polynom der Ordnung 7 entsprechen, aber welches? Es gibt viele Möglichkeiten, die die richtige Struktur ergeben. AES wählt x^8 = x^4 + x^3 + x + 1(das lexikographisch kleinste Polynom, das funktioniert).

Wie berechnen wir also eine Inverse in dieser bestimmten Darstellung von GF (2 ^ 8)? Es ist ein zu großes Problem, um es direkt anzugehen, also müssen wir es auflösen.

Feldtürme: repräsentieren GF (2 ^ 8) in Bezug auf GF (2 ^ 4)

Anstatt GF (2 ^ 8) mit Polynomen von 8 Termen über GF (2) darzustellen, können wir es mit Polynomen von 2 Termen über GF (2 ^ 4) darstellen. Diesmal müssen wir ein lineares Polynom für wählen x^2. Angenommen, wir wählen x^2 = px + q. Dann (ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq).

Ist es einfacher, eine Inverse in dieser Darstellung zu finden? Wenn (cx + d) = (ax + b)^-1wir die simultanen Gleichungen erhalten

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

Lass D = [b(b+ap) + a^2 q]und setze c = a * D^-1; d = (b + ap) * D^-1. Wir können also eine Inverse in GF (2 ^ 8) für die Kosten einer Konvertierung in GF (2 ^ 4), eine Inverse und einige Additionen und Multiplikationen in GF (2 ^ 4) und eine Konvertierung zurück durchführen. Auch wenn wir das Inverse mit einer Tabelle machen, haben wir die Tabellengröße von 256 auf 16 reduziert.

Implementierungsdetails

Um eine Darstellung von GF (4) zu konstruieren, können wir zwischen drei zu reduzierenden Polynomen wählen x^4:

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

Der wichtigste Unterschied besteht in der Implementierung der Multiplikation. Für alle drei (die poly3, 9, f entsprechen) funktioniert Folgendes:

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

Wenn wir poly = 3uns jedoch dafür entscheiden , können wir den Überlauf viel effizienter handhaben, da er eine schöne Struktur hat: Es gibt keinen doppelten Überlauf, da die beiden Eingänge beide x^6 = x^2 (x + 1)kubisch und auch kubisch sind. Außerdem können wir die Verschiebungen von speichern b: Da wir den Überlauf als letztes verlassen, a0 x^2haben xwir keine gesetzten Bits entsprechend oder 1 und können es daher mit -4 anstelle von -1 maskieren. Das Ergebnis ist

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

Wir müssen immer noch die Werte von pund qfür die Darstellung von GF (2 ^ 8) vor GF (2 ^ 4) wählen , aber wir haben nicht viele Einschränkungen. Was zählt, ist, dass wir eine lineare Funktion von den Bits unserer ursprünglichen Darstellung zu den Bits der Arbeitsdarstellung erhalten können. Dies ist aus zwei Gründen von Bedeutung: Erstens ist es einfach, lineare Transformationen durchzuführen, während für eine nichtlineare Transformation eine Optimierung erforderlich ist, die der Optimierung der gesamten S-Box entspricht. zweitens, weil wir einige nebenleistungen erhalten können. So rekapitulieren Sie die 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
}

Wenn die Bits xsind x7 x6 ... x0dann da die Transformation linear wir bekommen a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0}). Quadrieren Sie es und wir erhalten, a^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2wo die Kreuzbegriffe aufheben (weil in GF (2), 1 + 1 = 0). So a^2kann auch als lineare Funktion von berechnet werden x. Wir können die lineare Vorwärtstransformation erweitern, um Folgendes zu erhalten:

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

und wir sind auf drei Multiplikationen und eine Addition. Wir können den Multiplikationscode auch erweitern, um die beiden Multiplikationen Dinvparallel durchzuführen. Unsere Gesamtkosten sind also eine lineare Vorwärtstransformation, eine Addition, zwei Multiplikationen, eine Inverse in GF (2 ^ 4) und eine lineare Rückwärtstransformation. Wir können in der post-inversen linearen Transformation der S-Box rollen und diese im Wesentlichen kostenlos erhalten.

Die Berechnung der Koeffizienten für die linearen Transformationen ist nicht sehr interessant, ebenso wenig wie die Mikrooptimierung, um eine Maske hier und eine Verschiebung dort zu speichern. Der verbleibende interessante Teil ist die Optimierung voninverse_GF16. Es gibt 2 ^ 64 verschiedene Funktionen von 4 Bit bis 4 Bit, daher erfordert eine direkte Optimierung viel Speicher und Zeit. Was ich getan habe, ist, 4 Funktionen von 4 Bit bis 1 Bit zu betrachten, die zulässigen Gesamtkosten für eine Funktion zu begrenzen (mit einem Maximalpreis von 63 pro Funktion kann ich alle geeigneten Ausdrücke in weniger als einer Minute aufzählen). und für jedes Tupel von Funktionen werden gemeinsame Unterausdrücke eliminiert. Nach 25 Minuten Knirschen stelle ich fest, dass die bestmögliche Inverse mit dieser Obergrenze insgesamt 133 kostet (durchschnittlich 33,25 pro Bit der Ausgabe, was nicht schlecht ist, wenn man bedenkt, dass der billigste Ausdruck für ein einzelnes Bit 35 ist). .

Ich experimentiere immer noch mit anderen Ansätzen, um die Inversion in GF (2 ^ 4) zu minimieren, und ein Ansatz, der Bottom-Up statt Top-Down erstellt, hat eine Verbesserung von 133 auf 126 ergeben.


Fantastisch! Ich bestätige, dass es funktioniert! Ein Detail: Der 8. & 1kann getrimmt werden ( va wenn xist unsigned char; CHAR_BITist 8 im Codegolf).
Fgrieu

@fgrieu, guter Punkt.
Peter Taylor

8

Ergebnis: 980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7, Minimierung von Boyar und Peralta

Ich fand eine neue Technik zur Minimierung der kombinatorischen Logik mit Anwendungen auf die Kryptologie von Joan Boyar und René Peralta, die (abgesehen vom C-Formalismus) das tut, was erforderlich ist. Die Technik zur Ableitung ihrer Gleichungen ist von nicht weniger als den USA patentiert . Ich habe gerade eine C-Übersetzung ihrer Gleichungen gemacht , die freundlicherweise hier verlinkt sind .

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, funktioniert wirklich und ist wirklich günstig. Beim Zerlegen sind es in der Tat 144 Anweisungen, ausgenommen Prolog-, Epilog- und Bewegungsanweisungen.
Ugoren

5

Ergebnis: 10965

Dies verwendet dasselbe Prinzip wie das Aufrollen der Array-Suche. Erfordert möglicherweise zusätzliche Würfe.

Vielen Dank an ugoren für den Hinweis, wie man sich verbessern kann 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
Für eine ganze Zahl c (c|-c)>>31ist 0 für 0 und -1 ansonsten.
Ugoren

@ugoren, in vernünftigen Sprachen, ja. In C ist das Verschieben eines vorzeichenlosen Typs nach rechts nicht angegeben.
Peter Taylor

1
Du meinst wohl unterschrieben. Diese Seite ist jedoch nicht gerade für die strikte Einhaltung von Standards bekannt. Außerdem c >> 4scheint deine Rechtsverschiebung für mich signiert zu sein. Und wenn Sie wirklich darauf bestehen - ((unsigned int)(c|-c))>>31ist c?1:0.
Ugoren

@ugoren, du hast recht, ich meinte unterschrieben. Das c >>4funktioniert mit oder ohne Vorzeichenerweiterung. Aber ein guter Haken bei der Verwendung einer Schicht ohne Vorzeichen: Wird bearbeitet, wenn ich zu Hause bin und einen richtigen Computer anstelle eines Telefons verwenden kann. Vielen Dank.
Peter Taylor

3

Ergebnis: 9109, algebraischer Ansatz

Ich werde den Lookup-Ansatz verlassen, falls jemand ihn drastisch verbessern kann, aber es stellt sich heraus, dass ein guter algebraischer Ansatz möglich ist. Diese Implementierung findet die multiplikative Inverse unter Verwendung des Euclid-Algorithmus . Ich habe es in Java geschrieben, aber im Prinzip kann es nach C portiert werden. Ich habe die Teile kommentiert, die sich ändern würden, wenn Sie es nur mit 8-Bit-Typen überarbeiten möchten.

Vielen Dank an ugoren für den Hinweis, wie is_nonzeroich das Einchecken in einem Kommentar zu meiner anderen Antwort verkürzen kann .

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

Ergebnis: 256 * (7+ (8 * (7 + 7 + 7) - (2 + 2)) + 5 + 7 + 7) = 48640 (unter der Annahme, dass die Schleifen ausgerollt sind)

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

Erläuterung:

Im Wesentlichen eine Array-Suche, die mit bitweisen Operatoren neu implementiert und immer das gesamte Array verarbeitet wird. Wir durchlaufen das Array und xor xmit jedem Index und verwenden dann bitweise Operatoren, um das Ergebnis logisch zu negieren x=i. Ansonsten erhalten wir 255 when und 0. Wir bitweise - und dies mit dem Array-Wert, so dass der gewählte Wert unverändert ist und die anderen 0 werden. Dann nehmen wir das bitweise-oder dieses Arrays und ziehen dabei nur den gewählten Wert heraus.

Die zwei 1 << jOperationen würden als Teil des Abrollens der Schleife verschwinden und sie durch die Potenzen von 2 von 1 bis 128 ersetzen.


Nun wollen wir herausfinden, ob es möglich ist, mit bitweisen Operatoren zu rechnen.
Histokrat

Wenn ich mir den Algorithmus hier anschaue, bezweifle ich, dass es möglich sein wird, die Polynomumkehrung zu implementieren, ohne dass alle Bytes mindestens einmal als Ersatz für einige der Polynomzeitschritte durchlaufen werden. Das könnte also alle "intelligenten" Lösungen schlagen. Ich vermute, dass die Optimierung dieser zeitlich konstanten Array-Suche ein vielversprechenderer Weg ist.
Histokrat

Nett. Die Funktion rj_sbox in aes.c könnte hier als Inspiration dienen (obwohl sie wie sie ist nicht mit der Frage übereinstimmt).
Fgrieu

Woher kommt die -(2+2)in Ihrer Punkteberechnung? Edit: ah, Inlining schafft ein <<1und ein >>1.
Peter Taylor

0

Kerbe 1968 1692, unter Verwendung der Nachschlagetabelle

Hinweis: Diese Lösung erfüllt die Kriterien aufgrund von nicht w >> b.

Verwenden der Nachschlagetabelle, aber gleichzeitiges Lesen von 8 Bytes.
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
}

Ich glaube nicht, dass dies die Definition der konstanten Zeit in der Frage erfüllt, da w>>bRHS vonx
Peter Taylor

Es gibt mehrere Verstöße: w >> bWo bhängt von der Eingabe ab; auch x/8, x%8und *= (1-badi). Der erste degeneriert besonders wahrscheinlich in eine Zeitabhängigkeit von Low-End-CPUs. Die Idee, breite Variablen zu verwenden, hat jedoch durchaus Potenzial.
Fgrieu

Ich habe die Anleitung nicht sorgfältig genug gelesen. Ich kann die meisten Probleme beheben, aber es w >> bist sehr wichtig (ich muss sehen, ob es behoben werden kann, ohne alles neu zu schreiben.
Ugoren

0

Tabellensuche und Maske, Punktzahl = 256 * (5 * 7 + 1 * 5) = 10240

Verwendet die Tabellensuche mit einer Maske, um nur das gewünschte Ergebnis auszuwählen. Verwendet die Tatsache, dass j|-jentweder negativ (wenn i! = X) oder null (wenn i == x) ist. Durch Verschieben wird eine All-One- oder All-Zero-Maske erstellt, mit der nur der gewünschte Eintrag ausgewählt wird.

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

Ist das nicht das Gleiche wie meine zweite Antwort, außer dass sie weniger optimiert ist?
Peter Taylor

Schließen, denke ich. Ich benutze die vorzeichenbehaftete Umschalttaste, damit ich am Ende nicht die -1 machen muss.
Keith Randall
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.