C: remplacer la table AES FIPS-197 SubBytes par un code à temps constant


17

Dans FIPS-197 ( Advanced Encryption Standard , connu sous le nom d'AES), il est largement utilisé SubBytes, qui pourrait être implémenté comme

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

Cette fonction n'est pas arbitraire; c'est une cartographie réversible, consistant en une inversion dans un champ de Galois suivie d'une transformation affine. Tous les détails se trouvent dans FIPS-197 section 5.1.1 ou ici section 4.2.1 (sous un nom légèrement différent).

Un problème avec l'implémentation en tant que table est qu'elle s'ouvre aux attaques dites de synchronisation de cache .

Ainsi, votre mission est de concevoir un remplacement exact pour la SubBytes()fonction ci-dessus , qui présente un comportement à temps constant; nous supposerons que c'est le cas lorsque rien en fonction de l'entrée xde SubBytesn'est utilisé:

  • comme un index de tableau,
  • que le contrôle de l' opérande if, while, for, case, ou de l' opérateur ?:;
  • comme l' un des opérandes d'opérateurs &&, ||, !, ==, !=, <, >, <=, >=, *, /, %;
  • comme le droit opérande des opérateurs >>, <<, *=, /=, %=, <<=, >>=.

Le gagnant sera celui avec le plus faible coût, obtenu à partir du nombre d'opérateurs exécutées dans le chemin de données dépendant de l' entrée, avec un poids de 5 pour les opérateurs unaires -et ~ainsi que pour <<1, >>1, +1, -1; poids de 7 pour tous les autres opérateurs, quarts avec d'autres nombres ou ajouts / sous-ensembles d'autres constantes (les transformations de type et les promotions sont gratuites). En principe, ce coût est inchangé par les boucles de déroulement (le cas échéant) et indépendant de l'entrée x. En tant que bris d'égalité, la réponse avec le code le plus court après la suppression des espaces et des commentaires l'emporte.

J'ai l'intention de désigner une entrée comme réponse dès que possible en 2013, UTC. Je considérerai les réponses dans les langues que je connais, en les classant comme une traduction directe en C non optimisée pour la taille.

Toutes mes excuses pour l'omission initiale +1et -1dans les opérateurs favorisés, de lancers et promotions gratuits, et le classement de la taille. Notez que cela *est interdit à la fois lorsqu'il est unaire et comme multiplication.


1
Il convient de noter que les recherches sont gratuites car vous pouvez les aligner sous forme de constantes.
Peter Taylor

"au début de l'année 2013, UTC" - la date ne serait-elle pas plus intéressante que le fuseau horaire?
Paŭlo Ebermann

@ PaŭloEbermann: Mon intention devrait être claire maintenant.
fgrieu

Réponses:


13

Score: 940 933 926 910, approche de la tour sur le terrain

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

La structure est essentiellement la même que celle de Boyar et Peralta - réduire l'inversion dans GF (2 ^ 8) en inversion dans GF (2 ^ 4), la décomposer en un prologue linéaire, un corps non linéaire et un épilogue linéaire, puis les minimiser séparément. Je paye quelques pénalités pour l'extraction de bits, mais je compense en pouvant faire des opérations en parallèle (avec un bourrage judicieux des bits de fwd). Plus en détail...

Contexte

Comme mentionné dans la description du problème, la S-box consiste en une inversion dans une implémentation particulière du champ de Galois GF (2 ^ 8) suivie d'une transformation affine. Si vous savez ce que ces deux signifient, ignorez cette section.

Une transformation affine (ou linéaire) est une fonction f(x)qui respecte f(x + y) = f(x) + f(y)et f(a*x) = a*f(x).

Un champ est un ensemble Fd'éléments avec deux éléments spéciaux, que nous appellerons 0et 1, et deux opérateurs, que nous appellerons +et *, qui respectent diverses propriétés. Dans cette section, on suppose que x, yet zsont des éléments de F.

  • Les éléments de Fforment un groupe abélien sous +avec 0comme identité: ie x + yest un élément de F; x + 0 = 0 + x = x; chacun xa un correspondant -xtel que x + (-x) = (-x) + x = 0; x + (y + z) = (x + y) + z; et x + y= y + x.
  • Les éléments de Fautre que 0forment un groupe abélien sous *avec 1comme identité.
  • Distribue sur l' addition Multiplication: x * (y + z) = (x * y) + (x * z).

Il s'avère qu'il existe des limitations assez sévères sur les champs finis:

  • Ils doivent avoir un certain nombre d'éléments qui est une puissance d'un nombre premier.
  • Ils sont isomorphes avec tous les autres champs finis de la même taille (c'est-à-dire qu'il n'y a vraiment qu'un seul champ fini d'une taille donnée, et tous les autres ne sont que des réétiquettes; appelez ce champ GF (p ^ k) où pest le premier et kest la puissance) .
  • Le groupe multiplicatif F\{0}sous *est cyclique; c'est-à-dire qu'il y a au moins un élément gtel que chaque élément est une puissance de g.
  • Pour les puissances supérieures à 1, il existe une représentation sous forme de polynômes univariés d'ordre kdu champ de l'ordre premier. Par exemple, GF (2 ^ 8) a une représentation en termes de polynômes de xplus de GF (2). En fait, il y a généralement plus d'une représentation. Considérez x^7 * xdans GF (2 ^ 8); il doit être équivalent à un polynôme d'ordre 7, mais lequel? Il y a beaucoup de choix qui donnent la bonne structure; AES choisit de faire x^8 = x^4 + x^3 + x + 1(le polynôme lexicographiquement le plus petit qui fonctionne).

Alors, comment calculer un inverse dans cette représentation particulière de GF (2 ^ 8)? C'est un problème trop volumineux pour être abordé directement, nous devons donc le décomposer.

Tours de campagne: représentant GF (2 ^ 8) en termes de GF (2 ^ 4)

Au lieu de représenter GF (2 ^ 8) avec des polynômes de 8 termes sur GF (2), nous pouvons le représenter avec des polynômes de 2 termes sur GF (2 ^ 4). Cette fois, nous devons choisir un polynôme linéaire pour x^2. Supposons que nous choisissions x^2 = px + q. Alors (ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq).

Est-il plus facile de trouver un inverse dans cette représentation? Si (cx + d) = (ax + b)^-1nous obtenons les équations simultanées

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

Let D = [b(b+ap) + a^2 q]and set c = a * D^-1; d = (b + ap) * D^-1. On peut donc faire un inverse en GF (2 ^ 8) pour le coût d'une conversion en GF (2 ^ 4), un inverse et quelques additions et multiplications en GF (2 ^ 4), et une reconversion. Même si nous faisons l'inverse au moyen d'un tableau, nous avons réduit la taille du tableau de 256 à 16.

Détails d'implémentation

Pour construire une représentation de GF (4) on peut choisir entre trois polynômes pour réduire x^4:

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

La différence la plus importante réside dans la mise en œuvre de la multiplication. Pour l'un des trois (qui correspondent à poly3, 9, f), les éléments suivants fonctionneront:

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

Cependant, si nous choisissons, poly = 3nous pouvons gérer le débordement beaucoup plus efficacement car il a une belle structure: il n'y a pas de double débordement, car les deux entrées sont à la fois cubiques et x^6 = x^2 (x + 1)cubiques. De plus, nous pouvons enregistrer les décalages de b: puisque nous laissons le débordement durer, il a0 x^2n'y a pas de bits définis correspondant à xou 1 et nous pouvons donc le masquer avec -4 au lieu de -1. Le résultat est

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

Nous devons encore choisir les valeurs de pet qpour la représentation de GF (2 ^ 8) sur GF (2 ^ 4), mais nous n'avons pas beaucoup de contraintes sur elles. La seule chose qui compte, c'est que nous pouvons obtenir une fonction linéaire des bits de notre représentation d'origine aux bits de la représentation de travail. Cela compte pour deux raisons: premièrement, il est facile de faire des transformations linéaires, alors qu'une transformation non linéaire nécessiterait une optimisation équivalente en difficulté à une optimisation complète de la S-box entière; deuxièmement, parce que nous pouvons obtenir des avantages secondaires. Pour récapituler la structure:

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
}

Si les bits de xsont x7 x6 ... x0alors puisque la transformée est linéaire, nous obtenons a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0}). Carré et nous obtenons a^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2où les termes croisés s'annulent (car dans GF (2), 1 + 1 = 0). Il a^2peut donc aussi être calculé en fonction linéaire de x. Nous pouvons augmenter la transformation linéaire directe pour obtenir:

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

et nous en sommes à trois multiplications et un ajout. Nous pouvons également étendre le code de multiplication pour effectuer les deux multiplications Dinven parallèle. Notre coût total est donc une transformation linéaire en avant, une addition, deux multiplications, une inverse en GF (2 ^ 4) et une transformation linéaire en arrière. Nous pouvons rouler dans la transformation linéaire post-inverse de la S-box et l'obtenir essentiellement gratuitement.

Le calcul des coefficients pour les transformations linéaires n'est pas très intéressant, pas plus que la micro-optimisation pour sauvegarder un masque ici et un décalage là. La partie intéressante restante est l'optimisation deinverse_GF16. Il y a 2 ^ 64 fonctions différentes de 4 bits à 4 bits, donc une optimisation directe nécessite beaucoup de mémoire et de temps. Ce que j'ai fait, c'est de considérer 4 fonctions de 4 bits à 1 bit, de plafonner le coût total autorisé pour une fonction (avec un coût maximum de 63 par fonction, je peux énumérer toutes les expressions appropriées en moins d'une minute), et pour chaque tuple de fonctions, éliminer la sous-expression commune. Après 25 minutes de resserrement, je trouve que le meilleur inverse possible avec cette casquette a un coût total de 133 (une moyenne de 33,25 par bit de sortie, ce qui n'est pas mal étant donné que l'expression la moins chère pour un bit individuel est de 35) .

J'expérimente toujours avec d'autres approches pour minimiser l'inversion dans GF (2 ^ 4), et une approche qui construit de bas en haut plutôt que de haut en bas a produit une amélioration de 133 à 126.


Fantastique! Je confirme que ça marche! Un détail: le 8e & 1peut être coupé (surtout s'il l' xest unsigned char; CHAR_BITest 8 en codegolf).
fgrieu

@fgrieu, bon point.
Peter Taylor

8

Score: 980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7, minimisation de Boyar et Peralta

J'ai trouvé une nouvelle technique de minimisation de la logique combinatoire avec des applications à la cryptologie par Joan Boyar et René Peralta, qui (sauf pour le formalisme C) fait ce qui est nécessaire. La technique utilisée pour dériver leurs équations est brevetée par pas moins que les États-Unis. Je viens de faire une traduction directe en C de leurs équations , gentiment liées ici .

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, fonctionne vraiment et vraiment pas cher. Lors du démontage, il s'agit en effet de 144 instructions, hors prologue, épilogie et instructions de déplacement.
ugoren

5

Résultat: 10965

Cela utilise le même principe de déroulement de la recherche de tableau. Peut nécessiter des moulages supplémentaires.

Merci à ugoren d'avoir indiqué comment s'améliorer 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
Pour un entier c, (c|-c)>>31vaut 0 pour 0 et -1 sinon.
ugoren

@ugoren, dans des langues sensées, oui. En C, le déplacement vers la droite d'un type non signé n'est pas spécifié.
Peter Taylor

1
Je suppose que vous voulez dire signé. Mais ce site n'est pas vraiment réputé pour sa stricte conformité aux normes. De plus, vous c >> 4semblez avoir signé le bon changement pour moi. Et si vous insistez vraiment - ((unsigned int)(c|-c))>>31c'est c?1:0.
ugoren

@ugoren, vous avez raison, je voulais dire signé. Les c >>4travaux avec ou sans extension de signe. Mais bonne prise sur l'utilisation d'un quart de travail non signé: modifiera quand je rentrerai à la maison et pourra utiliser un ordinateur approprié plutôt qu'un téléphone. Merci.
Peter Taylor

3

Score: 9109, approche algébrique

Je laisserai l'approche de recherche au cas où quelqu'un pourrait l'améliorer considérablement, mais il s'avère qu'une bonne approche algébrique est possible. Cette implémentation trouve l'inverse multiplicatif en utilisant l'algorithme d'Euclide . Je l'ai écrit en Java mais en principe il peut être porté en C - j'ai commenté les parties qui changeraient si vous voulez le retravailler en utilisant uniquement des types 8 bits.

Merci à ugoren d'avoir indiqué comment raccourcir le is_nonzerochèque dans un commentaire sur mon autre réponse.

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

Score: 256 * (7+ (8 * (7 + 7 + 7) - (2 + 2)) + 5 + 7 + 7) = 48640 (en supposant que les boucles soient déroulées)

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

Explication:

Essentiellement, une recherche de tableau est réimplémentée à l'aide d'opérateurs au niveau du bit et traite toujours l'ensemble du tableau. Nous parcourons le tableau et xor xavec chaque index, puis utilisons des opérateurs au niveau du bit pour nier logiquement le résultat, nous obtenons donc 255 quand x=iet 0 sinon. Nous binaire-et ceci avec la valeur de tableau, de sorte que la valeur choisie est inchangée et les autres deviennent 0. Ensuite, nous prenons le binaire-ou de ce tableau, tirant ainsi uniquement la valeur choisie.

Les deux 1 << jopérations disparaîtraient dans le cadre du déroulement de la boucle, en les remplaçant par les puissances de 2 de 1 à 128.


Maintenant, pour voir s'il est possible de faire le calcul en utilisant des opérateurs au niveau du bit.
histocrate

En parcourant l'algorithme ici , je doute qu'il soit possible d'implémenter l'inversion polynomiale sans boucler sur tous les octets au moins une fois en remplacement de certaines des étapes polynomiales. Donc, cela pourrait bien battre toutes les solutions «intelligentes». Je soupçonne que le réglage de cette recherche de tableau à temps constant est une avenue plus prometteuse.
histocrate

Agréable. La fonction rj_sbox dans aes.c ici pourrait donner de l'inspiration (bien qu'elle ne corresponde pas à la question).
fgrieu

D'où vient le -(2+2)calcul de votre score? Edit: ah, l'incrustation crée un <<1et un >>1.
Peter Taylor

0

Score 1968 1692, en utilisant la table de correspondance

Remarque: Cette solution ne répond pas aux critères, à cause de w >> b.

Utilisation de la table de recherche, mais lecture de 8 octets à la fois.
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
}

Je ne pense pas que cela réponde à la définition du temps constant dans la question, car la w>>bRHS a-t-elle été calculée à partir dex
Peter Taylor

Il y a plusieurs violations: w >> bbdépend de l'entrée; aussi x/8, x%8et *= (1-badi). Le premier est particulièrement susceptible de dégénérer en une dépendance temporelle sur les processeurs bas de gamme. Cependant, l'idée d'utiliser des variables larges a certainement du potentiel.
fgrieu

Je n'ai pas lu les instructions assez attentivement. Je peux résoudre la plupart des problèmes, mais w >> bc'est assez essentiel (j'ai besoin de voir si cela peut être résolu sans tout réécrire.
ugoren

0

Recherche de table et masque, score = 256 * (5 * 7 + 1 * 5) = 10240

Utilise la recherche de table avec un masque pour sélectionner uniquement le résultat souhaité. Utilise le fait qui j|-jest soit négatif (quand i! = X) soit nul (quand i == x). Le décalage crée un masque tout-un ou tout-zéro qui est utilisé pour sélectionner uniquement l'entrée que nous voulons.

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

N'est-ce pas la même que ma deuxième réponse sauf moins optimisée?
Peter Taylor

Fermer, je suppose. J'utilise shift décalé donc je n'ai pas à faire le -1 à la fin.
Keith Randall
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.